@woosh/meep-engine 2.146.0 → 2.148.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.
- package/package.json +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +0 -11
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +8 -6
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -551
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +195 -188
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallRun.js +183 -175
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
- package/src/engine/ecs/EntityManager.d.ts +34 -11
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +71 -42
- package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.d.ts +48 -0
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.js +49 -0
- package/src/engine/interpolation/Interpolated.d.ts +101 -0
- package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
- package/src/engine/interpolation/Interpolated.js +149 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
- package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
- package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
- package/src/engine/interpolation/InterpolationSystem.js +233 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
- package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/pose_interpoland.js +27 -0
- package/src/engine/network/NetworkSession.d.ts +2 -2
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +2 -2
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
- package/src/engine/physics/PLAN.md +10 -9
- package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
- package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
- package/src/engine/physics/body/SolverBodyState.js +251 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +5 -3
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +691 -673
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -15
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +387 -87
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
- package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
- package/src/engine/physics/integration/integrate_position.d.ts +11 -1
- package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_position.js +97 -79
- package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
- package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_velocity.js +201 -160
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +19 -46
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +52 -4
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +7 -4
- package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +1341 -1173
- package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
- package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
- /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
- /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).
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # SolverBodyState — data-oriented mirror of the per-body solver hot state
|
|
3
|
+
*
|
|
4
|
+
* The TGS substep window (contact solver, joint solver, gravity / position
|
|
5
|
+
* integration) reads and writes a small, fixed set of per-body quantities
|
|
6
|
+
* thousands of times per step: linear / angular velocity, inverse mass,
|
|
7
|
+
* inverse-inertia diagonal, and orientation. In the object world those live on
|
|
8
|
+
* the {@link RigidBody} and {@link Transform} *components*, reached via
|
|
9
|
+
* `system.__bodies[idx]` / `system.__transforms[idx]` — a chain of pointer
|
|
10
|
+
* dereferences (array → component object → `Vector3` field) on the hottest
|
|
11
|
+
* inner loop in the engine.
|
|
12
|
+
*
|
|
13
|
+
* This class packs exactly those quantities into one ArrayBuffer-backed,
|
|
14
|
+
* stride-{@link SBS_STRIDE} `Float64Array` indexed by **body slot index** (the
|
|
15
|
+
* same index the rest of the physics system already uses). A body's whole
|
|
16
|
+
* solver state sits in one contiguous span, so the solver reads it with flat
|
|
17
|
+
* typed-array indexing and no object dereference.
|
|
18
|
+
*
|
|
19
|
+
* ## Lifecycle within one `fixedUpdate`
|
|
20
|
+
*
|
|
21
|
+
* - {@link begin} once, after islands are built: ensures capacity and rolls
|
|
22
|
+
* the per-step gather stamp.
|
|
23
|
+
* - {@link gather} every body referenced this step (the awake set + the
|
|
24
|
+
* static / kinematic anchors referenced by contacts and joints). Idempotent
|
|
25
|
+
* per step via the stamp, so callers may gather the same body redundantly.
|
|
26
|
+
* MUST run after the once-per-step force integration (so the gathered
|
|
27
|
+
* velocity is post-force) and before any substep stage reads it.
|
|
28
|
+
* - the substep loop mutates `data` in place.
|
|
29
|
+
* - {@link scatter} once at the end: write the persistent linear / angular
|
|
30
|
+
* velocity back to each gathered body's `RigidBody`. Pose is NOT scattered
|
|
31
|
+
* here — `integrate_position` keeps the authoritative pose on the
|
|
32
|
+
* `Transform` (write-through, so its `onChanged` subscribers and the
|
|
33
|
+
* per-substep concave re-detection still see the moved pose); the
|
|
34
|
+
* orientation quaternion is mirrored into `data` purely for the impulse
|
|
35
|
+
* loop's world-inverse-inertia evaluation.
|
|
36
|
+
*
|
|
37
|
+
* ## Material content
|
|
38
|
+
*
|
|
39
|
+
* For Static / Kinematic bodies `invMass` and the inverse-inertia diagonal are
|
|
40
|
+
* stored as **zero** regardless of the component values — the solver treats
|
|
41
|
+
* "no inverse mass / no inverse inertia" as immovable, which is exactly how the
|
|
42
|
+
* object path's `kind !== Dynamic` guards behave. Velocity is mirrored as-is
|
|
43
|
+
* (a kinematic mover carries a real velocity the contact rows must see; a
|
|
44
|
+
* static reads zero). Scattering velocity back to a non-dynamic body is a
|
|
45
|
+
* no-op in value (the solver never moves it), so scatter need not special-case.
|
|
46
|
+
*
|
|
47
|
+
* The arithmetic the solver performs on this data is identical to the object
|
|
48
|
+
* path — only the *source of the operands* changes — so the engine's
|
|
49
|
+
* same-runtime bit-identical determinism contract is preserved.
|
|
50
|
+
*
|
|
51
|
+
* @author Alex Goldring
|
|
52
|
+
* @copyright Company Named Limited (c) 2026
|
|
53
|
+
*/
|
|
54
|
+
/** Inverse mass (0 for Static / Kinematic). */
|
|
55
|
+
export const SBS_INV_MASS: 0;
|
|
56
|
+
/** Inverse-inertia diagonal, body-local principal frame (0,0,0 for non-dynamic). */
|
|
57
|
+
export const SBS_INV_I_X: 1;
|
|
58
|
+
export const SBS_INV_I_Y: 2;
|
|
59
|
+
export const SBS_INV_I_Z: 3;
|
|
60
|
+
/** Orientation quaternion (mirrors `transform.rotation`, refreshed per substep). */
|
|
61
|
+
export const SBS_QX: 4;
|
|
62
|
+
export const SBS_QY: 5;
|
|
63
|
+
export const SBS_QZ: 6;
|
|
64
|
+
export const SBS_QW: 7;
|
|
65
|
+
/** Persistent linear velocity. */
|
|
66
|
+
export const SBS_LV_X: 8;
|
|
67
|
+
export const SBS_LV_Y: 9;
|
|
68
|
+
export const SBS_LV_Z: 10;
|
|
69
|
+
/** Persistent angular velocity. */
|
|
70
|
+
export const SBS_AV_X: 11;
|
|
71
|
+
export const SBS_AV_Y: 12;
|
|
72
|
+
export const SBS_AV_Z: 13;
|
|
73
|
+
/** Doubles per body. */
|
|
74
|
+
export const SBS_STRIDE: 14;
|
|
75
|
+
export class SolverBodyState {
|
|
76
|
+
constructor(initial_capacity?: number);
|
|
77
|
+
/**
|
|
78
|
+
* Interleaved per-body state, `slot_index * SBS_STRIDE + field`.
|
|
79
|
+
* @type {Float64Array}
|
|
80
|
+
*/
|
|
81
|
+
data: Float64Array;
|
|
82
|
+
/**
|
|
83
|
+
* Per-body "gathered this step" stamp; equals {@link __gen} when the
|
|
84
|
+
* body's slot has been gathered in the current step.
|
|
85
|
+
* @private
|
|
86
|
+
* @type {Int32Array}
|
|
87
|
+
*/
|
|
88
|
+
private __stamp;
|
|
89
|
+
/**
|
|
90
|
+
* Dense list of slot indices gathered this step, for {@link scatter}.
|
|
91
|
+
* @private
|
|
92
|
+
* @type {Uint32Array}
|
|
93
|
+
*/
|
|
94
|
+
private __gathered;
|
|
95
|
+
/** @private */
|
|
96
|
+
private __gathered_count;
|
|
97
|
+
/**
|
|
98
|
+
* Monotonic per-step generation. {@link begin} increments it so the
|
|
99
|
+
* previous step's stamps are stale without an O(n) clear.
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
private __gen;
|
|
103
|
+
/** @private */
|
|
104
|
+
private __capacity;
|
|
105
|
+
/**
|
|
106
|
+
* Number of bodies gathered in the current step.
|
|
107
|
+
* @returns {number}
|
|
108
|
+
*/
|
|
109
|
+
get gathered_count(): number;
|
|
110
|
+
/**
|
|
111
|
+
* Ensure the typed arrays can index slot `[0, hwm)` and roll the per-step
|
|
112
|
+
* gather state. Call once per `fixedUpdate`, after islands are built.
|
|
113
|
+
*
|
|
114
|
+
* @param {number} hwm body-storage high-water mark
|
|
115
|
+
*/
|
|
116
|
+
begin(hwm: number): void;
|
|
117
|
+
/**
|
|
118
|
+
* Mirror one body's solver state into `data` if not already gathered this
|
|
119
|
+
* step. Idempotent per step. Reads the body's current (post-force)
|
|
120
|
+
* velocity and current pose.
|
|
121
|
+
*
|
|
122
|
+
* @param {number} idx body slot index
|
|
123
|
+
* @param {RigidBody} rb
|
|
124
|
+
* @param {Transform} transform
|
|
125
|
+
*/
|
|
126
|
+
gather(idx: number, rb: RigidBody, transform: Transform): void;
|
|
127
|
+
/**
|
|
128
|
+
* Write the solved persistent linear / angular velocity back onto every
|
|
129
|
+
* gathered body's `RigidBody`. Direct typed-array writes into the
|
|
130
|
+
* `Vector3` backing — the same observer-bypassing path the solver already
|
|
131
|
+
* uses for velocity (`lv[0] += …`), so no `onChanged` semantics change.
|
|
132
|
+
*
|
|
133
|
+
* @param {RigidBody[]} bodies sparse, indexed by slot
|
|
134
|
+
*/
|
|
135
|
+
scatter(bodies: RigidBody[]): void;
|
|
136
|
+
/**
|
|
137
|
+
* @private
|
|
138
|
+
* @param {number} hwm
|
|
139
|
+
*/
|
|
140
|
+
private __grow;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=SolverBodyState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SolverBodyState.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/body/SolverBodyState.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,+CAA+C;AAC/C,6BAA8B;AAC9B,oFAAoF;AACpF,4BAA6B;AAC7B,4BAA6B;AAC7B,4BAA6B;AAC7B,oFAAoF;AACpF,uBAAwB;AACxB,uBAAwB;AACxB,uBAAwB;AACxB,uBAAwB;AACxB,kCAAkC;AAClC,yBAA0B;AAC1B,yBAA0B;AAC1B,0BAA2B;AAC3B,mCAAmC;AACnC,0BAA2B;AAC3B,0BAA2B;AAC3B,0BAA2B;AAE3B,wBAAwB;AACxB,4BAA6B;AAE7B;IACI,uCAoCC;IAjCG;;;OAGG;IACH,MAFU,YAAY,CAEwB;IAE9C;;;;;OAKG;IACH,gBAAkC;IAElC;;;;OAIG;IACH,mBAAsC;IAEtC,eAAe;IACf,yBAAyB;IAEzB;;;;OAIG;IACH,cAAc;IAEd,eAAe;IACf,mBAAqB;IAGzB;;;OAGG;IACH,6BAEC;IAED;;;;;OAKG;IACH,WAFW,MAAM,QAiBhB;IAED;;;;;;;;OAQG;IACH,YAJW,MAAM,6CA8ChB;IAED;;;;;;;OAOG;IACH,gBAFW,WAAW,QAoBrB;IAED;;;OAGG;IACH,eAcC;CACJ"}
|