Skip to content

Add local pointer field#41

Merged
Jess Sullivan (Jesssullivan) merged 2 commits into
v0.3.0-develfrom
jess/pointer-field-followup
May 1, 2026
Merged

Add local pointer field#41
Jess Sullivan (Jesssullivan) merged 2 commits into
v0.3.0-develfrom
jess/pointer-field-followup

Conversation

@Jesssullivan

Copy link
Copy Markdown
Contributor

Summary

Stacked on PR #39. Adds the first conservative standalone pointer-field slice:

  • applies a small local pointer attraction only after pointer IO moves away from the neutral center
  • keeps reset-to-center neutral so canceled/blurred pointer IO does not keep pulling blobs
  • removes now-dead private last-pointer anchor state while preserving pointer velocity coverage
  • keeps the generic point-field helper self-contained so runtime tree-shaking stays tighter
  • updates the physics feel contract current status

Refs #26 and #40.

Validation

  • pnpm vitest run tests/unit/core.test.ts tests/unit/interaction-field.test.ts
  • pnpm run check
  • pnpm run test (181 tests)
  • pnpm run build
  • pnpm run check:bundle-size (11.00 KiB gzip, rounded 0.00 KiB target overage; below 12.00 KiB gate)
  • pnpm run test:browser:motion

Notes

This deliberately keeps pointer strength/radius private and low. Browser pointer-locality probes should be the next slice before any stronger tuning.

@Jesssullivan

Copy link
Copy Markdown
Contributor Author

Follow-up pushed: 5641f28 test(browser): cover CDP pointer delivery.

What changed:

  • pnpm test:browser:motion now records page-level pointermove events in the Chrome/CDP probe.
  • The probe dispatches a real CDP mouse move while pointer physics is enabled and asserts the page receives one pointer event at (120, 180).
  • The JSON report now includes pointerDelivery with event count, last event coordinates, and observed animated path movement.
  • README and docs/physics-feel-contract.md now distinguish browser pointer-delivery coverage from deterministic unit-level pointer-locality coverage.

Validation:

  • node --check scripts/probe-motion-cdp.mjs
  • pnpm run test:browser:motion (Chrome 147; pointerDelivery.events: 1)
  • pnpm run check
  • pnpm run test (181 tests)

@Jesssullivan

Copy link
Copy Markdown
Contributor Author

CI update for 5641f28: Actions are green.

Checks:

  • package / resolve-runner: success
  • package / validate (22): success
  • publish jobs: skipped as expected for this PR

PR #41 is mergeable and clean against jess/integrate-v03-test-harness.

Maintenance note: the run emitted GitHub's Node.js 20 JavaScript action deprecation warning for actions/upload-artifact and pnpm/action-setup; no branch failure, but we should schedule a workflow/action-version cleanup before the June 2, 2026 Node 24 default.

@Jesssullivan Jess Sullivan (Jesssullivan) marked this pull request as ready for review May 1, 2026 15:37
@Jesssullivan Jess Sullivan (Jesssullivan) changed the base branch from jess/integrate-v03-test-harness to v0.3.0-devel May 1, 2026 15:37
@Jesssullivan Jess Sullivan (Jesssullivan) merged commit 5fd287a into v0.3.0-devel May 1, 2026
5 checks passed
@greptile-apps

greptile-apps Bot commented May 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a small local pointer-attraction field (applyPointerField) in BlobPhysics, removes the now-dead lastMouseX/Y anchor fields, inlines the pointAttractorField falloff in InteractionField, and extends the CDP motion probe to verify pointer delivery and blob-geometry response. Documentation and physics-feel-contract notes are updated to reflect the completed slice.

Confidence Score: 4/5

Safe to merge; two P2 quality notes in BlobPhysics.ts, no logic errors or regressions found.

All findings are P2: magic numbers in the private pointer-field method and a sentinel-based neutral check with a known center-coordinate edge case. No P1/P0 issues identified.

src/core/BlobPhysics.ts — pointer field constants and neutral sentinel worth revisiting before the next tuning slice.

Important Files Changed

Filename Overview
src/core/BlobPhysics.ts Adds applyPointerField with hardcoded radius/strength constants; removes now-dead lastMouseX/Y fields. Logic is correct but magic numbers and a sentinel-based neutral check are worth refining.
src/core/InteractionField.ts pointAttractorField now inlines the smoothDistanceFalloff computation and adds explicit radius/distance guards; behavior is identical to the prior call-through path.
tests/unit/core.test.ts Test name updated to reflect distant-pointer no-op, new test covers near vs far blob response; lastMouseX/Y assertions cleanly removed.
scripts/probe-motion-cdp.mjs Adds a CDP Input.dispatchMouseEvent probe that verifies pointermove delivery and blob-geometry change after a 500 ms wait; indentation of the injected listener block is slightly inconsistent with surrounding code.
docs/physics-feel-contract.md Status and slice descriptions updated to reflect the completed pointer-field slice; documentation-only change.
README.md Two sentences updated to mention pointer delivery in the CDP browser probe description; documentation-only change.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant BlobPhysics
    participant ConvexBlob

    Browser->>BlobPhysics: updateMousePosition(x, y)
    Note over BlobPhysics: stores mouseX/Y,<br/>computes mouseVelX/Y
    BlobPhysics->>BlobPhysics: updateScreensaverPhysics(blob, dt, t)
    BlobPhysics->>ConvexBlob: blob.mouseDistance = dist(blob, mouse)
    BlobPhysics->>BlobPhysics: applyAccelerometerForces(blob)
    BlobPhysics->>BlobPhysics: applyPointerField(blob)
    Note over BlobPhysics: skip if mouseX==50 && mouseY==50<br/>skip if distance >= 34<br/>apply (1-d/34)² × 0.0014 attraction
    BlobPhysics->>ConvexBlob: velocityX += dx * scale
    BlobPhysics->>ConvexBlob: velocityY += dy * scale
    BlobPhysics->>BlobPhysics: updateMovementWithAccelerometer(blob, t)
    BlobPhysics->>ConvexBlob: currentX += velocityX
    BlobPhysics->>ConvexBlob: currentY += velocityY
    BlobPhysics->>ConvexBlob: velocity *= 0.992 (damping)
Loading

Reviews (1): Last reviewed commit: "test(browser): cover CDP pointer deliver..." | Re-trigger Greptile

Comment thread src/core/BlobPhysics.ts
Comment on lines +496 to +508
private applyPointerField(blob: ConvexBlob): void {
if (this.mouseX === 50 && this.mouseY === 50) return;

const dx = this.mouseX - blob.currentX;
const dy = this.mouseY - blob.currentY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance === 0 || distance >= 34) return;

const normalized = 1 - distance / 34;
const scale = (normalized * normalized * 0.0014) / distance;
blob.velocityX += dx * scale;
blob.velocityY += dy * scale;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Magic numbers for pointer radius and strength

The hardcoded 34 (radius) and 0.0014 (strength) are embedded directly in the arithmetic without named constants. When the next tuning slice arrives (per the physics contract, "pointer-locality probes should be the next slice"), it will be easy to miss one of the two sites since the same values also appear implicitly in the test (distance >= 34, velocityX > 0). Extracting them as private named constants keeps the relationship explicit.

Suggested change
private applyPointerField(blob: ConvexBlob): void {
if (this.mouseX === 50 && this.mouseY === 50) return;
const dx = this.mouseX - blob.currentX;
const dy = this.mouseY - blob.currentY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance === 0 || distance >= 34) return;
const normalized = 1 - distance / 34;
const scale = (normalized * normalized * 0.0014) / distance;
blob.velocityX += dx * scale;
blob.velocityY += dy * scale;
}
private static readonly POINTER_FIELD_RADIUS = 34;
private static readonly POINTER_FIELD_STRENGTH = 0.0014;
private applyPointerField(blob: ConvexBlob): void {
if (this.mouseX === 50 && this.mouseY === 50) return;
const dx = this.mouseX - blob.currentX;
const dy = this.mouseY - blob.currentY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance === 0 || distance >= BlobPhysics.POINTER_FIELD_RADIUS) return;
const normalized = 1 - distance / BlobPhysics.POINTER_FIELD_RADIUS;
const scale = (normalized * normalized * BlobPhysics.POINTER_FIELD_STRENGTH) / distance;
blob.velocityX += dx * scale;
blob.velocityY += dy * scale;
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/core/BlobPhysics.ts
}

private applyPointerField(blob: ConvexBlob): void {
if (this.mouseX === 50 && this.mouseY === 50) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Neutral sentinel silently disables field at coordinate center

mouseX === 50 && mouseY === 50 is the sentinel for "no pointer input yet / pointer reset to neutral." If the coordinate space is [0, 100] (which mouseX = 50 at init and the mouseDistance formula suggest), a user whose cursor legitimately lands at the exact center will get no pointer attraction. The sentinel conflates "uninitialized" with a valid position. A separate boolean flag (e.g. private pointerActive = false) would eliminate the ambiguity without a performance cost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant