@irtio/runtime 0.7.0 → 0.9.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/dist/{chunk-EXPFVRD4.js → chunk-ADZZEVJU.js} +868 -30
- package/dist/{chunk-ZNNTF7Y3.js → chunk-SBDZ45JG.js} +10 -2
- package/dist/{contract-DwxqjeWP.d.ts → contract-C5aqs49-.d.ts} +23 -1
- package/dist/index.d.ts +18 -5
- package/dist/index.js +12 -2
- package/dist/{room-CoQDczh2.d.ts → room-C4RZJ2KO.d.ts} +192 -15
- package/dist/test/index.d.ts +15 -4
- package/dist/test/index.js +12 -4
- package/dist/worker/index.d.ts +9 -1
- package/dist/worker/index.js +14 -3
- package/package.json +5 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseHibernationBlob,
|
|
3
3
|
writeHibernationBlob
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ADZZEVJU.js";
|
|
5
5
|
|
|
6
6
|
// src/migrate.ts
|
|
7
7
|
import { PRESENCE_COLLECTION, withBuiltins } from "@irtio/protocol";
|
|
@@ -53,7 +53,15 @@ function migrateSnapshot(blob, chain, options) {
|
|
|
53
53
|
}
|
|
54
54
|
plain = fromMigrationState(withBuiltins(schema), state, last.version);
|
|
55
55
|
const bytes = writeHibernationBlob(
|
|
56
|
-
{
|
|
56
|
+
{
|
|
57
|
+
seed: parsed.seed,
|
|
58
|
+
rngState: parsed.rngState,
|
|
59
|
+
tick: parsed.tick,
|
|
60
|
+
mode: parsed.mode,
|
|
61
|
+
// Stamped with the *target* schema: these bytes are now that schema's, and a room waking on
|
|
62
|
+
// it must not see them as a mismatch.
|
|
63
|
+
schemaHash8: withBuiltins(schema).hash8
|
|
64
|
+
},
|
|
57
65
|
encodeSnapshot(withBuiltins(schema), plain, { tick: parsed.tick })
|
|
58
66
|
);
|
|
59
67
|
return {
|
|
@@ -250,6 +250,24 @@ interface RoomHost {
|
|
|
250
250
|
* A host with no room list (the in-process test harness) records it and does nothing else.
|
|
251
251
|
*/
|
|
252
252
|
setBackfill(open: boolean): void;
|
|
253
|
+
/**
|
|
254
|
+
* D75: `room.lobby.setPublic(v)`, and the terminal deregistration a game start performs.
|
|
255
|
+
*
|
|
256
|
+
* Fire-and-forget, and host state rather than room state, for exactly `setBackfill`'s reason:
|
|
257
|
+
* the supervisor holds it, reports it on the room list the agent already polls, and control
|
|
258
|
+
* turns that report into a registry row. None of which the room could arrange for itself and
|
|
259
|
+
* none of which belongs in the hibernation blob.
|
|
260
|
+
*
|
|
261
|
+
* `started` is a one-way door. A room that reports it is deregistered terminally, and no later
|
|
262
|
+
* report — raced, stale or forged — can put it back in front of a stranger.
|
|
263
|
+
*
|
|
264
|
+
* A host with no room list (the in-process test harness) records it and does nothing else.
|
|
265
|
+
*/
|
|
266
|
+
setLobby(update: {
|
|
267
|
+
readonly public?: boolean;
|
|
268
|
+
readonly queue?: string;
|
|
269
|
+
readonly started?: boolean;
|
|
270
|
+
}): void;
|
|
253
271
|
/**
|
|
254
272
|
* D44: `room.spawnNPC(config)`. The host opens a loopback client session under `clientId` and
|
|
255
273
|
* runs the named entry of the room definition's `npcs` map against it. The room minted the id,
|
|
@@ -284,7 +302,11 @@ interface RoomHost {
|
|
|
284
302
|
}
|
|
285
303
|
interface RoomCoreOptions {
|
|
286
304
|
readonly roomId: string;
|
|
287
|
-
/**
|
|
305
|
+
/**
|
|
306
|
+
* Seed for `room.random()`. Defaults to a stable 32-bit hash of `roomId`, so two rooms of the
|
|
307
|
+
* same game generate different worlds; pass a number to pin the sequence instead. A room
|
|
308
|
+
* restored from a snapshot ignores both and keeps the seed it was created with.
|
|
309
|
+
*/
|
|
288
310
|
readonly seed?: number;
|
|
289
311
|
/** Base URL for `room.link` (`<publicUrl>?room=<id>`); default `http://localhost/`. */
|
|
290
312
|
readonly publicUrl?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { D as DEFAULT_TIMELINE_MAX_RECORDS, a as DEFAULT_TIMELINE_MAX_TICKS, E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, b as HostCall, c as HostCallResult, J as JoinOptions, d as JoinResult, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, f as RoomEvent, g as RoomEventKind, h as RoomFullError, i as RoomHost, j as RoomInspection, k as RoomStats, T as TimelineDump, l as TimelineFrame, m as TimelineRecorder, n as TimelineRecorderOptions, o as inspectState } from './contract-
|
|
2
|
-
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as MatterBodyRecord, b as MatterSection, c as Mulberry32, P as PhysicsEngineTag, d as PhysicsSection, R as RoomCore, e as decodeMatterBodies, f as decodeMatterSectionEnvelope, g as decodePhysicsSection, h as
|
|
1
|
+
export { D as DEFAULT_TIMELINE_MAX_RECORDS, a as DEFAULT_TIMELINE_MAX_TICKS, E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, b as HostCall, c as HostCallResult, J as JoinOptions, d as JoinResult, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, f as RoomEvent, g as RoomEventKind, h as RoomFullError, i as RoomHost, j as RoomInspection, k as RoomStats, T as TimelineDump, l as TimelineFrame, m as TimelineRecorder, n as TimelineRecorderOptions, o as inspectState } from './contract-C5aqs49-.js';
|
|
2
|
+
export { C as CRASH_AFTER_THROWS, M as MAX_CATCHUP, a as MatterBodyRecord, b as MatterSection, c as Mulberry32, P as PhysicsEngineTag, d as PhysicsSection, R as RoomCore, e as decodeMatterBodies, f as decodeMatterSectionEnvelope, g as decodePhysicsSection, h as decodeRapier2dSectionEnvelope, i as encodeMatterBodies, j as encodeMatterSectionEnvelope, k as encodePhysicsSection, l as encodeRapier2dSectionEnvelope, m as initMatter, n as initPhysics, o as initRapier2d, p as loadedMatter, q as loadedPhysics, r as loadedRapier2d, s as physicsSectionEngine, t as resetMatterForTests, u as resetPhysicsForTests, v as resetRapier2dForTests } from './room-C4RZJ2KO.js';
|
|
3
3
|
import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
|
|
4
4
|
import '@irtio/protocol';
|
|
5
5
|
import '@irtio/server';
|
|
@@ -70,9 +70,16 @@ declare function catchUpDirty(ext: AnySchema, plain: PlainState, fromRole: strin
|
|
|
70
70
|
* u32 rng state
|
|
71
71
|
* u32 tick
|
|
72
72
|
* u8 mode (1 = event, 0 = tick)
|
|
73
|
+
* [v3] 8 bytes schema hash (`withBuiltins(schema).hash8`)
|
|
73
74
|
* [v2] blob physics section (varint length + bytes; length 0 = no world)
|
|
74
75
|
* ... encodeSnapshot(withBuiltins(schema), state)
|
|
75
76
|
*
|
|
77
|
+
* **v3** adds the schema hash. Before it, restoring a snapshot written under a different schema
|
|
78
|
+
* fed the old bytes to the new decoder and surfaced as a `TypeError` from deep inside the string
|
|
79
|
+
* decoder; the room could only be started again by hand-deleting `.irtio/snapshots`. The hash is
|
|
80
|
+
* in the envelope (not only in the codec payload) so the check costs no decode: a mismatch is
|
|
81
|
+
* seen, logged and the snapshot discarded before anything is parsed against the wrong schema.
|
|
82
|
+
*
|
|
76
83
|
* **v2 (week 9, D22)** adds the physics section: the Rapier world snapshot plus the entity↔handle
|
|
77
84
|
* map (`core/physics.ts` encodes it). It rides *inside* this blob rather than beside it so that
|
|
78
85
|
* wake is atomic — state and world always come back from the same bytes, and every store, cache
|
|
@@ -89,8 +96,8 @@ declare function catchUpDirty(ext: AnySchema, plain: PlainState, fromRole: strin
|
|
|
89
96
|
* instead of parsing bytes it may not yet know how to read.
|
|
90
97
|
*/
|
|
91
98
|
|
|
92
|
-
/** Written when the
|
|
93
|
-
declare const SNAPSHOT_FORMAT_VERSION =
|
|
99
|
+
/** Written when the header carries a schema hash (every room does); older versions still read. */
|
|
100
|
+
declare const SNAPSHOT_FORMAT_VERSION = 3;
|
|
94
101
|
/** Versions this build can parse. */
|
|
95
102
|
declare const READABLE_SNAPSHOT_VERSIONS: readonly number[];
|
|
96
103
|
type SnapshotMode = 'tick' | 'event';
|
|
@@ -99,6 +106,12 @@ interface SnapshotHeader {
|
|
|
99
106
|
readonly rngState: number;
|
|
100
107
|
readonly tick: number;
|
|
101
108
|
readonly mode: SnapshotMode;
|
|
109
|
+
/**
|
|
110
|
+
* `withBuiltins(schema).hash8` of the schema these bytes were written under. Omitted only by a
|
|
111
|
+
* writer that has no schema in hand; a restore that finds it absent cannot check and proceeds
|
|
112
|
+
* as pre-v3 builds did.
|
|
113
|
+
*/
|
|
114
|
+
readonly schemaHash8?: Uint8Array | undefined;
|
|
102
115
|
}
|
|
103
116
|
interface ParsedSnapshot extends SnapshotHeader {
|
|
104
117
|
/** The blob's own format version (1 or 2). */
|
|
@@ -121,7 +134,7 @@ interface DecodedSave extends SnapshotHeader {
|
|
|
121
134
|
* there is no section at all. A pre-D45 blob has no discriminant and reads as `'rapier3d'`,
|
|
122
135
|
* which is what it is.
|
|
123
136
|
*/
|
|
124
|
-
readonly physicsEngine: 'rapier3d' | 'matter2d' | undefined;
|
|
137
|
+
readonly physicsEngine: 'rapier3d' | 'matter2d' | 'rapier2d' | undefined;
|
|
125
138
|
/**
|
|
126
139
|
* The room's authoritative state, in exactly the shape `inspectState` and the recorded
|
|
127
140
|
* timeline use: entity collections as `{ id: { owner, value } }`, singletons as their value.
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
fromMigrationState,
|
|
3
3
|
migrateSnapshot,
|
|
4
4
|
toMigrationState
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SBDZ45JG.js";
|
|
6
6
|
import {
|
|
7
7
|
CRASH_AFTER_THROWS,
|
|
8
8
|
DEFAULT_TIMELINE_MAX_RECORDS,
|
|
@@ -22,27 +22,32 @@ import {
|
|
|
22
22
|
decodeMatterBodies,
|
|
23
23
|
decodeMatterSectionEnvelope,
|
|
24
24
|
decodePhysicsSection,
|
|
25
|
+
decodeRapier2dSectionEnvelope,
|
|
25
26
|
decodeSave,
|
|
26
27
|
encodeMatterBodies,
|
|
27
28
|
encodeMatterSectionEnvelope,
|
|
28
29
|
encodePhysicsSection,
|
|
30
|
+
encodeRapier2dSectionEnvelope,
|
|
29
31
|
encodeViewDelta,
|
|
30
32
|
encodeViewSnapshot,
|
|
31
33
|
initMatter,
|
|
32
34
|
initPhysics,
|
|
35
|
+
initRapier2d,
|
|
33
36
|
inspectState,
|
|
34
37
|
isVisible,
|
|
35
38
|
loadedMatter,
|
|
36
39
|
loadedPhysics,
|
|
40
|
+
loadedRapier2d,
|
|
37
41
|
parseHibernationBlob,
|
|
38
42
|
physicsSectionEngine,
|
|
39
43
|
resetMatterForTests,
|
|
40
44
|
resetPhysicsForTests,
|
|
45
|
+
resetRapier2dForTests,
|
|
41
46
|
viewKeyFor,
|
|
42
47
|
visibleNames,
|
|
43
48
|
visibleTo,
|
|
44
49
|
writeHibernationBlob
|
|
45
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-ADZZEVJU.js";
|
|
46
51
|
export {
|
|
47
52
|
CRASH_AFTER_THROWS,
|
|
48
53
|
DEFAULT_TIMELINE_MAX_RECORDS,
|
|
@@ -62,24 +67,29 @@ export {
|
|
|
62
67
|
decodeMatterBodies,
|
|
63
68
|
decodeMatterSectionEnvelope,
|
|
64
69
|
decodePhysicsSection,
|
|
70
|
+
decodeRapier2dSectionEnvelope,
|
|
65
71
|
decodeSave,
|
|
66
72
|
encodeMatterBodies,
|
|
67
73
|
encodeMatterSectionEnvelope,
|
|
68
74
|
encodePhysicsSection,
|
|
75
|
+
encodeRapier2dSectionEnvelope,
|
|
69
76
|
encodeViewDelta,
|
|
70
77
|
encodeViewSnapshot,
|
|
71
78
|
fromMigrationState,
|
|
72
79
|
initMatter,
|
|
73
80
|
initPhysics,
|
|
81
|
+
initRapier2d,
|
|
74
82
|
inspectState,
|
|
75
83
|
isVisible,
|
|
76
84
|
loadedMatter,
|
|
77
85
|
loadedPhysics,
|
|
86
|
+
loadedRapier2d,
|
|
78
87
|
migrateSnapshot,
|
|
79
88
|
parseHibernationBlob,
|
|
80
89
|
physicsSectionEngine,
|
|
81
90
|
resetMatterForTests,
|
|
82
91
|
resetPhysicsForTests,
|
|
92
|
+
resetRapier2dForTests,
|
|
83
93
|
toMigrationState,
|
|
84
94
|
viewKeyFor,
|
|
85
95
|
visibleNames,
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { AttributeOptions, ProfileSnapshot } from '@irtio/protocol';
|
|
2
2
|
import { DirtySet, AnySchema, PlainState, Tracked, State } from '@irtio/schema';
|
|
3
|
-
import { RoomDefinition, RoomMode, Room, RewindView, Ctx, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
4
|
-
import { i as RoomHost, k as RoomStats, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, g as RoomEventKind, n as TimelineRecorderOptions, T as TimelineDump, j as RoomInspection, J as JoinOptions, d as JoinResult, c as HostCallResult } from './contract-
|
|
3
|
+
import { RoomDefinition, RoomMode, Room, RewindView, Ctx, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
4
|
+
import { i as RoomHost, k as RoomStats, L as LogLevel, R as RoomCoreApi, e as RoomCoreOptions, g as RoomEventKind, n as TimelineRecorderOptions, T as TimelineDump, j as RoomInspection, J as JoinOptions, d as JoinResult, c as HostCallResult } from './contract-C5aqs49-.js';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* `room.random()` — mulberry32 over a u32 seed. Deterministic, tiny, and serializable: the
|
|
8
|
-
* generator's whole state is one u32, so `serialize()`/`restore()` round-trip it exactly.
|
|
9
|
-
*/
|
|
10
6
|
declare class Mulberry32 {
|
|
11
7
|
/** Current internal state (u32). Survives hibernation. */
|
|
12
8
|
state: number;
|
|
@@ -93,8 +89,8 @@ interface QueuedFrame {
|
|
|
93
89
|
}
|
|
94
90
|
/** What the core modules may do with the physics world (`core/physics.ts` implements it). */
|
|
95
91
|
interface PhysicsApi {
|
|
96
|
-
/** D45: which of the
|
|
97
|
-
readonly engineKind: 'rapier3d' | 'matter2d';
|
|
92
|
+
/** D45: which of the blessed engines this room is running. */
|
|
93
|
+
readonly engineKind: 'rapier3d' | 'matter2d' | 'rapier2d';
|
|
98
94
|
readonly timestep: number;
|
|
99
95
|
/** Creates bodies for new instances, destroys bodies whose instance is gone. */
|
|
100
96
|
reconcile(): void;
|
|
@@ -110,9 +106,9 @@ interface PhysicsApi {
|
|
|
110
106
|
* `bodyFor`, and it never hands out the map itself.
|
|
111
107
|
*/
|
|
112
108
|
eachTrackedBody(fn: (collection: string, id: string, body: unknown) => void): void;
|
|
113
|
-
/** rapier3d
|
|
109
|
+
/** rapier3d and rapier2d: the engine namespace. `engineKind` says which module it is. */
|
|
114
110
|
readonly rapier: unknown;
|
|
115
|
-
/** rapier3d
|
|
111
|
+
/** rapier3d and rapier2d: the live `World`. */
|
|
116
112
|
readonly world: unknown;
|
|
117
113
|
/** matter2d only: the `matter-js` namespace. */
|
|
118
114
|
readonly matter: unknown;
|
|
@@ -139,6 +135,8 @@ interface RoomInternals {
|
|
|
139
135
|
readonly ext: AnySchema;
|
|
140
136
|
readonly host: RoomHost;
|
|
141
137
|
readonly roomId: string;
|
|
138
|
+
/** The resolved RNG seed: explicit option, else derived from the room id; restored on wake. */
|
|
139
|
+
readonly seed: number;
|
|
142
140
|
readonly mode: RoomMode;
|
|
143
141
|
/** The plain state the tracked proxies wrap (what the codec reads). */
|
|
144
142
|
readonly plain: PlainState;
|
|
@@ -346,6 +344,15 @@ declare class MatterRuntime {
|
|
|
346
344
|
private readonly restore;
|
|
347
345
|
private readonly stepMs;
|
|
348
346
|
private readonly sleepSynced;
|
|
347
|
+
/**
|
|
348
|
+
* The plain record object each body was built from. See `PhysicsRuntime.sourceRecords`: `add()`
|
|
349
|
+
* installs a new record object, so an id removed and re-added between two reconciles (a pooled
|
|
350
|
+
* projectile) is identifiable by identity, and is the one case where a live body has to be
|
|
351
|
+
* rebuilt so the new row's spawn pose and velocity are honoured.
|
|
352
|
+
*/
|
|
353
|
+
private readonly sourceRecords;
|
|
354
|
+
/** `collection.field` pairs already warned about a channel a plane cannot hold. */
|
|
355
|
+
private readonly warnedChannels;
|
|
349
356
|
constructor(core: RoomInternals, matter: MatterModule, options: MatterRuntimeOptions);
|
|
350
357
|
get timestep(): number;
|
|
351
358
|
runSetup(room: Room): void;
|
|
@@ -431,9 +438,27 @@ declare function encodePhysicsSection(section: PhysicsSection): Uint8Array;
|
|
|
431
438
|
* pre-D45 blob is therefore byte-identical and reads as Rapier without a version bump, which is
|
|
432
439
|
* what the recommendation in the plan asked for and why format 3 was not needed.
|
|
433
440
|
*/
|
|
434
|
-
type PhysicsEngineTag = 'rapier3d' | 'matter2d';
|
|
435
|
-
/**
|
|
441
|
+
type PhysicsEngineTag = 'rapier3d' | 'matter2d' | 'rapier2d';
|
|
442
|
+
/**
|
|
443
|
+
* Reads the engine out of an encoded section without decoding the rest of it.
|
|
444
|
+
*
|
|
445
|
+
* The `else` is deliberately bare rather than a third tag comparison: a pre-D45 blob has no
|
|
446
|
+
* discriminant at all, and every byte sequence that is not the zero-length-world envelope is a
|
|
447
|
+
* rapier3d world snapshot. Adding rapier2d must not change that, or every blob written before
|
|
448
|
+
* this engine existed stops decoding.
|
|
449
|
+
*/
|
|
436
450
|
declare function physicsSectionEngine(bytes: Uint8Array): PhysicsEngineTag;
|
|
451
|
+
/**
|
|
452
|
+
* Wraps a rapier2d section in the same discriminated envelope, under its own tag.
|
|
453
|
+
*
|
|
454
|
+
* The payload is an ordinary {@link encodePhysicsSection} — rapier2d has a real
|
|
455
|
+
* `world.takeSnapshot()`, so unlike matter2d it has a world to write, and the only thing the
|
|
456
|
+
* envelope buys is telling the two Rapiers apart. Restoring a 2D snapshot into a 3D world (or the
|
|
457
|
+
* reverse) does not fail cleanly, which is exactly why the discriminant is not optional here.
|
|
458
|
+
*/
|
|
459
|
+
declare function encodeRapier2dSectionEnvelope(payload: Uint8Array): Uint8Array;
|
|
460
|
+
/** The payload inside a rapier2d envelope. Throws if the section was written by another engine. */
|
|
461
|
+
declare function decodeRapier2dSectionEnvelope(bytes: Uint8Array): Uint8Array;
|
|
437
462
|
/** Wraps a matter2d body-state payload in the discriminated envelope. */
|
|
438
463
|
declare function encodeMatterSectionEnvelope(payload: Uint8Array): Uint8Array;
|
|
439
464
|
/** The payload inside a matter2d envelope. Throws if the section is a Rapier one. */
|
|
@@ -470,6 +495,19 @@ declare class PhysicsRuntime {
|
|
|
470
495
|
* world rebuilt from schema state would wake it with a kick). Found by the drift check.
|
|
471
496
|
*/
|
|
472
497
|
private readonly sleepSynced;
|
|
498
|
+
/**
|
|
499
|
+
* The plain record object each body was built from, by body key. `add()` always installs a
|
|
500
|
+
* **new** record object (`normalizeRecord` builds one), so an id that is removed and re-added
|
|
501
|
+
* between two reconciles — the pooled-projectile shape — is identifiable by identity alone,
|
|
502
|
+
* which is the only way to see it from here: both edits happened before this module looked.
|
|
503
|
+
* Without it the old body kept its pose and velocity and the new row's spawn values were lost.
|
|
504
|
+
*
|
|
505
|
+
* Unknown means adopt, never rebuild: a world restored from a hibernation blob has bodies with
|
|
506
|
+
* no recorded source, and rebuilding those would throw away exactly what the blob preserved.
|
|
507
|
+
*/
|
|
508
|
+
private readonly sourceRecords;
|
|
509
|
+
/** `collection.field` pairs already warned about a channel value the engine would not take. */
|
|
510
|
+
private readonly warnedChannels;
|
|
473
511
|
constructor(core: RoomInternals, rapier: RapierModule, options: PhysicsRuntimeOptions);
|
|
474
512
|
get timestep(): number;
|
|
475
513
|
/** Runs the room's `setup` — static geometry — on a world that was built rather than restored. */
|
|
@@ -478,6 +516,17 @@ declare class PhysicsRuntime {
|
|
|
478
516
|
/** The body behind an instance, created on demand so a handler's own `add` is usable at once. */
|
|
479
517
|
bodyFor(collection: string, id: string): RapierRigidBody | undefined;
|
|
480
518
|
private create;
|
|
519
|
+
/**
|
|
520
|
+
* A spawn value the engine will not take, said once per field.
|
|
521
|
+
*
|
|
522
|
+
* Rapier accepts `setLinvel` on an axis the body has disabled and then keeps zero, and it
|
|
523
|
+
* accepts one on a fixed body and then ignores it — both read back as "the field was applied"
|
|
524
|
+
* and behave as if it never was. The desc says which, before the body exists, so the check is
|
|
525
|
+
* on the desc: the usual case is the 2D-in-3D recipe (`enabledTranslations(true, true, false)`)
|
|
526
|
+
* meeting a row that spawns with a `vz`. Named by field, because the field is what the author
|
|
527
|
+
* wrote and what they will grep for.
|
|
528
|
+
*/
|
|
529
|
+
private warnLockedChannels;
|
|
481
530
|
/**
|
|
482
531
|
* D72: every tracked body, in the order `sync()` walks them (collection order, then instance
|
|
483
532
|
* order). Read-only: it hands out the live bodies and nothing else, and the map stays private.
|
|
@@ -501,6 +550,128 @@ declare class PhysicsRuntime {
|
|
|
501
550
|
serialize(): PhysicsSection;
|
|
502
551
|
}
|
|
503
552
|
|
|
553
|
+
/**
|
|
554
|
+
* The rapier2d world inside the room: Rapier, held in a plane.
|
|
555
|
+
*
|
|
556
|
+
* It is modelled on `core/physics.ts` rather than on `core/matter.ts`, and the choice is the whole
|
|
557
|
+
* point of the engine existing. Everything that made the Rapier path good is kept:
|
|
558
|
+
*
|
|
559
|
+
* - **A real engine snapshot.** `world.takeSnapshot()` / `World.restoreSnapshot()` exist in the 2D
|
|
560
|
+
* build too, so a woken room is byte-for-byte the world that went to sleep, contacts and sleep
|
|
561
|
+
* timers included, and `setup` runs only on a genuine rebuild. matter2d has no equivalent and
|
|
562
|
+
* rebuilds every wake; that difference is why a settled pile can jolt there and does not here.
|
|
563
|
+
* - **The world applies gravity.** It goes into the `World` constructor and dynamic bodies fall
|
|
564
|
+
* without the room lifting a finger. A matter2d room's hooks apply gravity themselves; a
|
|
565
|
+
* rapier2d room that copied that would fall twice as fast.
|
|
566
|
+
* - **Velocities are per second.** `linvel()` is metres per second, not matter's per-step
|
|
567
|
+
* displacement, so nothing here rescales anything.
|
|
568
|
+
*
|
|
569
|
+
* What it takes from the 2D side is only the plane: `{ x, y }` gravity, and the channel mapping in
|
|
570
|
+
* `@irtio/schema` (`channelOf2d` / `applyChannel2d` / `angleFrom2d`), which is the single place the
|
|
571
|
+
* six planar quantities are written onto the thirteen 3D wire channels. A second copy of that
|
|
572
|
+
* mapping here would be a way for the two sides to disagree by a sign, so there is not one.
|
|
573
|
+
*
|
|
574
|
+
* There is no planar-lock warning in this file. Bug 6's friction trap needs a third axis to lock,
|
|
575
|
+
* and this engine does not have one.
|
|
576
|
+
*
|
|
577
|
+
* ## Async init
|
|
578
|
+
*
|
|
579
|
+
* Rapier's WASM needs `await RAPIER.init()`, and room handlers are synchronous, so the engine is
|
|
580
|
+
* initialized before the room is constructed — by the worker host at bundle-load time, and by
|
|
581
|
+
* `initRapier2d()` in the test harness. This is `initPhysics()`'s twin, against the 2D module.
|
|
582
|
+
*/
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Loads and initializes `@dimforge/rapier2d-compat` once per process. Idempotent and safe to call
|
|
586
|
+
* concurrently. It is a *separate* WASM module from the 3D build — the two do not share an
|
|
587
|
+
* instance and loading one does not load the other — so a rapier2d room pays the 2D module's
|
|
588
|
+
* footprint and nothing else.
|
|
589
|
+
*/
|
|
590
|
+
declare function initRapier2d(): Promise<Rapier2dModule>;
|
|
591
|
+
/** The initialized engine, or `undefined` when `initRapier2d()` has not resolved yet. */
|
|
592
|
+
declare function loadedRapier2d(): Rapier2dModule | undefined;
|
|
593
|
+
/** Test seam: forget the loaded engine (never used in production paths). */
|
|
594
|
+
declare function resetRapier2dForTests(): void;
|
|
595
|
+
interface Rapier2dRuntimeOptions {
|
|
596
|
+
/** From a v2 hibernation blob's rapier2d section. Absent → a fresh world, and `setup` runs. */
|
|
597
|
+
readonly restore?: PhysicsSection;
|
|
598
|
+
/** Seconds per step when the config does not name one (the tick interval). */
|
|
599
|
+
readonly defaultTimestep: number;
|
|
600
|
+
}
|
|
601
|
+
declare class Rapier2dRuntime {
|
|
602
|
+
readonly engineKind: "rapier2d";
|
|
603
|
+
/** matter2d only; present so every runtime satisfies one internal shape. */
|
|
604
|
+
readonly matter: undefined;
|
|
605
|
+
readonly matterEngine: undefined;
|
|
606
|
+
readonly rapier: Rapier2dModule;
|
|
607
|
+
readonly world: Rapier2dWorld;
|
|
608
|
+
/** `true` when the world was built from scratch and `setup` has to run. */
|
|
609
|
+
readonly rebuilt: boolean;
|
|
610
|
+
/** Rapier restores a whole world, contacts included, so `setup` runs only on a rebuild. */
|
|
611
|
+
get needsSetup(): boolean;
|
|
612
|
+
private readonly core;
|
|
613
|
+
private readonly config;
|
|
614
|
+
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
615
|
+
private readonly collections;
|
|
616
|
+
private readonly bodies;
|
|
617
|
+
/** See `PhysicsRuntime.sleepSynced`: the one extra sync a body owes on the tick it sleeps. */
|
|
618
|
+
private readonly sleepSynced;
|
|
619
|
+
/**
|
|
620
|
+
* See `PhysicsRuntime.sourceRecords`: the plain record object each body was built from. `add()`
|
|
621
|
+
* always installs a new record object, so an id removed and re-added between two reconciles —
|
|
622
|
+
* the pooled-projectile shape — is identifiable by identity alone, and its body is rebuilt so
|
|
623
|
+
* the new row's spawn pose and velocity are what the world gets. Unknown means adopt, never
|
|
624
|
+
* rebuild: a world restored from a hibernation blob has bodies with no recorded source.
|
|
625
|
+
*/
|
|
626
|
+
private readonly sourceRecords;
|
|
627
|
+
/** `collection.field` pairs already warned about a channel value the engine would not take. */
|
|
628
|
+
private readonly warnedChannels;
|
|
629
|
+
constructor(core: RoomInternals, rapier: Rapier2dModule, options: Rapier2dRuntimeOptions);
|
|
630
|
+
get timestep(): number;
|
|
631
|
+
/** Runs the room's `setup` — static geometry — on a world that was built rather than restored. */
|
|
632
|
+
runSetup(room: Room): void;
|
|
633
|
+
free(): void;
|
|
634
|
+
/** The body behind an instance, created on demand so a handler's own `add` is usable at once. */
|
|
635
|
+
bodyFor(collection: string, id: string): Rapier2dRigidBody | undefined;
|
|
636
|
+
private create;
|
|
637
|
+
/**
|
|
638
|
+
* A spawn value the engine will not take, said once per field — the planar half of
|
|
639
|
+
* `PhysicsRuntime.warnLockedChannels`.
|
|
640
|
+
*
|
|
641
|
+
* Rapier accepts `setLinvel` on an axis the body has disabled and then keeps zero, and it
|
|
642
|
+
* accepts one on a fixed body and then ignores it: both read back as "the field was applied"
|
|
643
|
+
* and behave as if it never was. The desc says which, before the body exists, so the check is on
|
|
644
|
+
* the desc. In the plane there are three movable channels — `vx`, `vy` and the one rotation
|
|
645
|
+
* `wz` — and the desc carries exactly those three flags. Named by field, because the field is
|
|
646
|
+
* what the author wrote and what they will grep for.
|
|
647
|
+
*/
|
|
648
|
+
private warnLockedChannels;
|
|
649
|
+
/**
|
|
650
|
+
* D72: every tracked body, in the order `sync()` walks them (collection order, then instance
|
|
651
|
+
* order). The rapier2d third of the same read-only accessor the other two runtimes carry.
|
|
652
|
+
*/
|
|
653
|
+
eachTrackedBody(fn: (collection: string, id: string, body: Rapier2dRigidBody) => void): void;
|
|
654
|
+
private applyRecordToBody;
|
|
655
|
+
/**
|
|
656
|
+
* Creates bodies for new instances and destroys bodies whose instance is gone. Runs once per
|
|
657
|
+
* tick, right before the step, in collection order then instance order.
|
|
658
|
+
*/
|
|
659
|
+
reconcile(): void;
|
|
660
|
+
step(): void;
|
|
661
|
+
/**
|
|
662
|
+
* Body → schema, through the tracked proxies, so movement leaves the room as an ordinary delta.
|
|
663
|
+
* Values are `Math.fround`ed for f32 fields, so room code reads exactly what the wire carries.
|
|
664
|
+
*/
|
|
665
|
+
sync(): void;
|
|
666
|
+
/**
|
|
667
|
+
* The same `PhysicsSection` the 3D runtime writes — a real engine snapshot plus the
|
|
668
|
+
* entity↔handle map, whose handles must ride as f64 for the reason spelled out on
|
|
669
|
+
* {@link PhysicsSection}. What tells the two apart on the way back in is the envelope
|
|
670
|
+
* `core/physics.ts` wraps this in, not anything in here.
|
|
671
|
+
*/
|
|
672
|
+
serialize(): PhysicsSection;
|
|
673
|
+
}
|
|
674
|
+
|
|
504
675
|
/**
|
|
505
676
|
* `RoomCore` — the host-agnostic room runtime. It owns the extended schema, the
|
|
506
677
|
* tracked authority state, presence, the loop, and the frame dispatch; everything outside comes
|
|
@@ -521,7 +692,7 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
521
692
|
readonly clients: Map<string, ClientEntry>;
|
|
522
693
|
readonly loop: Loop;
|
|
523
694
|
/** D22: the Rapier world, or `undefined` in a room whose config declares no physics. */
|
|
524
|
-
readonly physics: PhysicsRuntime | MatterRuntime | undefined;
|
|
695
|
+
readonly physics: PhysicsRuntime | MatterRuntime | Rapier2dRuntime | undefined;
|
|
525
696
|
readonly stats: RoomStats;
|
|
526
697
|
tick: number;
|
|
527
698
|
stopped: boolean;
|
|
@@ -550,7 +721,7 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
550
721
|
* it again over its next `history` ticks.
|
|
551
722
|
*/
|
|
552
723
|
private readonly rewindState;
|
|
553
|
-
|
|
724
|
+
readonly seed: number;
|
|
554
725
|
private readonly api;
|
|
555
726
|
private readonly internals;
|
|
556
727
|
private started;
|
|
@@ -596,6 +767,12 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
596
767
|
* worth logging.
|
|
597
768
|
*/
|
|
598
769
|
private buildMatter;
|
|
770
|
+
/**
|
|
771
|
+
* The rapier2d half. It follows the *rapier3d* shape rather than matter2d's, because the 2D
|
|
772
|
+
* build has a real `takeSnapshot()`: a restored world comes back whole and `setup` does not run
|
|
773
|
+
* again. The only thing that differs from `buildPhysics` is which engine and which envelope.
|
|
774
|
+
*/
|
|
775
|
+
private buildRapier2d;
|
|
599
776
|
/** Convenience for hosts: `RoomCore.restore(def, bytes, host, opts)`. */
|
|
600
777
|
static restore<S2 extends AnySchema>(definition: RoomDefinition<S2>, bytes: Uint8Array, host: RoomHost, options: Omit<RoomCoreOptions, 'restoreFrom'>): RoomCore<S2>;
|
|
601
778
|
get schema(): S;
|
|
@@ -707,4 +884,4 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
707
884
|
flush(): void;
|
|
708
885
|
}
|
|
709
886
|
|
|
710
|
-
export { CRASH_AFTER_THROWS as C, MAX_CATCHUP as M, type PhysicsEngineTag as P, RoomCore as R, type MatterBodyRecord as a, type MatterSection as b, Mulberry32 as c, type PhysicsSection as d, decodeMatterBodies as e, decodeMatterSectionEnvelope as f, decodePhysicsSection as g,
|
|
887
|
+
export { CRASH_AFTER_THROWS as C, MAX_CATCHUP as M, type PhysicsEngineTag as P, RoomCore as R, type MatterBodyRecord as a, type MatterSection as b, Mulberry32 as c, type PhysicsSection as d, decodeMatterBodies as e, decodeMatterSectionEnvelope as f, decodePhysicsSection as g, decodeRapier2dSectionEnvelope as h, encodeMatterBodies as i, encodeMatterSectionEnvelope as j, encodePhysicsSection as k, encodeRapier2dSectionEnvelope as l, initMatter as m, initPhysics as n, initRapier2d as o, loadedMatter as p, loadedPhysics as q, loadedRapier2d as r, physicsSectionEngine as s, resetMatterForTests as t, resetPhysicsForTests as u, resetRapier2dForTests as v };
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as RoomCore } from '../room-
|
|
2
|
-
export {
|
|
1
|
+
import { R as RoomCore } from '../room-C4RZJ2KO.js';
|
|
2
|
+
export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-C4RZJ2KO.js';
|
|
3
3
|
import { ErrorCodeName, FrameType } from '@irtio/protocol';
|
|
4
4
|
import { NpcConfig, LeaveReason, Room, RoomDefinition } from '@irtio/server';
|
|
5
|
-
import { i as RoomHost, L as LogLevel, R as RoomCoreApi, b as HostCall, c as HostCallResult, k as RoomStats } from '../contract-
|
|
5
|
+
import { i as RoomHost, L as LogLevel, R as RoomCoreApi, b as HostCall, c as HostCallResult, k as RoomStats } from '../contract-C5aqs49-.js';
|
|
6
6
|
import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -166,6 +166,17 @@ declare class HarnessHost implements RoomHost {
|
|
|
166
166
|
/** D63-e: the harness has no room list to report on, so it records the value and stops. */
|
|
167
167
|
backfillOpen: boolean | undefined;
|
|
168
168
|
setBackfill(open: boolean): void;
|
|
169
|
+
/** D75: recorded, like the backfill flag, and for the same reason — no room list to report on. */
|
|
170
|
+
lobby: {
|
|
171
|
+
public?: boolean;
|
|
172
|
+
queue?: string;
|
|
173
|
+
started?: boolean;
|
|
174
|
+
};
|
|
175
|
+
setLobby(update: {
|
|
176
|
+
readonly public?: boolean;
|
|
177
|
+
readonly queue?: string;
|
|
178
|
+
readonly started?: boolean;
|
|
179
|
+
}): void;
|
|
169
180
|
busPublish(channel: string, payload: string): void;
|
|
170
181
|
busSubscribe(channel: string, subscribed: boolean): void;
|
|
171
182
|
setAlarm(name: string, atMs: number | undefined): void;
|
|
@@ -320,7 +331,7 @@ interface FakeClient<S extends AnySchema = AnySchema> {
|
|
|
320
331
|
*/
|
|
321
332
|
|
|
322
333
|
interface HarnessOptions {
|
|
323
|
-
/** `room.random()` seed;
|
|
334
|
+
/** `room.random()` seed; defaults to a hash of `roomId`, exactly as `RoomCore` does. */
|
|
324
335
|
readonly seed?: number;
|
|
325
336
|
/** Base URL for `room.link`. */
|
|
326
337
|
readonly publicUrl?: string;
|
package/dist/test/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
createVisibilityPolicy,
|
|
4
4
|
initMatter,
|
|
5
5
|
initPhysics,
|
|
6
|
+
initRapier2d,
|
|
6
7
|
visibleNames
|
|
7
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-ADZZEVJU.js";
|
|
8
9
|
|
|
9
10
|
// src/test/clock.ts
|
|
10
11
|
var FakeClock = class {
|
|
@@ -235,6 +236,12 @@ var HarnessHost = class {
|
|
|
235
236
|
setBackfill(open) {
|
|
236
237
|
this.backfillOpen = open;
|
|
237
238
|
}
|
|
239
|
+
// ---- M6 lane H: lobby front door ----
|
|
240
|
+
/** D75: recorded, like the backfill flag, and for the same reason — no room list to report on. */
|
|
241
|
+
lobby = {};
|
|
242
|
+
setLobby(update) {
|
|
243
|
+
this.lobby = { ...this.lobby, ...update };
|
|
244
|
+
}
|
|
238
245
|
busPublish(channel, payload) {
|
|
239
246
|
this.busPublishes.push({ channel, payload });
|
|
240
247
|
}
|
|
@@ -670,7 +677,7 @@ var Harness = class {
|
|
|
670
677
|
this.host.onSend = (clientId, frame) => this.deliver(clientId, frame);
|
|
671
678
|
this.coreRef = new RoomCore(definition, this.host, {
|
|
672
679
|
roomId: options.roomId ?? "test-room",
|
|
673
|
-
seed: options.seed
|
|
680
|
+
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
674
681
|
...options.publicUrl !== void 0 ? { publicUrl: options.publicUrl } : {},
|
|
675
682
|
...options.restoreFrom !== void 0 ? { restoreFrom: options.restoreFrom } : {}
|
|
676
683
|
});
|
|
@@ -956,7 +963,7 @@ var Harness = class {
|
|
|
956
963
|
this.nextClientId = 1;
|
|
957
964
|
this.coreRef = RoomCore.restore(this.definition, bytes, this.host, {
|
|
958
965
|
roomId: this.options.roomId ?? "test-room",
|
|
959
|
-
seed: this.options.seed
|
|
966
|
+
...this.options.seed !== void 0 ? { seed: this.options.seed } : {},
|
|
960
967
|
...this.options.publicUrl !== void 0 ? { publicUrl: this.options.publicUrl } : {}
|
|
961
968
|
});
|
|
962
969
|
this.coreRef.start();
|
|
@@ -974,5 +981,6 @@ export {
|
|
|
974
981
|
createRoomHarness,
|
|
975
982
|
frameTypeName,
|
|
976
983
|
initMatter,
|
|
977
|
-
initPhysics
|
|
984
|
+
initPhysics,
|
|
985
|
+
initRapier2d
|
|
978
986
|
};
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LogLevel, T as TimelineDump, k as RoomStats, b as HostCall, c as HostCallResult, R as RoomCoreApi, i as RoomHost } from '../contract-
|
|
1
|
+
import { L as LogLevel, T as TimelineDump, k as RoomStats, b as HostCall, c as HostCallResult, R as RoomCoreApi, i as RoomHost } from '../contract-C5aqs49-.js';
|
|
2
2
|
import { ErrorCodeName, ProfileSnapshot } from '@irtio/protocol';
|
|
3
3
|
import { NpcConfig, LeaveReason } from '@irtio/server';
|
|
4
4
|
import '@irtio/schema';
|
|
@@ -151,6 +151,9 @@ type FromWorker = {
|
|
|
151
151
|
/** D63-e: the room type's declared backfill eligibility. The ceiling on
|
|
152
152
|
* `room.backfill.set`, and `false` for every room that declares nothing. */
|
|
153
153
|
backfill?: boolean;
|
|
154
|
+
/** D75: the room type declared a lobby. The ceiling on `setLobby`, and the supervisor
|
|
155
|
+
* re-checks it for exactly the reason it re-checks `backfill`. */
|
|
156
|
+
lobby?: boolean;
|
|
154
157
|
/** D47: the physics engine this room runs, when it declares one. Reported so room-hours
|
|
155
158
|
* rows carry the engine as a dimension; the supervisor never acts on it. */
|
|
156
159
|
engine?: string;
|
|
@@ -289,6 +292,11 @@ type FromWorker = {
|
|
|
289
292
|
| {
|
|
290
293
|
t: 'setBackfill';
|
|
291
294
|
open: boolean;
|
|
295
|
+
} | {
|
|
296
|
+
t: 'setLobby';
|
|
297
|
+
public?: boolean;
|
|
298
|
+
queue?: string;
|
|
299
|
+
started?: boolean;
|
|
292
300
|
}
|
|
293
301
|
/**
|
|
294
302
|
* D59: `room.bus.publish(channel, payload)`. Fire-and-forget by design — the room gets no
|
package/dist/worker/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
migrateSnapshot
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-SBDZ45JG.js";
|
|
4
4
|
import {
|
|
5
5
|
RoomCore,
|
|
6
6
|
RoomFullError,
|
|
7
7
|
initMatter,
|
|
8
8
|
initPhysics,
|
|
9
|
+
initRapier2d,
|
|
9
10
|
onFirstRapierStep,
|
|
10
11
|
rapierHasStepped
|
|
11
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-ADZZEVJU.js";
|
|
12
13
|
|
|
13
14
|
// src/worker/index.ts
|
|
14
15
|
import { getHeapStatistics } from "v8";
|
|
@@ -79,6 +80,10 @@ function createWorkerHost(post) {
|
|
|
79
80
|
setBackfill(open) {
|
|
80
81
|
post({ t: "setBackfill", open });
|
|
81
82
|
},
|
|
83
|
+
// ---- M6 lane H: lobby front door ----
|
|
84
|
+
setLobby(update) {
|
|
85
|
+
post({ t: "setLobby", ...update });
|
|
86
|
+
},
|
|
82
87
|
setAlarm(name, atMs) {
|
|
83
88
|
if (atMs === void 0) {
|
|
84
89
|
post({ t: "setAlarm", name });
|
|
@@ -153,7 +158,8 @@ async function handleMessage(state, host, post, msg) {
|
|
|
153
158
|
try {
|
|
154
159
|
if (def.config.physics.engine === "matter2d") await initMatter();
|
|
155
160
|
else {
|
|
156
|
-
await
|
|
161
|
+
if (def.config.physics.engine === "rapier2d") await initRapier2d();
|
|
162
|
+
else await initPhysics();
|
|
157
163
|
onFirstRapierStep(() => {
|
|
158
164
|
const core = state.core;
|
|
159
165
|
if (core) post(statsPayload(core.stats, void 0, core.profile()));
|
|
@@ -211,6 +217,11 @@ async function handleMessage(state, host, post, msg) {
|
|
|
211
217
|
// `engine` is: `ready.config` replaces the supervisor's copy, so a flag threaded
|
|
212
218
|
// only through `loadBundle` would be dropped the moment a worker started.
|
|
213
219
|
backfill: c.backfill === true,
|
|
220
|
+
// ---- M6 lane H: lobby front door ----
|
|
221
|
+
// D75: the declared ceiling on `setLobby`, carried for exactly `backfill`'s reason.
|
|
222
|
+
// The supervisor re-checks it, because a check only the tenant's own worker makes is
|
|
223
|
+
// advice rather than a boundary.
|
|
224
|
+
lobby: c.lobby !== void 0,
|
|
214
225
|
// D47: `ready.config` REPLACES the supervisor's copy of the bundle config, so an
|
|
215
226
|
// engine threaded only through `loadBundle` would be dropped here the moment a
|
|
216
227
|
// worker started. That is exactly how the first known-shape run read `engine: none`
|