@woosh/meep-engine 2.146.0 → 2.147.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/package.json +1 -1
  2. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +0 -11
  3. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  4. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +8 -6
  5. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  6. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -551
  7. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
  8. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  9. package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
  10. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  11. package/src/engine/control/first-person/abilities/Mantle.js +195 -188
  12. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  13. package/src/engine/control/first-person/abilities/WallRun.js +183 -175
  14. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
  15. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  16. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
  17. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
  18. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  19. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
  20. package/src/engine/ecs/EntityManager.d.ts +34 -11
  21. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  22. package/src/engine/ecs/EntityManager.js +71 -42
  23. package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
  24. package/src/engine/interpolation/Interpoland.d.ts +48 -0
  25. package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
  26. package/src/engine/interpolation/Interpoland.js +49 -0
  27. package/src/engine/interpolation/Interpolated.d.ts +101 -0
  28. package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
  29. package/src/engine/interpolation/Interpolated.js +149 -0
  30. package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
  31. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
  32. package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
  33. package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
  34. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
  35. package/src/engine/interpolation/InterpolationSystem.js +233 -0
  36. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
  37. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
  38. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
  39. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
  40. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
  41. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
  42. package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
  43. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
  44. package/src/engine/interpolation/pose_interpoland.js +27 -0
  45. package/src/engine/network/NetworkSession.d.ts +2 -2
  46. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  47. package/src/engine/network/NetworkSession.js +2 -2
  48. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  49. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
  50. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  51. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  52. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
  53. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  54. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  55. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
  56. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  57. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
  58. package/src/engine/physics/PLAN.md +10 -9
  59. package/src/engine/physics/ecs/PhysicsSystem.d.ts +64 -0
  60. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  61. package/src/engine/physics/ecs/PhysicsSystem.js +168 -0
  62. package/src/engine/physics/narrowphase/box_triangle_contact.js +15 -12
  63. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  64. package/src/engine/physics/narrowphase/narrowphase_step.js +28 -1
  65. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
  66. package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
  67. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
  68. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
@@ -0,0 +1,287 @@
1
+ # Interpolation system — build plan
2
+
3
+ Cross-cutting plan for fixed-step → render interpolation. Spans **render**,
4
+ **physics** (a producer), and **network** (a producer + the existing primitives
5
+ this builds on). Referenced from `engine/physics/PLAN.md`.
6
+
7
+ > Filename note: created as `INTEPOLATION_SYSTEM_PLAN.md` per the request; rename
8
+ > to `INTERPOLATION_SYSTEM_PLAN.md` if the spelling was unintended.
9
+
10
+ ---
11
+
12
+ ## Goal
13
+
14
+ The simulation advances on a fixed step (`EntityManager.fixedUpdateStepSize`,
15
+ ~60 Hz). The renderer runs at a variable, usually higher, display rate on its own
16
+ `requestAnimationFrame` loop. Between two fixed steps the rendered pose is frozen
17
+ at the last step, so motion stutters / temporally aliases — worst at low fixed
18
+ rates or high refresh. This system smooths the discrete per-tick poses across the
19
+ render rate.
20
+
21
+ ## Hard constraints (these shaped every decision)
22
+
23
+ 1. **Write `Transform`, not the renderable.** Scene culling, mesh BVHs, particle
24
+ systems, trails, and attachment hierarchies are all driven off the ECS
25
+ `Transform`. Writing an interpolated pose into a three.js object directly
26
+ would smooth *some* visuals and desync the rest. The interpolated pose must
27
+ land in `Transform` so the whole engine sees it uniformly. (Bonus: attachment
28
+ children of an interpolated physics parent then track it **for free**, because
29
+ the attachment system is push-based on the parent's `Transform.onChanged`.)
30
+ 2. **Only interpolate what moved.** Per-frame cost must be O(moving bodies),
31
+ never O(total). The engine targets massive worlds — tens of thousands of
32
+ dynamic bodies with only a tiny awake/moving fraction at any moment.
33
+ 3. **Don't fight physics.** Physics must never integrate from an interpolated
34
+ pose. The simulation reads authoritative state; interpolation is a downstream
35
+ render channel.
36
+ 4. **Co-designed with network.** The network package can change (not yet shipped).
37
+ Interpolation must share machinery with replication, not duplicate it.
38
+ 5. **Rewind-safe.** The network can rewind and re-simulate. If a rewind+resim
39
+ produces the same state, the observer must see the **same** interpolated
40
+ result — no visual drift from re-running history.
41
+ 6. **Agnostic to what it interpolates.** Translation+rotation is one case;
42
+ network may interpolate other replicated quantities. The mechanism blends
43
+ *encoded snapshots of any registered component type*.
44
+
45
+ ## Key realization — reuse, don't rebuild
46
+
47
+ The network package already contains the agnostic interpolation engine. Physics
48
+ becomes a *producer* into it; we do not build a parallel system.
49
+
50
+ - `network/sim/BinaryInterpolationAdapter.js` —
51
+ `interpolate(out, source, offA, offB, t)`: type-agnostic blend of two encoded
52
+ snapshots. Concrete `Transform` / `Vector3` / `Quaternion` adapters exist.
53
+ **This is constraint 6, already solved.**
54
+ - `network/sim/InterpolationLog.js` — a **tick-indexed ring** (`Map<tick, …>`)
55
+ of per-component snapshots, recording **only components that changed that tick**
56
+ (the package's O(mutations)-not-O(entities) bet == constraint 2). Keyed by a
57
+ plain integer; `interpolate(out, key, type_id, tickA, tickB, t, adapter)`
58
+ blends two ticks into an out-buffer that is then deserialized into the live
59
+ component.
60
+ - `network/sim/SimActionExecutor.js` — `before_execute` hook, JSDoc:
61
+ *"NetworkSession undoes render-time interpolation on remote-owned components"*
62
+ before reading authoritative state. **The architecture already committed to
63
+ interpolation writing the live component and the sim restoring authoritative
64
+ before it reads** (constraints 1 + 3).
65
+ - `network/sim/ActionLog.js` + `network/sim/RewindEngine.js` — rewind restores
66
+ prior authoritative state; resim replays forward via `onReplay`
67
+ (constraint 5).
68
+
69
+ ## Locked decisions
70
+
71
+ 1. **Neutral shared primitive.** Lift `InterpolationLog`,
72
+ `BinaryInterpolationAdapter`, and the blend adapters out of `network/` into a
73
+ neutral `engine/interpolation/`. Physics and network both produce into it; one
74
+ render-time pass blends for both. Replication + rewind layer on top **only**
75
+ for networked entities — single-player physics pays no action-log cost.
76
+ 2. **Restore-from-log at step start.** Before each physics tick, reset each moving
77
+ body's live `Transform` to its latest authoritative tick snapshot (a *quiet*
78
+ write that bypasses `onChanged`). The solver / narrowphase / broadphase /
79
+ integrators keep reading & writing `Transform` exactly as today — **no rewiring
80
+ of the physics hot paths.** The authoritative "cache" is the log's newest tick.
81
+
82
+ ## Glossary (load-bearing terms)
83
+
84
+ - **Tick** — one fixed simulation step, identified by a monotonic integer *tick
85
+ number*. The orchestrator's frame number when networked; an internal counter
86
+ otherwise.
87
+ - **Authoritative pose / endpoint** — the exact pose the simulation produced at
88
+ the *end* of a tick. The thing physics owns and rewind restores.
89
+ - **Snapshot** — an authoritative pose encoded into the log under
90
+ `(log key, type_id, tick)` via a serialization adapter.
91
+ - **Log key** — the integer identity under which an entity's snapshots are
92
+ recorded and looked up in the shared log. `network_id` when the entity is
93
+ networked (so a server's recording and a client's receipt line up under one
94
+ identity); a locally-allocated interp id otherwise. Carried on the
95
+ `Interpolated` component. (See §"Q&A".)
96
+ - **alpha** — `accumulator / fixedUpdateStepSize ∈ [0, 1)`: the fractional
97
+ progress between the two most-recent *completed* ticks. The blend parameter `t`.
98
+ We render **between** the last two ticks (one step in the past), so both
99
+ endpoints always exist — no extrapolation. (See §"Q&A".)
100
+ - **`InterpolationSystem`** — the system that owns the render-time blend pass. It
101
+ runs in `System.update(dt)` (the variable-rate, rAF-driven cycle), NOT ad-hoc in
102
+ a `preRender` callback. `update` can fire on ticks where no render happens —
103
+ harmless (a blend with no following draw is just discarded). If display-exact
104
+ timing is ever needed, the system can additionally hook `preRender` from its
105
+ `startup`; `update` is the default.
106
+ - **`Interpolated` component** — opt-in marker physics & render cooperate around.
107
+ Holds the **log key** (defaults to `-1`; `InterpolationSystem.link` assigns it
108
+ `entity_id` when still `-1`, so the common case needs no manual setup — the
109
+ network system can instead set it to the `network_id` directly for replicated
110
+ entities), the blend type/adapter id, and a `snap` flag. A body without it keeps
111
+ today's direct-write behavior (this is how physics "sees" who to cooperate
112
+ with).
113
+
114
+ ## Architecture — one engine, two producers
115
+
116
+ **Per fixed tick, for bodies carrying `Interpolated`:**
117
+
118
+ 1. **Restore (step start):** reset the live `Transform` to the log's latest
119
+ authoritative tick — a quiet write (no `onChanged`). This is `before_execute`
120
+ / "undo render interpolation" applied to physics. Physics then reads truth.
121
+ 2. **Step:** physics integrates as today → `Transform` = new authoritative pose.
122
+ 3. **Record (step end):** if it actually moved (`|Δ| > ε`), snapshot the pose into
123
+ the current tick. Sparse by construction → O(moving).
124
+
125
+ **Per `InterpolationSystem.update(dt)` (render/rAF cadence):** for each
126
+ `Interpolated` entity, pick `(tickA, tickB, t)` and blend → deserialize into the
127
+ live `Transform` (this write *does* fire `onChanged` → culling/particles/trails/
128
+ attachments update). O(moving). `t` source is per-producer: local =
129
+ `EntityManager.getFixedStepAlpha(physicsSystem)`; remote = the network's existing
130
+ render-delay/jitter playout.
131
+
132
+ ```
133
+ simulate() [fixed-step catch-up loop] InterpolationSystem.update(dt) [rAF cadence]
134
+ for awake+Interpolated: for Interpolated e:
135
+ restore Transform ← log.latest (quiet) (a,b,t) = window(e) // t = getFixedStepAlpha(physics)
136
+ ...physics step → Transform = authoritative blend(a,b,t) → Transform (onChanged)
137
+ for moved+Interpolated: → later draw passes read Transform
138
+ log.record(key, tick, Transform)
139
+ ```
140
+
141
+ ## Why each constraint holds
142
+
143
+ 1. **Writes Transform** — blend deserializes into the live component; every
144
+ Transform-linked system works, hierarchy included.
145
+ 2. **Only moving** — produce & blend only for moved bodies; the log is sparse.
146
+ 3. **Doesn't fight physics** — restore-at-step-start; physics never sees the
147
+ interpolated pose. Provable by an interp-on vs interp-off sim-state bit-identity
148
+ test.
149
+ 4. **Co-designed with network** — same log, same adapters, same `before_execute`;
150
+ physics is a new producer into the existing pipeline.
151
+ 5. **Rewind-safe** — the log is tick-keyed; resim re-runs the deterministic sim,
152
+ which re-records the *same* tick numbers with identical bytes → identical
153
+ blend output. Requirement: the replay path records snapshots like the live path
154
+ (it runs the same systems), and the log supports re-recording rewound ticks.
155
+ 6. **Agnostic** — `BinaryInterpolationAdapter`; pose is one type; any component
156
+ with a (serialization adapter + interpolation adapter) pair interpolates the
157
+ same way.
158
+
159
+ ## Invariants
160
+
161
+ - **Interpolation never feeds back into the sim.** Guarded by restore-at-step-start
162
+ and an interp-on/off sim bit-identity test.
163
+ - **The log's latest tick is the single authoritative cache** between steps;
164
+ `Transform` is a derived render channel for interpolated bodies.
165
+ - **Sparse by motion** — O(moving), never O(N).
166
+
167
+ ## Precision note (massive worlds)
168
+
169
+ The network *wire* Transform adapter uses Float32 position + smallest-three
170
+ quaternion for bandwidth. That quantizes badly far from the origin. The shared
171
+ **interp log stores full-precision snapshots** (Float64 position + full
172
+ quaternion) for both local and remote (remote is re-serialized at full precision
173
+ on apply); wire compression stays on the wire only.
174
+
175
+ ## Contract change (interpolated bodies only)
176
+
177
+ For a body carrying `Interpolated`, raw external `Transform` writes are no longer
178
+ authoritative — restore-at-step-start would clobber them. External authoritative
179
+ changes (teleport, kinematic drive) go through a `setPose` / `teleport` API that
180
+ updates the log's latest tick **and** sets `snap` (so the next render does not
181
+ slide across the jump). Bodies *without* `Interpolated` keep today's raw-write
182
+ teleport contract.
183
+
184
+ ## Phases
185
+
186
+ Each phase: implement → spec → run from `H:/git/moh` → commit. Black-box tests,
187
+ headless where possible (the log, adapters, and physics producer are
188
+ headless-testable; the render hook is thin).
189
+
190
+ > **Status (Phases 0–2 landed).** Key deltas from the original sketch, decided
191
+ > during build: the fixed-step **tick lives on `EntityManager`**
192
+ > (`fixedStepTick`), not a per-system clock, and `getFixedStepAlpha()` is the one
193
+ > global sim-clock alpha (no system arg). To make the per-step tick exact, the
194
+ > scheduler was changed to a **global lock-step loop** on a single accumulator
195
+ > (commit `0fd41f3`) — all systems' `fixedUpdate` advance together, tick++ per
196
+ > step. The physics producer is **agnostic** (iterates the component's
197
+ > interpolands, uses their adapters) and reads its log via an injected
198
+ > `PhysicsSystem.interpolationLog` (null → skipped). Restore reads `tick-1`,
199
+ > record writes `tick`; lock-step makes both exact with no per-body tick field.
200
+
201
+ - [x] **Phase 0 — Neutralize the primitive** (`0980e45`). `InterpolationLog` +
202
+ `BinaryInterpolationAdapter` (+ spec) moved to `engine/interpolation/`; network
203
+ importers repointed. `EntityManager.getFixedStepAlpha`. (Log key generalization
204
+ is cosmetic — it was already a plain integer.)
205
+ - [x] **Phase 1 — `Interpolated` + `InterpolationSystem`** (`fc3c3aa`).
206
+ `Interpoland` descriptor; `Interpolated` carries a **list** of interpolands
207
+ (one component-per-class → many smoothed quantities); `link` auto-assigns
208
+ `key = entity` when `-1`; `update` blends each interpoland `(tick-1, tick)` at
209
+ `getFixedStepAlpha()` into the live component. Full-precision (Float64) pose
210
+ adapters + `POSE_INTERPOLAND`. Tested: blend / snap / untouched-when-empty /
211
+ key auto-assign / multi-interpoland.
212
+ - [x] **Phase 2 — Physics as producer** (`a05db76` tick + `dade4d6` producer;
213
+ scheduler `0fd41f3`). `__interp_restore` (top of `fixedUpdate`, undo render
214
+ interpolation) + `__interp_record` (after solve/CCD, before sleep test).
215
+ **Determinism guard passes**: interp ON (live Transform polluted every awake
216
+ step) vs OFF → bit-identical pose AND velocity every step through fall →
217
+ contact → settle → sleep.
218
+ - [x] *`setPose` teleport API* (`691f9a0`). `PhysicsSystem.setPose(rb, pos, rot)`
219
+ writes the Transform, wakes, and sets `snap`. `snap` semantics finalized:
220
+ the consumer **leaves the live pose untouched** for that frame (a teleport's
221
+ new pose isn't in the log yet) and the producer's restore **skips** a snapped
222
+ body. *Known edge:* clean for the common ~1:1 sim:render rate; when render
223
+ rate ≫ sim rate a teleport could show a brief flicker (the latest tick isn't
224
+ retroactively overwritten) — fixable later with an in-place latest-tick
225
+ overwrite if it ever matters.
226
+ - [x] *End-to-end wiring verified* (`691f9a0`,
227
+ `InterpolationSystem.integration.spec`). A real `EntityManager` driving
228
+ physics + `InterpolationSystem` with `physics.interpolationLog = interp.log`
229
+ interpolates a body's `Transform` every render frame between fixed steps
230
+ (control with interp off is frozen between steps). *App wiring* (registering
231
+ the systems + connecting the log in the actual game world) is still the app
232
+ layer's job — the recipe is the integration spec.
233
+ - **Phase 3 — Network unification.** *Architecture + coherence landed; live
234
+ NetworkSession migration is the remaining integration.*
235
+ - **Finding:** the network package *already* does remote interpolation on the
236
+ shared primitive — `NetworkSession` owns an `InterpolationLog`, records
237
+ remote-owned components on `onFrameApplied` (`#on_frame_applied`), undoes
238
+ render interpolation via `normalize_if_dirty` (the `before_execute` hook),
239
+ and blends each remote entity through the SAME `log.interpolate → deserialize`
240
+ path as the local system, on an `AdaptiveRenderDelay`-smoothed playout frame
241
+ (`#render_interpolated_entities`). So the *primitive* was unified by Phase 0;
242
+ what was missing was a single *consumer* spanning both timelines.
243
+ - [x] **Unified consumer.** Component stays pure data: `Interpolated.sourceId`
244
+ (an int; `INTERPOLATION_SOURCE_LOCAL = 0` default = local fixed-step clock +
245
+ the system's own log). The timelines' logs + window providers live on the
246
+ SYSTEM, registered via `InterpolationSystem.registerSource(id, log, sample)`
247
+ (the network owns its playout window). One `update` blends local and
248
+ source-driven entities, each source sampled once per frame. *Test:* a local
249
+ sim-clock entity and a remote playout-source entity both interpolate
250
+ correctly in one `update` (local + remote coherent in one scene).
251
+ - [ ] **Live migration (remaining):** point `NetworkSession`'s remote entities
252
+ at a `NetworkInterpolationSource` (wrapping its playout window + log), mark
253
+ them with `Interpolated`, and drop `#render_interpolated_entities` so the
254
+ `InterpolationSystem` is the sole render consumer. Deferred to avoid
255
+ destabilizing the tested netcode; needs exposing the session's playout window
256
+ as a side-effect-free `sample()`.
257
+ - **Phase 4 — Rewind/resim correctness.** Interp log supports resim re-recording:
258
+ on rewind to target, truncate ticks ≥ target; resim re-records forward.
259
+ *Test:* sim to T, capture interp-log bytes; rewind to T−k, resim to T with
260
+ identical inputs; assert interp-log bytes over (T−k, T] are bit-identical →
261
+ observer-identical when nothing changed.
262
+ - **Phase 5 — Camera + edges.** Camera following an interpolated body reads the
263
+ interpolated source pose. Spawn/despawn & key reuse via generation; verify a
264
+ visual child of an interpolated physics parent tracks it (hierarchy-for-free);
265
+ kinematic/parked filtered by the moved-ε test.
266
+
267
+ ## Open items (flagged, not yet decided)
268
+
269
+ - **Local-vs-remote render timeline.** Local predicted-present vs remote
270
+ delayed-playout is the classic netcode coherence question. v1 can render both
271
+ at local-present; refine when the predict/reconcile client lands.
272
+ - **`onChanged` suppression at sim time.** Interpolated bodies could suppress
273
+ `integrate_position`'s `onChanged` (the render blend will set the final pose).
274
+ Keep it for now — it keeps culling current between slow render frames.
275
+
276
+ ## Source touchpoints (verify at implementation)
277
+
278
+ - `engine/physics/integration/integrate_position.js` — writes `Transform` via
279
+ `.set()` (fires `onChanged`); restore must be a quiet sibling write.
280
+ - `engine/physics/body/BodyStorage.js` — awake set (`awake_at`/`awake_count`) =
281
+ moving candidates; generation = spawn/despawn/reuse detection.
282
+ - `engine/ecs/EntityManager.js` — `fixedUpdateStepSize`, the per-system
283
+ accumulator `systemAccumulatedFixedStepTime`, the fixed-step catch-up loop.
284
+ - `engine/graphics/GraphicsEngine.js` — `on.preRender` (the blend hook, fired
285
+ before the draw passes).
286
+ - `engine/network/sim/{InterpolationLog,BinaryInterpolationAdapter,
287
+ SimActionExecutor,RewindEngine}.js` — the primitives to neutralize/extend.
@@ -777,15 +777,16 @@ scaffolding is in place.
777
777
  each body's pose straight into the ECS `Transform` once per fixed step
778
778
  (`EntityManager.fixedUpdateStepSize`); with a render rate that doesn't match
779
779
  the fixed rate, the rendered motion aliases (stutter / temporal aliasing,
780
- worst at low fixed rates). The standard fix is to keep the previous and
781
- current fixed-step pose per body and have the renderer lerp position /
782
- slerp rotation by the accumulator's fractional remainder
783
- (`alpha = leftover / fixedStep`). Open design questions: where the
784
- double-buffered "previous pose" lives so it does NOT bloat the simulation
785
- hot state or the netcode-replicated component set (a render-side component
786
- vs. the physics body), and how teleports / kinematic snaps opt out of
787
- interpolation for a frame. Sits at the physics↔render seam, not in the
788
- solver.
780
+ worst at low fixed rates). Designed as a cross-cutting system (render +
781
+ physics-as-producer + network-as-producer), reusing the network package's
782
+ agnostic interpolation primitives rather than a physics-local mechanism.
783
+ Full design + phasing: **see
784
+ [`INTEPOLATION_SYSTEM_PLAN.md`](./INTEPOLATION_SYSTEM_PLAN.md)**. Locked
785
+ decisions: a neutral shared interpolation log/adapters (lifted out of
786
+ `network/`); physics restores authoritative pose from the log at step start
787
+ (no solver rewiring) and records moved bodies' snapshots at step end;
788
+ blend → `Transform` at `preRender`; O(moving) not O(N); rewind-safe via the
789
+ tick-keyed log.
789
790
 
790
791
  ### API polish
791
792
  - [x] **`overlap(shape, position, rotation, output, output_offset,
@@ -218,6 +218,23 @@ export class PhysicsSystem extends System<any, any, any, any, any> {
218
218
  * @private
219
219
  */
220
220
  private __pair_filter_bound;
221
+ /**
222
+ * Optional shared interpolation log to produce per-step pose snapshots
223
+ * into (the {@link InterpolationSystem}'s log). When set — and only then —
224
+ * each fixedUpdate restores every awake {@link Interpolated} body's live
225
+ * components from the previous tick (undoing render-time interpolation so
226
+ * the sim reads authoritative state), then records the post-step state
227
+ * under the current `entityManager.fixedStepTick`. Null on a headless /
228
+ * non-rendering world, where the producer work is skipped entirely.
229
+ * @type {InterpolationLog|null}
230
+ */
231
+ interpolationLog: InterpolationLog | null;
232
+ /**
233
+ * Reusable decode buffer for restoring interpolated snapshots.
234
+ * @private
235
+ * @type {BinaryBuffer}
236
+ */
237
+ private __interp_scratch;
221
238
  /**
222
239
  * Symmetric layer/mask check + optional user callback. Called per
223
240
  * candidate pair during broadphase. Returns `true` to accept the pair.
@@ -468,6 +485,32 @@ export class PhysicsSystem extends System<any, any, any, any, any> {
468
485
  y: number;
469
486
  z: number;
470
487
  }): void;
488
+ /**
489
+ * Teleport a body to a new pose, bypassing integration: writes the body's
490
+ * Transform directly and wakes it. For an interpolated body this also flags a
491
+ * render `snap` on its {@link Interpolated} component, so the producer keeps
492
+ * this pose (rather than restoring the previous tick over it) and the
493
+ * renderer shows the new pose without sliding across the jump.
494
+ *
495
+ * This is the authoritative way to reposition an interpolated body — a raw
496
+ * `Transform` write would be undone by the per-step restore. Velocity is left
497
+ * as-is; zero it via {@link setLinearVelocity} if the teleport should also
498
+ * stop the body.
499
+ *
500
+ * @param {RigidBody} rigidBody
501
+ * @param {Vector3|{x:number,y:number,z:number}} position world position
502
+ * @param {Quaternion|{x:number,y:number,z:number,w:number}} rotation world unit-quaternion rotation
503
+ */
504
+ setPose(rigidBody: RigidBody, position: Vector3 | {
505
+ x: number;
506
+ y: number;
507
+ z: number;
508
+ }, rotation: Quaternion | {
509
+ x: number;
510
+ y: number;
511
+ z: number;
512
+ w: number;
513
+ }): void;
471
514
  /**
472
515
  * Force the body awake. Static bodies are ignored.
473
516
  * @param {RigidBody} rigidBody
@@ -674,6 +717,27 @@ export class PhysicsSystem extends System<any, any, any, any, any> {
674
717
  * @private
675
718
  */
676
719
  private __dispatch_contact_events;
720
+ /**
721
+ * Producer — restore pass. At the top of a fixed step, reset every awake
722
+ * {@link Interpolated} body's live components to their authoritative state
723
+ * from the previous tick's snapshot, undoing any render-time interpolation
724
+ * the {@link InterpolationSystem} wrote between frames so the sim integrates
725
+ * from truth, not an interpolated pose. A body with no previous snapshot
726
+ * (first step ever, or just woken) is left as-is — its live state is already
727
+ * authoritative. No-op unless {@link interpolationLog} is wired.
728
+ * @private
729
+ */
730
+ private __interp_restore;
731
+ /**
732
+ * Producer — record pass. At the end of a fixed step, snapshot every awake
733
+ * {@link Interpolated} body's live components into the shared log under the
734
+ * current `entityManager.fixedStepTick`. The render-time
735
+ * {@link InterpolationSystem} blends consecutive ticks from these snapshots.
736
+ * Only awake (moving) bodies are recorded, so the log stays sparse. No-op
737
+ * unless {@link interpolationLog} is wired.
738
+ * @private
739
+ */
740
+ private __interp_record;
677
741
  fixedUpdate(dt: any): void;
678
742
  /**
679
743
  * @readonly
@@ -1 +1 @@
1
- {"version":3,"file":"PhysicsSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/PhysicsSystem.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PhysicsSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/PhysicsSystem.js"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { assert } from "../../../core/assert.js";
2
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
2
3
  import { BVH } from "../../../core/bvh2/bvh3/BVH.js";
3
4
  import Signal from "../../../core/events/signal/Signal.js";
4
5
  import Vector3 from "../../../core/geom/Vector3.js";
@@ -47,6 +48,7 @@ import { Collider, COLLIDER_UNBOUND } from "./Collider.js";
47
48
  import { RIGID_BODY_UNALLOCATED, RigidBody } from "./RigidBody.js";
48
49
  import { RigidBodyFlags } from "./RigidBodyFlags.js";
49
50
  import { SleepState } from "./SleepState.js";
51
+ import { Interpolated } from "../../interpolation/Interpolated.js";
50
52
 
51
53
  /**
52
54
  * Reusable scratch buffer for world-AABB construction so the link path is
@@ -319,6 +321,26 @@ export class PhysicsSystem extends System {
319
321
  * @private
320
322
  */
321
323
  this.__pair_filter_bound = (idA, idB) => this.__pair_filter(idA, idB);
324
+
325
+ /**
326
+ * Optional shared interpolation log to produce per-step pose snapshots
327
+ * into (the {@link InterpolationSystem}'s log). When set — and only then —
328
+ * each fixedUpdate restores every awake {@link Interpolated} body's live
329
+ * components from the previous tick (undoing render-time interpolation so
330
+ * the sim reads authoritative state), then records the post-step state
331
+ * under the current `entityManager.fixedStepTick`. Null on a headless /
332
+ * non-rendering world, where the producer work is skipped entirely.
333
+ * @type {InterpolationLog|null}
334
+ */
335
+ this.interpolationLog = null;
336
+
337
+ /**
338
+ * Reusable decode buffer for restoring interpolated snapshots.
339
+ * @private
340
+ * @type {BinaryBuffer}
341
+ */
342
+ this.__interp_scratch = new BinaryBuffer();
343
+ this.__interp_scratch.fromArrayBuffer(new ArrayBuffer(256));
322
344
  }
323
345
 
324
346
  /**
@@ -829,6 +851,50 @@ export class PhysicsSystem extends System {
829
851
  }
830
852
  }
831
853
 
854
+ /**
855
+ * Teleport a body to a new pose, bypassing integration: writes the body's
856
+ * Transform directly and wakes it. For an interpolated body this also flags a
857
+ * render `snap` on its {@link Interpolated} component, so the producer keeps
858
+ * this pose (rather than restoring the previous tick over it) and the
859
+ * renderer shows the new pose without sliding across the jump.
860
+ *
861
+ * This is the authoritative way to reposition an interpolated body — a raw
862
+ * `Transform` write would be undone by the per-step restore. Velocity is left
863
+ * as-is; zero it via {@link setLinearVelocity} if the teleport should also
864
+ * stop the body.
865
+ *
866
+ * @param {RigidBody} rigidBody
867
+ * @param {Vector3|{x:number,y:number,z:number}} position world position
868
+ * @param {Quaternion|{x:number,y:number,z:number,w:number}} rotation world unit-quaternion rotation
869
+ */
870
+ setPose(rigidBody, position, rotation) {
871
+ const idx = body_id_index(rigidBody._bodyId);
872
+ const transform = this.__transforms[idx];
873
+ if (transform === undefined) {
874
+ return;
875
+ }
876
+
877
+ transform.position.set(position.x, position.y, position.z);
878
+ transform.rotation.set(rotation.x, rotation.y, rotation.z, rotation.w);
879
+
880
+ this.__wake_body(rigidBody);
881
+
882
+ // Flag a snap on the body's Interpolated component, if any, so the
883
+ // interpolation producer (restore) and consumer (blend) treat this pose
884
+ // as authoritative this frame instead of interpolating across the jump.
885
+ const em = this.entityManager;
886
+ if (em !== null && em !== undefined) {
887
+ const dataset = em.dataset;
888
+ if (dataset !== null && dataset !== undefined) {
889
+ const entity = this.storage.entity_at(idx);
890
+ const interpolated = dataset.getComponent(entity, Interpolated);
891
+ if (interpolated !== undefined && interpolated !== null) {
892
+ interpolated.snap = true;
893
+ }
894
+ }
895
+ }
896
+ }
897
+
832
898
  /**
833
899
  * Force the body awake. Static bodies are ignored.
834
900
  * @param {RigidBody} rigidBody
@@ -1273,7 +1339,103 @@ export class PhysicsSystem extends System {
1273
1339
  }
1274
1340
  }
1275
1341
 
1342
+ /**
1343
+ * Producer — restore pass. At the top of a fixed step, reset every awake
1344
+ * {@link Interpolated} body's live components to their authoritative state
1345
+ * from the previous tick's snapshot, undoing any render-time interpolation
1346
+ * the {@link InterpolationSystem} wrote between frames so the sim integrates
1347
+ * from truth, not an interpolated pose. A body with no previous snapshot
1348
+ * (first step ever, or just woken) is left as-is — its live state is already
1349
+ * authoritative. No-op unless {@link interpolationLog} is wired.
1350
+ * @private
1351
+ */
1352
+ __interp_restore() {
1353
+ const log = this.interpolationLog;
1354
+ const em = this.entityManager;
1355
+ if (log === null || em === null || em === undefined) return;
1356
+ const dataset = em.dataset;
1357
+ if (dataset === null || dataset === undefined) return;
1358
+
1359
+ const prev_tick = em.fixedStepTick - 1;
1360
+ if (prev_tick < 0) return;
1361
+
1362
+ const storage = this.storage;
1363
+ const count = storage.awake_count;
1364
+ const scratch = this.__interp_scratch;
1365
+
1366
+ for (let i = 0; i < count; i++) {
1367
+ const idx = storage.awake_at(i);
1368
+ const entity = storage.entity_at(idx);
1369
+ const interpolated = dataset.getComponent(entity, Interpolated);
1370
+ if (interpolated === undefined || interpolated === null) continue;
1371
+ // A teleported body (snap set, e.g. via setPose) keeps its live pose
1372
+ // this step — restoring the previous tick would undo the teleport.
1373
+ if (interpolated.snap) continue;
1374
+ const key = interpolated.key;
1375
+ if (key < 0) continue;
1376
+
1377
+ const interpolands = interpolated.interpolands;
1378
+ for (let k = 0; k < interpolands.length; k++) {
1379
+ const ip = interpolands[k];
1380
+ scratch.position = 0;
1381
+ // Snap to the previous tick's snapshot (both offsets equal → t irrelevant).
1382
+ const ok = log.interpolate(scratch, key, ip.type_id, prev_tick, prev_tick, 0, ip.interpolation_adapter);
1383
+ if (!ok) continue;
1384
+ const target = dataset.getComponent(entity, ip.component_class);
1385
+ if (target === undefined || target === null) continue;
1386
+ scratch.position = 0;
1387
+ ip.serialization_adapter.deserialize(scratch, target);
1388
+ }
1389
+ }
1390
+ }
1391
+
1392
+ /**
1393
+ * Producer — record pass. At the end of a fixed step, snapshot every awake
1394
+ * {@link Interpolated} body's live components into the shared log under the
1395
+ * current `entityManager.fixedStepTick`. The render-time
1396
+ * {@link InterpolationSystem} blends consecutive ticks from these snapshots.
1397
+ * Only awake (moving) bodies are recorded, so the log stays sparse. No-op
1398
+ * unless {@link interpolationLog} is wired.
1399
+ * @private
1400
+ */
1401
+ __interp_record() {
1402
+ const log = this.interpolationLog;
1403
+ const em = this.entityManager;
1404
+ if (log === null || em === null || em === undefined) return;
1405
+ const dataset = em.dataset;
1406
+ if (dataset === null || dataset === undefined) return;
1407
+
1408
+ const tick = em.fixedStepTick;
1409
+ const storage = this.storage;
1410
+ const count = storage.awake_count;
1411
+
1412
+ log.begin_tick(tick);
1413
+ for (let i = 0; i < count; i++) {
1414
+ const idx = storage.awake_at(i);
1415
+ const entity = storage.entity_at(idx);
1416
+ const interpolated = dataset.getComponent(entity, Interpolated);
1417
+ if (interpolated === undefined || interpolated === null) continue;
1418
+ const key = interpolated.key;
1419
+ if (key < 0) continue;
1420
+
1421
+ const interpolands = interpolated.interpolands;
1422
+ for (let k = 0; k < interpolands.length; k++) {
1423
+ const ip = interpolands[k];
1424
+ const target = dataset.getComponent(entity, ip.component_class);
1425
+ if (target === undefined || target === null) continue;
1426
+ const buf = log.begin_record(key, ip.type_id);
1427
+ ip.serialization_adapter.serialize(buf, target);
1428
+ log.end_record();
1429
+ }
1430
+ }
1431
+ log.end_tick();
1432
+ }
1433
+
1276
1434
  fixedUpdate(dt) {
1435
+ // Producer: restore authoritative pose (undo render interpolation)
1436
+ // before the sim reads any Transform this step.
1437
+ this.__interp_restore();
1438
+
1277
1439
  const gx = this.gravity.x;
1278
1440
  const gy = this.gravity.y;
1279
1441
  const gz = this.gravity.z;
@@ -1454,6 +1616,12 @@ export class PhysicsSystem extends System {
1454
1616
  ccd_resolve(this);
1455
1617
  }
1456
1618
 
1619
+ // Producer: record the post-step authoritative pose of every awake
1620
+ // interpolated body under this step's tick. Before the sleep test, so a
1621
+ // body that settles this step still records its final pose for the last
1622
+ // interpolation interval.
1623
+ this.__interp_record();
1624
+
1457
1625
  // Stage 9: sleep test.
1458
1626
  this.__sleep_test(dt);
1459
1627