Skip to content

Integrate motion, pointer, gel, and package validation#39

Merged
Jess Sullivan (Jesssullivan) merged 44 commits into
v0.3.0-develfrom
jess/integrate-v03-test-harness
May 1, 2026
Merged

Integrate motion, pointer, gel, and package validation#39
Jess Sullivan (Jesssullivan) merged 44 commits into
v0.3.0-develfrom
jess/integrate-v03-test-harness

Conversation

@Jesssullivan

@Jesssullivan Jess Sullivan (Jesssullivan) commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Integrates the current v0.3 cleanup work into v0.3.0-devel as reviewable slices:

  • hardened DeviceOrientation/TiltSource permission, calibration, smoothing, fallback, stale-IO neutral reset behavior, and status observability
  • pointer and scroll integration through TinyVectors with unit coverage, pointer capability auto-detection, and pointer leave/blur reset
  • browser/device harness with Chrome/CDP orientation probing, directional motion sign assertions, idle-reset coverage, motion-status coverage, pointer listener lifecycle coverage, and listener cleanup checks
  • Bazel/npm package validation, bundle-size gate, declaration build wrapper, and release-flow docs
  • Svelte lifecycle guard so async TinyVectors init cannot restart animation/listeners after cleanup
  • restored blob physics/rendering feel to the pre-Phase-A baseline after the gel-physics rewrite proved less pleasant in the live demo
  • added the TIN-853 / Define TinyVectors field-based feel contract #40 field-based feel contract, pure field helpers, and first runtime gravity/device-orientation field routing
  • aligned renderer/release docs after the restore and made the package root exports explicit for easier surface review and tree-shaking
  • updated check:bundle-size so tracked runtime field modules are reported when they enter the { TinyVectors } consumer bundle

Review Follow-up

  • Confirmed BlobPhysics owns/merges the TinyVectors default physics config, with unit coverage.
  • Removed the post-calibration warmup dead zone so output resumes immediately after requested samples.
  • Added unit coverage for the iOS Safari permission receiver binding.
  • Added --disable-dev-shm-usage to the Chrome/CDP probe for container stability.
  • Documented calibration sample consumption and hardened bundle-size env/threshold validation.
  • Preserved permission-created DeviceMotion during async component init so cleanup owns the active listener.
  • Tightened the Svelte peer floor to >=5.20.0 for current Svelte runtime assumptions.
  • Re-engaged DeviceMotion correctly when prefers-reduced-motion changes from reduce to no-preference.
  • Cleaned up scroll listener teardown by setup state so dynamic prop changes cannot leak the wheel listener.
  • Codified browser listener lifecycle coverage for scroll, pointer, and device-motion toggles in test:browser:motion.
  • Gated requestDeviceMotionPermission() on mounted physics so stale handles cannot create post-unmount listeners.
  • Tracked enableDeviceMotion in setup so live prop toggles follow the same cleanup path as scroll and pointer toggles.
  • Rolled back the Phase A gel-physics/rendering feel work: removed XSPH coupling, soft-wall rewrite, Gaussian anti-clustering replacement, tuned specular renderer, and the follow-up bandaid test.
  • Added docs/physics-feel-contract.md, internal InteractionField helpers, and the first gravity/device-orientation runtime route through that field contract.
  • Corrected the v0.3 release-flow compatibility note and optional CSS @property registration back to --tv-blob-intensity.
  • Replaced top-level wildcard re-exports with an explicit package-root export list.
  • Updated the bundle-size check to report tracked runtime field modules, including dist/core/InteractionField.js now that gravity routing imports it.
  • Added DeviceMotion idle reset, deviceMotionIdleResetMs, browser idle-reset coverage, and explicit pointer IO capability detection.
  • Added pointer pointerout/mouseout/blur reset so scroll/pointer state cannot preserve stale pointer location after viewport exit.
  • Added getDeviceMotionStatus() on the TinyVectors handle so host apps can inspect support, permission, and active listener state.
  • Shared DeviceMotion capability/permission detection between runtime and Svelte status reporting, with helper coverage.
  • Fixed fallback control-point smoothing to read from a pre-pass radius snapshot and added rotation-equivariance coverage, removing start-index directional bias.
  • Added browser probe coverage that a fully disabled IO start still renders and attaches no wheel, pointer, or device-orientation listeners.
  • Neutralized active device motion immediately when prefers-reduced-motion changes to reduce, with unit coverage.
  • Reset pointer physics on browser pointercancel, with public type exports, package-consumer coverage, browser listener lifecycle coverage, and docs.
  • Blocked orientation listener startup when reduced motion is enabled during an in-flight permission request, while preserving restart after reduced motion is disabled.
  • Added Chrome/CDP browser coverage that emulated prefers-reduced-motion: reduce neutralizes motion, removes the orientation listener, and restores exactly one listener on no-preference.
  • Supported legacy Safari-style reduced-motion addListener/removeListener media query APIs for device-motion gating and cleanup.
  • Honored ScrollHandler's existing maxForces config so scroll pull-force retention can be capped or disabled without changing defaults.
  • Added package-consumer type coverage for ScrollHandlerConfig from both package root and motion entrypoints.
  • Routed gravity/device-orientation through InteractionField as a bounded directional field, cached the resulting force outside the per-blob hot path, inlined the directional clamp for bundle recovery, and updated bundle-size reporting.
  • Asserted expected directional motion signs for synthetic and CDP orientation in the browser probe.
  • Recorded the current field-route status in the physics feel contract so gravity is distinguished from future pointer/scroll routing.
  • Captured the current pointer state contract: pointer IO updates the physics anchor, velocity, and per-blob distance, while standalone pointer force remains future field-routing work.
  • Fixed consecutive pointer velocity samples to diff against the current pointer anchor instead of a one-sample-stale anchor, with regression coverage.
  • Removed the fallback control-point smoothing hot-path radii allocation and made the gradient clamp winding-order symmetric, with invariant coverage.
  • Added field-helper invariants for neutral combination, directional clamping, and bounded point falloff alongside the first gravity runtime route.

Issue Map

Refs #24, #25, #26, #27, #28, #40.

Linear mapping:

  • TIN-819: Bazel/Nix/package authority
  • TIN-820: accelerometer/orientation permission, calibration, smoothing
  • TIN-821: pointer/mouse physics integration
  • TIN-822: browser/device-facing harness
  • TIN-823: gel/blob gradient rendering polish
  • TIN-853: field-based feel contract and interaction architecture

Validation

  • pnpm run check
  • pnpm run test (180 tests)
  • pnpm run build
  • pnpm run check:release-metadata
  • pnpm run check:package
  • pnpm run check:bundle-size (10.90 KiB gzip, 0.10 KiB target headroom; reports InteractionField is included for gravity routing)
  • pnpm run test:browser:motion (disabled-IO clean start, synthetic orientation, CDP orientation, directional motion signs, motion status, idle reset, reduced-motion listener lifecycle, pointer cancel/listener lifecycle, listener cleanup)
  • nix develop . --command bazel build //:pkg //:package_consumer_check //:bundle_size_check //:typecheck //:test --verbose_failures
  • pnpm run check:package-consumer
  • npm pack --dry-run ./bazel-bin/pkg
  • npm publish --dry-run --ignore-scripts --access public ./bazel-bin/pkg

Notes

The browser probe confirms synthetic deviceorientation and CDP orientation overrides change blob paths, reports device-motion status as enabled/supported/granted/active in Chrome, and verifies device-orientation silence returns motion to neutral. Raw CDP accelerometer override remains informational because the runtime intentionally uses DeviceOrientation/TiltSource rather than raw accelerometer events.

InteractionField now has its first runtime consumer: gravity/device-orientation is routed through a bounded directional field. Pointer and scroll routing remain future slices so the restored demo feel stays reviewable.

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR integrates DeviceOrientation permission/calibration/idle-reset, pointer and scroll physics, the InteractionField gravity routing, phase-A gel physics rollback, and a curated explicit package export surface across 49 files. The previous review threads (BlobPhysics defaults ownership, wheel listener teardown, calibration warmup dead zone, iOS Safari permission binding, Svelte peer floor) are all addressed in this revision.

Confidence Score: 5/5

Safe to merge; no P0 or P1 findings — only two P2 style/documentation concerns

No critical or blocking bugs found. Previous P1 threads are addressed. Two P2 findings (DEFAULT_BLOB_PHYSICS_CONFIG export inconsistency and scroll decay restart semantics change) are non-blocking style/documentation concerns that do not affect correctness.

src/core/BlobPhysics.ts (DEFAULT_BLOB_PHYSICS_CONFIG export gap) and src/motion/ScrollHandler.ts (decay restart semantics change)

Important Files Changed

Filename Overview
src/svelte/TinyVectors.svelte Major refactor integrating DeviceMotion, PointerPhysicsController, and ScrollHandler with correct reactive lifecycle guards, snapshot-based scroll/pointer/motion flags for teardown safety, and gated requestDeviceMotionPermission on mounted physics
src/motion/DeviceMotion.ts Comprehensive rewrite: permission flow, calibration, idle-reset, stale-event handling, dead-zone, reduced-motion toggle (including legacy addListener/removeListener), and emitNeutral – all cleanly separated and covered by unit tests
src/motion/PointerPhysicsController.ts New file providing coalesced-event pointer tracking, exit/blur/cancel reset with relatedTarget guard, RAF-batched position updates, and clean dispose; all behaviours covered by unit tests
src/motion/ScrollHandler.ts Added configurable maxForces, scrollEndTimer leak fix, disposed guard, and dispose() method; decay loop guard changes restart semantics subtly (see comment)
src/core/BlobPhysics.ts Phase-A gel physics rolled back; gravity routed through InteractionField (directionalBiasField, cached in setGravity); smoothControlPoints rewritten with pre-pass snapshot; DEFAULT_BLOB_PHYSICS_CONFIG exported but not in curated package surface
src/core/InteractionField.ts New field-math module (clampFieldVector, combineFieldVectors, directionalBiasField, smoothDistanceFalloff, pointAttractorField) with full invariant unit test coverage
src/index.ts Replaced wildcard re-exports with a complete explicit public surface; all previously public symbols verified present; new motion/svelte types added cleanly
src/motion/PointerMapper.ts New pure helper mapping client coordinates to physics-space with bounds guard and symmetric range centering

Sequence Diagram

sequenceDiagram
    participant Host as Host App
    participant TV as TinyVectors.svelte
    participant DM as DeviceMotion
    participant PP as PointerPhysicsController
    participant SH as ScrollHandler
    participant BP as BlobPhysics
    participant IF as InteractionField

    Host->>TV: mount (enableDeviceMotion, enableScrollPhysics, enablePointerPhysics)
    TV->>BP: new BlobPhysics(blobCount, physicsConfig)
    TV->>BP: init()
    BP-->>TV: ready

    TV->>DM: new DeviceMotion(callback, opts)
    TV->>DM: initialize()
    DM->>DM: observeReducedMotion()
    DM->>DM: startListening() / return 'prompt'

    TV->>SH: new ScrollHandler()
    TV->>PP: createPointerPhysicsController({target: window})

    loop Animation Frame
        TV->>BP: tick(dt)
        BP->>IF: gravityField (pre-cached via setGravity)
        BP-->>TV: getBlobs()
    end

    Note over DM,BP: On device orientation event
    DM->>DM: handleOrientation → calibrate / filter / deadZone
    DM->>TV: callback(MotionVector)
    TV->>BP: setGravity(x * strength, y * strength)
    TV->>BP: setTilt(motionData)
    BP->>IF: directionalBiasField(gravity, strength, maxForce)

    Note over PP,BP: On pointer move
    PP->>PP: getCoalescedEvents → rAF-batch
    PP->>BP: updateMousePosition(x, y)

    Note over SH,BP: On wheel event
    SH->>SH: handleScroll → stickiness / pullForces
    TV->>BP: setScrollStickiness(stickiness * 0.12)

    Host->>TV: unmount
    TV->>DM: cleanup()
    TV->>PP: dispose()
    TV->>SH: dispose()
    TV->>BP: dispose()
Loading

Reviews (29): Last reviewed commit: "fix(physics): compute pointer velocity f..." | Re-trigger Greptile

Comment thread src/svelte/TinyVectors.svelte
Comment thread src/motion/DeviceMotion.ts
Comment thread src/motion/DeviceMotion.ts
@Jesssullivan Jess Sullivan (Jesssullivan) marked this pull request as ready for review April 30, 2026 17:05
Comment thread src/svelte/BlobSVG.svelte
Comment thread src/svelte/TinyVectors.svelte
Comment thread src/core/BlobPhysics.ts
@Jesssullivan Jess Sullivan (Jesssullivan) merged commit 0322065 into v0.3.0-devel May 1, 2026
5 checks passed
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