@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.
- package/package.json +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +33 -3
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +486 -451
- package/src/engine/control/first-person/DESIGN_COLLISION.md +365 -352
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +1 -14
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +20 -8
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -546
- package/src/engine/control/first-person/TODO.md +13 -11
- 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/WallJump.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallJump.js +11 -3
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallRun.js +183 -163
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
- package/src/engine/control/first-person/collision/KinematicMover.js +634 -592
- package/src/engine/control/first-person/prototype_first_person_controller.js +1003 -901
- 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 +944 -809
- package/src/engine/physics/body/BodyStorage.d.ts +9 -0
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
- package/src/engine/physics/body/BodyStorage.js +23 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +7 -0
- package/src/engine/physics/ccd/linear_sweep.d.ts +97 -0
- package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -0
- package/src/engine/physics/ccd/linear_sweep.js +238 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -3
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +227 -8
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -0
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBodyFlags.js +6 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.js +814 -811
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +325 -323
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +27 -8
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +235 -204
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +97 -13
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
- package/src/engine/physics/queries/overlap_shape.js +185 -183
- package/src/engine/simulation/Ticker.d.ts +14 -0
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +136 -1
- 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
|
@@ -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 "
|
|
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 "
|
|
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,
|
|
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 "
|
|
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.
|