@unrulysystems/native-motion-core
v0.1.0-alpha.0
Published
The host-agnostic heart of the system (SPEC-CORE): a deterministic motion graph plus every pure animation subsystem, with **zero host dependencies** — nothing here may import react, react-native, reanimated, gesture-handler, or motion. That law is structu
Readme
@unrulysystems/native-motion-core
The host-agnostic heart of the system (SPEC-CORE): a deterministic motion graph plus every pure
animation subsystem, with zero host dependencies — nothing here may import react,
react-native, reanimated, gesture-handler, or motion. That law is structural: an oxlint
no-restricted-imports rule over src/** (root .oxlintrc.json, run by nub run lint) fails on
ANY non-relative import in non-test source (REQ-CORE-003). The native runtime, the web shim, and the conformance harness all consume this
one module.
Surface map (src/index.ts)
| Area | Role | Key files |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| graph / clock / scheduler | The deterministic motion graph: injectable clock + scheduler, motion values with clock-timestamped velocity | graph.ts, clock.ts, types.ts, motion-value.ts (internal) |
| spring | Analytic closed-form scalar spring solver | spring.ts |
| transition | User transition config → concrete physics; the retarget/interruption seam (retargetSpring seeds from live state) | transition.ts |
| timing | Duration + easing tween generator | timing.ts |
| value-types | Typed parsers + gamma-aware mixers (numeric/unit, color, complex), unified mix() | value-types/ |
| subset | The frozen universal-subset registry — the single source of truth every engine's prop surface derives from | subset/ |
| component | The declarative public API contract: Target/Transition (seconds), loud-fail validateTarget, pure resolveTarget | component/ |
| driver | The host-agnostic Driver seam + pure per-frame stepProp, reference driver, prop tiers | driver/ |
| presence | Exit-lifecycle state machine + controller (present → exiting → removed, settle-coupled) | presence/ |
| gesture | Pure gesture math (projection, snap, elastic, velocity normalization) + the begin→active→end/cancel session | gesture/ |
| layout | Pure FLIP projection (invert/projectAtProgress, top-left anchored Transform) + measurement lifecycle + projection session | layout/ |
Naming note: src/transition.ts (spring-config resolution, resolveSpring) and
src/component/transition.ts (public seconds→ms boundary, toSpringConfig) are different
seams that share a basename — read the directory.
Verification
- 460 colocated unit tests (39 files,
src/**plus thescripts/**gate suites), all deterministic underManualClock/ManualScheduler; green under Vitest. - Golden files under
src/__goldens__/pin the pinned-motion oracle's outputs; they are GENERATED bypackages/conformance/scripts/gen-*-goldens.mjs(core itself must never import motion-dom) and regenerated only on an intentional oracle bump. - Worklet tagging is manifest-driven (
scripts/worklet-tagging-manifest.mjs, the single source):scripts/tag-worklet-dist.mjspost-build tags the four FILE-level crossing modules (transition,spring,timing,driver/step), 19 FUNCTION-level directives on the dual-use modules (value-types/color,layout/*), and the taggedworklet-layout/runtime copies — an un-tagged crossing fails only live on device (0.5.x SIGSEGV / 0.10.x Remote-Function throw), so the lists are closed over transitive callees and verified fail-closed byscripts/check-worklet-banners.mjs(innub run check) and per-artifact byscripts/check-bundle-worklets.mjs(run byapps/mobile/scripts/proof-build.sh).
cd packages/core && bunx vitest run