@msb235/blits-rapier 0.6.0 → 0.7.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
4
4
  version and everything else the patch. The release workflow refuses a `rapier-v*` tag with no
5
5
  section here.
6
6
 
7
+ ## 0.7.1
8
+
9
+ ### Changed
10
+
11
+ - Built and tested against engine 1.1.1.
12
+
13
+ ## 0.7.0
14
+
15
+ ### Breaking
16
+
17
+ - `@msb235/blits` is a peer at `^1.1.0`, where it was `^1.0.0`. It needs that release's `mix.onSeek`
18
+ and `mix.call`.
19
+
20
+ ### Fixed
21
+
22
+ - `impulse`, `drop` and `body` are calls on the mix's tape, so a `cue` or any other host call made
23
+ after a seek back leaves the calls ahead behind, and `tape.switchBranch` brings them back. The
24
+ world learns of each seek from `onSeek`, paused or not, where it guessed from frame numbers and
25
+ timestamps. A host making a call again once the mix has played past it again has made it twice,
26
+ as with a `cue`.
27
+ - A seek forward after a seek back past memory, with a history store, replays exactly: the world
28
+ paged its log of pushes before stepping through it, so the stretch replayed with the wrong push.
29
+
7
30
  ## 0.6.0
8
31
 
9
32
  ### Breaking
package/README.md CHANGED
@@ -55,11 +55,12 @@ const { position, rotation } = sim.mix.probe(box);
55
55
  the shown pose moves, so handing it back starts the simulation from there. A voice outside the
56
56
  role adds on top of wherever physics has it.
57
57
  - **`body.impulse(v)`** applies an impulse once, on the step after the mix's current time.
58
- **`body.drop()`** takes the body out of the world then. Both are logged at that time, so a seek
59
- back before it undoes them and playing forward through it does them again. One of these calls,
60
- or `sim.body`, made after a seek lets go of the calls logged ahead of it before the seek, as a
61
- call on the mix's tape starts a new future: a host that makes its calls again on the way does
62
- each once.
58
+ **`body.drop()`** takes the body out of the world then. They and `sim.body` are calls on the
59
+ mix's tape (`mix.call`), so a seek back before one undoes it, and the mix makes it again as it
60
+ plays past, as it does a `cue`. Any host call made after a seek back, one of these or a call on
61
+ the mix, starts a new future without the calls that lay ahead, and `tape.switchBranch` brings
62
+ them back. A host that makes a call again after the mix has played past it again has made it
63
+ twice, as with a `cue`.
63
64
  - **`sim.world()`** is the Rapier world as the last `sync` left it, for reading: raycasts, contact
64
65
  queries, debugging draws. A change made to it directly is lost at the next seek.
65
66
 
@@ -137,14 +138,8 @@ deterministic build.
137
138
  ## Not handled
138
139
 
139
140
  - **Reads at another time.** `mix.project(t)` and `mix.trace` show a body where the world was at
140
- the last `sync`, not at `t`, since the world is a voice on a subject of its own. A seek is
141
- shown at once, before the next `sync`.
142
- - **Picking another future.** The tape's `switchBranch` brings back the mix's own calls, not
143
- `impulse`, `drop` or `body` calls a later one let go of. A call on the mix itself, such as a
144
- `cue`, made after a seek does not let go of those calls logged ahead.
145
- - **A seek forward past memory after playing on from a seek back**, with a `store`: the seek back
146
- cuts the pushes paged after it from the store, so the world replays that stretch with the push
147
- of the first frame it still holds. Within memory it replays exactly.
141
+ the last `sync` or seek, not at `t`, since the world is a voice on a subject of its own and a
142
+ patch reads no other subject. A seek is shown at once, before the next `sync`.
148
143
  - **Growth with a store.** Each `impulse`, `drop` and `body` call is kept for the session, since
149
144
  a seek past memory replays it and a body's description is a Rapier object, not data a store
150
145
  takes. Without a store they go once history can no longer reach them. Every body made keeps its
package/dist/physics.js CHANGED
@@ -208,36 +208,35 @@ export function physics(kit, opts) {
208
208
  return holds;
209
209
  };
210
210
  let next = 0;
211
- // The frame, mix time and host timestamp of the last `step`, the first frame after a seek it
212
- // noticed, and the frame `live` was read in. A seek comes and goes without the driver hearing.
213
- let lastSeq;
214
- let lastAt = Number.NEGATIVE_INFINITY;
215
- let lastTs = Number.NaN;
216
- let soughtSeq = Number.NEGATIVE_INFINITY;
217
- let liveSeq = Number.NaN;
218
211
  let startAt = Number.NaN;
219
- const sought = () => lastSeq !== undefined && history?.tape !== undefined && (m.seq > lastSeq + 1 || m.now < lastAt);
220
- /** Reads the world at the time sought after seeks since the last step, with no sync since. */
221
- const fresh = () => {
222
- if (lastSeq === undefined ||
223
- history?.tape === undefined ||
224
- liveSeq === m.seq ||
225
- m.timestamp !== lastTs)
226
- return;
212
+ // Where the mix stood before the coming frame, whose pushes stand for the stretch from there.
213
+ let from = Number.NEGATIVE_INFINITY;
214
+ // Sought since the world was last read, so the next read steps it to the time sought.
215
+ let stale = false;
216
+ m.onSeek((s) => {
217
+ // What was logged after the frame kept is from a future the tape makes again.
218
+ if (s.back)
219
+ for (let i = log.length - 1; i >= 0; i--)
220
+ if (log[i].seq > s.frame)
221
+ log.splice(i, 1);
222
+ from = s.at;
223
+ holding = {};
224
+ held.clear();
225
+ stale = true;
226
+ });
227
+ const read = () => {
228
+ stale = false;
227
229
  m.probe(WORLD);
228
230
  live = seen;
229
- liveSeq = m.seq;
230
231
  };
231
- /** Logs a call, letting go of the calls logged ahead of it before a seek, as the mix's tape does. */
232
- const call = (entry) => {
233
- if (sought())
234
- soughtSeq = lastSeq + 1;
235
- const from = entry.at - stepMs / 2;
236
- for (let i = log.length - 1; i >= 0 && log[i].at >= from; i--)
237
- if (log[i].seq < soughtSeq)
238
- log.splice(i, 1);
239
- insert(log, entry);
232
+ const fresh = () => {
233
+ if (stale)
234
+ read();
240
235
  };
236
+ // Apart from `body`, so the closures a body keeps do not hold its description once the tape lets go.
237
+ const adding = (key, desc, colliders) => (at, seq) => ({ at, seq, key, kind: 'add', desc, colliders });
238
+ /** Logs a call on the mix's tape, so the mix makes it again wherever it makes the host's again. */
239
+ const call = (label, entry) => m.call(label, () => insert(log, entry(now(), m.seq)));
241
240
  const step = () => {
242
241
  // Cued before the mix has a time, the world would catch up from mix time 0 on its first step.
243
242
  if (!started) {
@@ -266,24 +265,17 @@ export function physics(kit, opts) {
266
265
  startAt = at;
267
266
  // This frame stands for the frames logged after where the mix stood before it, up to its own
268
267
  // time. Those ahead stay for a seek forward, which replays them as the mix's tape does its calls.
269
- let before = lastAt;
270
- if (sought()) {
271
- before = m.project({ frame: seq - 1 }).timestamp;
272
- soughtSeq = lastSeq + 1;
273
- holding = {};
274
- held.clear();
275
- }
276
268
  let end = pushLog.length;
277
269
  while (end > 0 && pushLog[end - 1].at > at)
278
270
  end--;
279
271
  let start = end;
280
272
  while (start > 0 &&
281
- (pushLog[start - 1].at > before || pushLog[start - 1].at === at))
273
+ (pushLog[start - 1].at > from || pushLog[start - 1].at === at))
282
274
  start--;
283
275
  pushLog.splice(start, end - start, { at, seq, pushes, holds: holding, goals });
284
- lastSeq = seq;
285
- lastAt = at;
286
- lastTs = m.timestamp;
276
+ from = at;
277
+ // Stepped before older frames are paged, since a world behind after a seek steps through them.
278
+ read();
287
279
  let old = 0;
288
280
  while (old < pushLog.length - 1 && pushLog[old + 1].at < at - keep)
289
281
  old++;
@@ -297,9 +289,6 @@ export function physics(kit, opts) {
297
289
  seq,
298
290
  data: { pushes, holds, goals },
299
291
  })));
300
- m.probe(WORLD);
301
- live = seen;
302
- liveSeq = seq;
303
292
  // Without a store, calls older than any copy a replay starts from are in every copy already,
304
293
  // once history no longer reaches the world's first step, which a fresh world replays from.
305
294
  const reach = Math.min(at, live?.t ?? at) - keep - stepMs;
@@ -317,7 +306,7 @@ export function physics(kit, opts) {
317
306
  role: ROLE,
318
307
  body(desc, ...colliders) {
319
308
  const key = next++;
320
- call({ at: now(), seq: m.seq, key, kind: 'add', desc, colliders });
309
+ call('add body', adding(key, desc, colliders));
321
310
  const controls = {};
322
311
  keys.set(controls, `controls ${key}`);
323
312
  const own = (spec, subject) => {
@@ -339,8 +328,11 @@ export function physics(kit, opts) {
339
328
  driven.add(body);
340
329
  return own(spec, controls);
341
330
  },
342
- impulse: (v) => call({ at: now(), seq: m.seq, key, kind: 'impulse', v: [...v] }),
343
- drop: () => call({ at: now(), seq: m.seq, key, kind: 'drop' }),
331
+ impulse: (v) => {
332
+ const copied = [...v];
333
+ call('impulse', (at, seq) => ({ at, seq, key, kind: 'impulse', v: copied }));
334
+ },
335
+ drop: () => call('drop body', (at, seq) => ({ at, seq, key, kind: 'drop' })),
344
336
  };
345
337
  keys.set(body, `body ${key}`);
346
338
  body.simulated = m.cue({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msb235/blits-rapier",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Pairs Rapier rigid-body physics with a blits mix: voices push bodies, and each body's simulated pose blends with authored motion",
5
5
  "keywords": [
6
6
  "physics",
@@ -38,14 +38,14 @@
38
38
  "prepack": "rm -rf dist && npm run build"
39
39
  },
40
40
  "peerDependencies": {
41
- "@msb235/blits": "^1.0.0",
41
+ "@msb235/blits": "^1.1.0",
42
42
  "@dimforge/rapier2d-compat": ">=0.21.0 <1.0.0",
43
43
  "@dimforge/rapier3d-compat": ">=0.21.0 <1.0.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@dimforge/rapier2d-compat": "0.21.0",
47
47
  "@dimforge/rapier3d-compat": "0.21.0",
48
- "@msb235/blits": "1.0.0"
48
+ "@msb235/blits": "1.1.1"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@dimforge/rapier2d-compat": {