@woosh/meep-engine 2.145.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 (99) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +33 -3
  3. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  4. package/src/core/geom/3d/shape/HeightMapShape3D.js +486 -451
  5. package/src/engine/control/first-person/DESIGN_COLLISION.md +365 -352
  6. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +1 -14
  7. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  8. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +20 -8
  9. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  10. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -546
  11. package/src/engine/control/first-person/TODO.md +13 -11
  12. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
  13. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  14. package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
  15. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  16. package/src/engine/control/first-person/abilities/Mantle.js +195 -188
  17. package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -1
  18. package/src/engine/control/first-person/abilities/WallJump.js +11 -3
  19. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  20. package/src/engine/control/first-person/abilities/WallRun.js +183 -163
  21. package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
  22. package/src/engine/control/first-person/collision/KinematicMover.js +634 -592
  23. package/src/engine/control/first-person/prototype_first_person_controller.js +1003 -901
  24. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
  25. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  26. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
  27. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
  28. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  29. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
  30. package/src/engine/ecs/EntityManager.d.ts +34 -11
  31. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  32. package/src/engine/ecs/EntityManager.js +71 -42
  33. package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
  34. package/src/engine/interpolation/Interpoland.d.ts +48 -0
  35. package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
  36. package/src/engine/interpolation/Interpoland.js +49 -0
  37. package/src/engine/interpolation/Interpolated.d.ts +101 -0
  38. package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
  39. package/src/engine/interpolation/Interpolated.js +149 -0
  40. package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
  41. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
  42. package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
  43. package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
  44. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
  45. package/src/engine/interpolation/InterpolationSystem.js +233 -0
  46. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
  47. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
  48. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
  49. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
  50. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
  51. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
  52. package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
  53. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
  54. package/src/engine/interpolation/pose_interpoland.js +27 -0
  55. package/src/engine/network/NetworkSession.d.ts +2 -2
  56. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  57. package/src/engine/network/NetworkSession.js +2 -2
  58. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  59. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
  60. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  61. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  62. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
  63. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  64. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  65. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
  66. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  67. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
  68. package/src/engine/physics/PLAN.md +944 -809
  69. package/src/engine/physics/body/BodyStorage.d.ts +9 -0
  70. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  71. package/src/engine/physics/body/BodyStorage.js +23 -0
  72. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  73. package/src/engine/physics/broadphase/generate_pairs.js +7 -0
  74. package/src/engine/physics/ccd/linear_sweep.d.ts +97 -0
  75. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -0
  76. package/src/engine/physics/ccd/linear_sweep.js +238 -0
  77. package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -3
  78. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  79. package/src/engine/physics/ecs/PhysicsSystem.js +227 -8
  80. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -0
  81. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
  82. package/src/engine/physics/ecs/RigidBodyFlags.js +6 -0
  83. package/src/engine/physics/narrowphase/box_triangle_contact.js +814 -811
  84. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  85. package/src/engine/physics/narrowphase/compute_penetration.js +325 -323
  86. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +27 -8
  87. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
  88. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +235 -204
  89. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  90. package/src/engine/physics/narrowphase/narrowphase_step.js +97 -13
  91. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  92. package/src/engine/physics/queries/overlap_shape.js +185 -183
  93. package/src/engine/simulation/Ticker.d.ts +14 -0
  94. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  95. package/src/engine/simulation/Ticker.js +136 -1
  96. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
  97. package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
  98. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
  99. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
@@ -2,7 +2,7 @@ import { quat_decode_from_uint32 } from "../../../core/geom/3d/quaternion/quat_d
2
2
  import { quat_encode_to_uint32 } from "../../../core/geom/3d/quaternion/quat_encode_to_uint32.js";
3
3
  import { v3_binary_equality_decode } from "../../../core/geom/vec3/serialization/v3_binary_equality_decode.js";
4
4
  import { v3_binary_equality_encode } from "../../../core/geom/vec3/serialization/v3_binary_equality_encode.js";
5
- import { BinaryInterpolationAdapter } from "../sim/BinaryInterpolationAdapter.js";
5
+ import { BinaryInterpolationAdapter } from "../../interpolation/BinaryInterpolationAdapter.js";
6
6
 
7
7
  /**
8
8
  * Module-level scratch. Layout: `[aq.xyzw, bq.xyzw, as.xyz, bs.xyz]`.
@@ -14,5 +14,5 @@
14
14
  */
15
15
  export class Vector3InterpolationAdapter extends BinaryInterpolationAdapter {
16
16
  }
17
- import { BinaryInterpolationAdapter } from "../sim/BinaryInterpolationAdapter.js";
17
+ import { BinaryInterpolationAdapter } from "../../interpolation/BinaryInterpolationAdapter.js";
18
18
  //# sourceMappingURL=Vector3InterpolationAdapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Vector3InterpolationAdapter.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/adapters/Vector3InterpolationAdapter.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH;CA4BC;2CA5C0C,sCAAsC"}
1
+ {"version":3,"file":"Vector3InterpolationAdapter.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/adapters/Vector3InterpolationAdapter.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH;CA4BC;2CA5C0C,mDAAmD"}
@@ -1,5 +1,5 @@
1
1
  import { lerp } from "../../../core/math/lerp.js";
2
- import { BinaryInterpolationAdapter } from "../sim/BinaryInterpolationAdapter.js";
2
+ import { BinaryInterpolationAdapter } from "../../interpolation/BinaryInterpolationAdapter.js";
3
3
 
4
4
  /**
5
5
  * Componentwise lerp of a 3-Float32 position payload.
@@ -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.