@irtio/runtime 0.5.1 → 0.6.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-5ZDQAAFJ.js → chunk-K42HA75G.js} +963 -114
- package/dist/{chunk-HFOMXKSO.js → chunk-VMCE3LRO.js} +1 -1
- package/dist/contract-BjMsoJIV.d.ts +415 -0
- package/dist/index.d.ts +41 -3
- package/dist/index.js +26 -2
- package/dist/{room-9ZQoy9yi.d.ts → room-CfnEjlcg.d.ts} +214 -7
- package/dist/test/index.d.ts +51 -4
- package/dist/test/index.js +70 -1
- package/dist/worker/index.d.ts +156 -3
- package/dist/worker/index.js +96 -10
- package/package.json +8 -4
- package/dist/contract-B8QSO0MH.d.ts +0 -204
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { AttributeOptions, ProfileSnapshot } from '@irtio/protocol';
|
|
1
2
|
import { DirtySet, AnySchema, PlainState, Tracked, State } from '@irtio/schema';
|
|
2
|
-
import { RoomDefinition, RoomMode, Room, Ctx, RapierModule, RapierWorld, RapierRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
3
|
-
import {
|
|
3
|
+
import { RoomDefinition, RoomMode, Room, 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-BjMsoJIV.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* `room.random()` — mulberry32 over a u32 seed. Deterministic, tiny, and serializable: the
|
|
@@ -27,6 +28,13 @@ type AcceptedWrites = Map<string, Map<string, unknown>>;
|
|
|
27
28
|
/** One joined client. Presence is the wire truth; this is the runtime's bookkeeping. */
|
|
28
29
|
interface ClientEntry {
|
|
29
30
|
readonly clientId: string;
|
|
31
|
+
/**
|
|
32
|
+
* D44: this session is a scripted NPC, spawned by room code rather than dialled from outside.
|
|
33
|
+
* It changes exactly two things: the idle clock does not count it (a room full of NPCs and no
|
|
34
|
+
* players hibernates like an empty one), and metrics report it separately. Everything else —
|
|
35
|
+
* `maxClients`, validation, ownership, presence — treats it as the client session it is.
|
|
36
|
+
*/
|
|
37
|
+
readonly npc: boolean;
|
|
30
38
|
/**
|
|
31
39
|
* D25: the identity `ctx.playerId` reports. Set once at the first join and kept across
|
|
32
40
|
* reconnects, exactly like the client id it defaults to.
|
|
@@ -85,6 +93,8 @@ interface QueuedFrame {
|
|
|
85
93
|
}
|
|
86
94
|
/** What the core modules may do with the physics world (`core/physics.ts` implements it). */
|
|
87
95
|
interface PhysicsApi {
|
|
96
|
+
/** D45: which of the two blessed engines this room is running. */
|
|
97
|
+
readonly engineKind: 'rapier3d' | 'matter2d';
|
|
88
98
|
readonly timestep: number;
|
|
89
99
|
/** Creates bodies for new instances, destroys bodies whose instance is gone. */
|
|
90
100
|
reconcile(): void;
|
|
@@ -92,8 +102,14 @@ interface PhysicsApi {
|
|
|
92
102
|
/** Body → schema, through the tracked proxies. */
|
|
93
103
|
sync(): void;
|
|
94
104
|
bodyFor(collection: string, id: string): unknown;
|
|
105
|
+
/** rapier3d only. */
|
|
95
106
|
readonly rapier: unknown;
|
|
107
|
+
/** rapier3d only. */
|
|
96
108
|
readonly world: unknown;
|
|
109
|
+
/** matter2d only: the `matter-js` namespace. */
|
|
110
|
+
readonly matter: unknown;
|
|
111
|
+
/** matter2d only: the live `Matter.Engine`. */
|
|
112
|
+
readonly matterEngine: unknown;
|
|
97
113
|
}
|
|
98
114
|
interface LoopApi {
|
|
99
115
|
start(): void;
|
|
@@ -133,7 +149,9 @@ interface RoomInternals {
|
|
|
133
149
|
stopped: boolean;
|
|
134
150
|
/** Runs a room handler; a throw is logged and counted, never rethrown. */
|
|
135
151
|
guard<T>(name: string, fn: () => T): T | undefined;
|
|
136
|
-
|
|
152
|
+
/** D41: record this tick on the authoritative timeline. A no-op while the recorder is unarmed. */
|
|
153
|
+
captureTimeline(): void;
|
|
154
|
+
recordEvent(kind: 'join' | 'leave' | 'write' | 'write-rejected' | 'correct' | 'call' | 'reply' | 'msg' | 'alarm' | 'bus' | 'error', clientId?: string, detail?: string): void;
|
|
137
155
|
/** `guard` that also reports whether the handler threw (the tick loop needs this). */
|
|
138
156
|
tryRun<T>(name: string, fn: () => T): GuardResult<T>;
|
|
139
157
|
log(level: LogLevel, ...args: unknown[]): void;
|
|
@@ -144,6 +162,13 @@ interface RoomInternals {
|
|
|
144
162
|
correctionFor(clientId: string): DirtySet | undefined;
|
|
145
163
|
/** Flushes the tracked dirty set: corrections first, then one `DELTA` per distinct view. */
|
|
146
164
|
flush(): void;
|
|
165
|
+
/**
|
|
166
|
+
* Schedules an event-mode flush after the current microtask chain settles; a no-op in tick
|
|
167
|
+
* mode (the next tick flushes anyway) and while one is already pending. This is the
|
|
168
|
+
* "own event, then flush" step the host-call seam runs after `completeHostCall`, exposed so a
|
|
169
|
+
* promise the room API rejects *locally* (bug #48) settles in the same scheduling class.
|
|
170
|
+
*/
|
|
171
|
+
scheduleContinuationFlush(): void;
|
|
147
172
|
/** Invalidates the cached `room.clients` array. */
|
|
148
173
|
invalidateClients(): void;
|
|
149
174
|
}
|
|
@@ -225,6 +250,101 @@ declare class Loop implements LoopApi {
|
|
|
225
250
|
after(ms: number, fn: () => void): void;
|
|
226
251
|
}
|
|
227
252
|
|
|
253
|
+
/**
|
|
254
|
+
* D45 part 1: the matter.js world inside the room.
|
|
255
|
+
*
|
|
256
|
+
* It sits in exactly the place `core/physics.ts` describes — reconcile, step, sync, between the
|
|
257
|
+
* `tick()` handler and the flush — and writes the same schema channels. What differs is everything
|
|
258
|
+
* underneath, and two differences are load-bearing enough to be worth stating here.
|
|
259
|
+
*
|
|
260
|
+
* ## There is no engine snapshot
|
|
261
|
+
*
|
|
262
|
+
* Rapier has `world.takeSnapshot()` / `restoreSnapshot()`, and a woken Rapier room is byte-for-byte
|
|
263
|
+
* the world that went to sleep, contacts included. matter.js has no equivalent: its world is plain
|
|
264
|
+
* JavaScript objects, and the honest options were to serialize the object graph ourselves or to
|
|
265
|
+
* rebuild the world and reapply per-body state. This module does the second. `setup` therefore runs
|
|
266
|
+
* on **every** matter2d wake, not only on a rebuild, and what is not restored is the solver's
|
|
267
|
+
* transient state: resting contacts, accumulated impulses, and the sleep timers behind them. A
|
|
268
|
+
* settled pile may re-settle with a small jolt. The measurement in `docs/m3-part5-report.md` says
|
|
269
|
+
* how big that jolt is on the fixture that ships with this.
|
|
270
|
+
*
|
|
271
|
+
* ## The units are matter.js's, not translated
|
|
272
|
+
*
|
|
273
|
+
* `gravity` goes into `engine.gravity` verbatim (matter's own default is `{ x: 0, y: 1 }` with
|
|
274
|
+
* `scale: 0.001`, and y is **down**), and `body.velocity` is matter's per-step displacement rather
|
|
275
|
+
* than a per-second velocity. Translating either would mean irtio inventing a unit system on top of
|
|
276
|
+
* an engine it promised to bless rather than abstract, and every matter.js tutorial would then be
|
|
277
|
+
* subtly wrong inside a room. The mapping onto the wire channels is in `@irtio/schema`
|
|
278
|
+
* (`channelOf2d`), and it is the only translation there is.
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Loads `matter-js` once per process. Pure JavaScript, so there is no `init()` to await and no
|
|
283
|
+
* WASM instance to keep unique — but the load is still async (it is an `import()`), and handlers
|
|
284
|
+
* are synchronous, so it happens before the room is constructed exactly as Rapier's does.
|
|
285
|
+
*/
|
|
286
|
+
declare function initMatter(): Promise<MatterModule>;
|
|
287
|
+
declare function loadedMatter(): MatterModule | undefined;
|
|
288
|
+
/** Test seam: forget the loaded engine. */
|
|
289
|
+
declare function resetMatterForTests(): void;
|
|
290
|
+
/** One body's restorable state. Everything a rebuild needs and nothing the world-builder gives. */
|
|
291
|
+
interface MatterBodyRecord {
|
|
292
|
+
readonly collection: string;
|
|
293
|
+
readonly id: string;
|
|
294
|
+
readonly x: number;
|
|
295
|
+
readonly y: number;
|
|
296
|
+
readonly angle: number;
|
|
297
|
+
readonly vx: number;
|
|
298
|
+
readonly vy: number;
|
|
299
|
+
readonly angularVelocity: number;
|
|
300
|
+
readonly sleeping: boolean;
|
|
301
|
+
}
|
|
302
|
+
interface MatterSection {
|
|
303
|
+
readonly bodies: readonly MatterBodyRecord[];
|
|
304
|
+
}
|
|
305
|
+
declare function encodeMatterBodies(section: MatterSection): Uint8Array;
|
|
306
|
+
declare function decodeMatterBodies(bytes: Uint8Array): MatterSection;
|
|
307
|
+
interface MatterRuntimeOptions {
|
|
308
|
+
readonly restore?: MatterSection;
|
|
309
|
+
readonly defaultTimestep: number;
|
|
310
|
+
}
|
|
311
|
+
declare class MatterRuntime {
|
|
312
|
+
readonly engineKind: "matter2d";
|
|
313
|
+
/** rapier3d only; present so both runtimes satisfy one internal shape. */
|
|
314
|
+
readonly rapier: undefined;
|
|
315
|
+
readonly world: undefined;
|
|
316
|
+
readonly matter: MatterModule;
|
|
317
|
+
readonly engine: MatterEngine;
|
|
318
|
+
/** Alias under the name `PhysicsApi` uses, so `room.physics2d` reads through one field. */
|
|
319
|
+
get matterEngine(): MatterEngine;
|
|
320
|
+
/** `true` when the blob carried no world at all: the caller logs it. */
|
|
321
|
+
readonly rebuilt: boolean;
|
|
322
|
+
/**
|
|
323
|
+
* Always `true`. Unlike Rapier's, a matter2d world is never restored as a world — only as
|
|
324
|
+
* per-body state on a world the builder made — so `setup` has to run every time.
|
|
325
|
+
*/
|
|
326
|
+
readonly needsSetup = true;
|
|
327
|
+
private readonly core;
|
|
328
|
+
private readonly config;
|
|
329
|
+
private readonly collections;
|
|
330
|
+
private readonly bodies;
|
|
331
|
+
private readonly restore;
|
|
332
|
+
private readonly stepMs;
|
|
333
|
+
private readonly sleepSynced;
|
|
334
|
+
constructor(core: RoomInternals, matter: MatterModule, options: MatterRuntimeOptions);
|
|
335
|
+
get timestep(): number;
|
|
336
|
+
runSetup(room: Room): void;
|
|
337
|
+
free(): void;
|
|
338
|
+
bodyFor(collection: string, id: string): MatterBody | undefined;
|
|
339
|
+
private create;
|
|
340
|
+
private applyState;
|
|
341
|
+
private applyRecordToBody;
|
|
342
|
+
reconcile(): void;
|
|
343
|
+
step(): void;
|
|
344
|
+
sync(): void;
|
|
345
|
+
serialize(): MatterSection;
|
|
346
|
+
}
|
|
347
|
+
|
|
228
348
|
/**
|
|
229
349
|
* D22 part 1: the Rapier world inside the room.
|
|
230
350
|
*
|
|
@@ -281,6 +401,23 @@ interface PhysicsSection {
|
|
|
281
401
|
readonly bodies: readonly (readonly [string, string, number])[];
|
|
282
402
|
}
|
|
283
403
|
declare function encodePhysicsSection(section: PhysicsSection): Uint8Array;
|
|
404
|
+
/**
|
|
405
|
+
* D45: which engine wrote a v2 physics section.
|
|
406
|
+
*
|
|
407
|
+
* The discriminant is not a new leading byte, because a new leading byte would change every blob
|
|
408
|
+
* ever written. It is a **zero-length world**: a Rapier section starts with the varint length of
|
|
409
|
+
* `world.takeSnapshot()`, which is never zero, so `0` is a value no existing blob can hold. A
|
|
410
|
+
* matter2d section writes that zero, then a one-byte engine tag, then its own payload. Every
|
|
411
|
+
* pre-D45 blob is therefore byte-identical and reads as Rapier without a version bump, which is
|
|
412
|
+
* what the recommendation in the plan asked for and why format 3 was not needed.
|
|
413
|
+
*/
|
|
414
|
+
type PhysicsEngineTag = 'rapier3d' | 'matter2d';
|
|
415
|
+
/** Reads the engine out of an encoded section without decoding the rest of it. */
|
|
416
|
+
declare function physicsSectionEngine(bytes: Uint8Array): PhysicsEngineTag;
|
|
417
|
+
/** Wraps a matter2d body-state payload in the discriminated envelope. */
|
|
418
|
+
declare function encodeMatterSectionEnvelope(payload: Uint8Array): Uint8Array;
|
|
419
|
+
/** The payload inside a matter2d envelope. Throws if the section is a Rapier one. */
|
|
420
|
+
declare function decodeMatterSectionEnvelope(bytes: Uint8Array): Uint8Array;
|
|
284
421
|
declare function decodePhysicsSection(bytes: Uint8Array): PhysicsSection;
|
|
285
422
|
interface PhysicsRuntimeOptions {
|
|
286
423
|
/** From a v2 hibernation blob. Absent → a fresh world, and `setup` runs. */
|
|
@@ -289,10 +426,16 @@ interface PhysicsRuntimeOptions {
|
|
|
289
426
|
readonly defaultTimestep: number;
|
|
290
427
|
}
|
|
291
428
|
declare class PhysicsRuntime {
|
|
429
|
+
readonly engineKind: "rapier3d";
|
|
430
|
+
/** matter2d only; present so both runtimes satisfy one internal shape. */
|
|
431
|
+
readonly matter: undefined;
|
|
432
|
+
readonly matterEngine: undefined;
|
|
292
433
|
readonly rapier: RapierModule;
|
|
293
434
|
readonly world: RapierWorld;
|
|
294
435
|
/** `true` when the world was built from scratch and `setup` has to run. */
|
|
295
436
|
readonly rebuilt: boolean;
|
|
437
|
+
/** Rapier restores a whole world, contacts included, so `setup` runs only on a rebuild. */
|
|
438
|
+
get needsSetup(): boolean;
|
|
296
439
|
private readonly core;
|
|
297
440
|
private readonly config;
|
|
298
441
|
/** Physics-backed collections, in schema (name-sorted) order. */
|
|
@@ -351,7 +494,7 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
351
494
|
readonly clients: Map<string, ClientEntry>;
|
|
352
495
|
readonly loop: Loop;
|
|
353
496
|
/** D22: the Rapier world, or `undefined` in a room whose config declares no physics. */
|
|
354
|
-
readonly physics: PhysicsRuntime | undefined;
|
|
497
|
+
readonly physics: PhysicsRuntime | MatterRuntime | undefined;
|
|
355
498
|
readonly stats: RoomStats;
|
|
356
499
|
tick: number;
|
|
357
500
|
stopped: boolean;
|
|
@@ -362,6 +505,17 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
362
505
|
* into a timeout in an unrelated assertion ten seconds later. Unset in production.
|
|
363
506
|
*/
|
|
364
507
|
onHandlerError: ((name: string, err: unknown) => void) | undefined;
|
|
508
|
+
/**
|
|
509
|
+
* D41: the recorded authoritative timeline. Armed by `startRecording()` and off otherwise, so a
|
|
510
|
+
* room nobody asked to record pays nothing. A room that was asked captures its own state at the
|
|
511
|
+
* end of every tick, which is the only moment in a tick where that state is settled.
|
|
512
|
+
*/
|
|
513
|
+
private recorder;
|
|
514
|
+
/**
|
|
515
|
+
* D65: the bandwidth ledger, present only when `RoomCoreOptions.profile` asked for one. Every
|
|
516
|
+
* cost the profiler has is behind this `undefined`.
|
|
517
|
+
*/
|
|
518
|
+
private readonly ledger;
|
|
365
519
|
private readonly seed;
|
|
366
520
|
private readonly api;
|
|
367
521
|
private readonly internals;
|
|
@@ -369,6 +523,26 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
369
523
|
/** One pending continuation flush at a time; concurrent completions coalesce into it. */
|
|
370
524
|
private continuationFlushPending;
|
|
371
525
|
constructor(definition: RoomDefinition<S>, host: RoomHost, options: RoomCoreOptions);
|
|
526
|
+
private subscribeDeclaredChannels;
|
|
527
|
+
private get busConfig();
|
|
528
|
+
/**
|
|
529
|
+
* D59: one published message arriving on a channel this room is subscribed to.
|
|
530
|
+
*
|
|
531
|
+
* Same scheduling class as an alarm or an RPC — a discrete event between ticks — so a tick-mode
|
|
532
|
+
* room never sees a `tick` run half-delivered. `from` is supervisor-stamped, so the handler may
|
|
533
|
+
* trust it as far as it trusts its own project.
|
|
534
|
+
*/
|
|
535
|
+
deliverBusEvent(channel: string, from: string, payload: string): void;
|
|
536
|
+
/**
|
|
537
|
+
* D59: one directed `room.bus.send` arriving. At-least-once, so this can run twice for one send;
|
|
538
|
+
* that is the receiver's problem to be idempotent about and the docs say so.
|
|
539
|
+
*
|
|
540
|
+
* A throw propagates through `guard` and counts toward the crash threshold exactly as any other
|
|
541
|
+
* handler throw does. That is deliberate, and it is why the supervisor bounds redelivery: the
|
|
542
|
+
* two behaviours together would otherwise let one poisonous message close a room on every wake,
|
|
543
|
+
* forever.
|
|
544
|
+
*/
|
|
545
|
+
deliverBusMessage(from: string, payload: string): void;
|
|
372
546
|
/**
|
|
373
547
|
* D22: builds the world, or returns `undefined` for a room with no `physics:` config.
|
|
374
548
|
*
|
|
@@ -381,6 +555,13 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
381
555
|
* and is lost; a stack of boxes may settle again with a small visible jolt.
|
|
382
556
|
*/
|
|
383
557
|
private buildPhysics;
|
|
558
|
+
/**
|
|
559
|
+
* D45: the matter2d half. It differs from Rapier's in one structural way — there is no engine
|
|
560
|
+
* snapshot to restore, so the world is always **built** and per-body state is reapplied on top
|
|
561
|
+
* of it. `setup` therefore runs on every wake, and only the "there was no world at all" case is
|
|
562
|
+
* worth logging.
|
|
563
|
+
*/
|
|
564
|
+
private buildMatter;
|
|
384
565
|
/** Convenience for hosts: `RoomCore.restore(def, bytes, host, opts)`. */
|
|
385
566
|
static restore<S2 extends AnySchema>(definition: RoomDefinition<S2>, bytes: Uint8Array, host: RoomHost, options: Omit<RoomCoreOptions, 'restoreFrom'>): RoomCore<S2>;
|
|
386
567
|
get schema(): S;
|
|
@@ -390,6 +571,16 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
390
571
|
tryRun<T>(name: string, fn: () => T): GuardResult<T>;
|
|
391
572
|
private readonly events;
|
|
392
573
|
recordEvent(kind: RoomEventKind, clientId?: string, detail?: string): void;
|
|
574
|
+
/**
|
|
575
|
+
* D41: begin (or restart) recording the authoritative timeline. Calling it again clears what
|
|
576
|
+
* was recorded, so two scenario runs against one long-lived dev server do not read each
|
|
577
|
+
* other's ticks.
|
|
578
|
+
*/
|
|
579
|
+
startRecording(options?: TimelineRecorderOptions): void;
|
|
580
|
+
/** D41: what has been recorded so far, or `undefined` when nobody armed the recorder. */
|
|
581
|
+
recording(): TimelineDump | undefined;
|
|
582
|
+
/** D41: called at the end of every tick (and every event-mode flush). No-op when unarmed. */
|
|
583
|
+
captureTimeline(): void;
|
|
393
584
|
/** Live JSON view of the room for the dev page / supervisor admin API. */
|
|
394
585
|
inspect(): RoomInspection;
|
|
395
586
|
guard<T>(name: string, fn: () => T): T | undefined;
|
|
@@ -434,8 +625,12 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
434
625
|
* Draining a bounded number of turns first covers the chains rooms actually write, coalesces
|
|
435
626
|
* concurrent completions into one flush, and — unlike a `setTimeout(0)` — keeps working under
|
|
436
627
|
* the harness's synchronous fake clock, where a macrotask would fire *before* the microtasks.
|
|
628
|
+
*
|
|
629
|
+
* Public on `RoomInternals` (bug #48) so the room API's local rejections — a float score, an
|
|
630
|
+
* empty bus target, a call to a client that is not connected — flush the state their `.catch()`
|
|
631
|
+
* writes, instead of leaving it for whatever frame happens to arrive next.
|
|
437
632
|
*/
|
|
438
|
-
|
|
633
|
+
scheduleContinuationFlush(): void;
|
|
439
634
|
/**
|
|
440
635
|
* D26: one durable alarm firing. Same scheduling class as an RPC — a discrete event between
|
|
441
636
|
* ticks — so a tick-mode room never sees a `tick` run half-alarmed, and a handler that re-arms
|
|
@@ -444,7 +639,19 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
444
639
|
fireAlarm(name: string): void;
|
|
445
640
|
correctionFor(clientId: string): DirtySet | undefined;
|
|
446
641
|
invalidateClients(): void;
|
|
447
|
-
|
|
642
|
+
/**
|
|
643
|
+
* D65: `hint` is profiling context and nothing else — the payload object a per-view frame was
|
|
644
|
+
* built from (so one encode is walked once and replayed per recipient) and the AOI ids whose
|
|
645
|
+
* ops are visibility churn. Ignored entirely when this room is not profiling, which is why it
|
|
646
|
+
* is an optional argument rather than a second method.
|
|
647
|
+
*/
|
|
648
|
+
send(clientId: string, frame: Uint8Array, hint?: AttributeOptions): void;
|
|
649
|
+
/**
|
|
650
|
+
* D65: the ledger so far. The room's own view, so it counts what `send()` sent and what
|
|
651
|
+
* `receive()` accepted, plus the join snapshots this room handed the host to wrap in a
|
|
652
|
+
* `WELCOME` (the host builds that frame, so the room never sees it — see the docs page).
|
|
653
|
+
*/
|
|
654
|
+
profile(): ProfileSnapshot | undefined;
|
|
448
655
|
private badFrame;
|
|
449
656
|
receive(clientId: string, frame: Uint8Array): void;
|
|
450
657
|
/**
|
|
@@ -455,4 +662,4 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
455
662
|
flush(): void;
|
|
456
663
|
}
|
|
457
664
|
|
|
458
|
-
export { CRASH_AFTER_THROWS as C, MAX_CATCHUP as M, type
|
|
665
|
+
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, encodeMatterBodies as h, encodeMatterSectionEnvelope as i, encodePhysicsSection as j, initMatter as k, initPhysics as l, loadedMatter as m, loadedPhysics as n, resetPhysicsForTests as o, physicsSectionEngine as p, resetMatterForTests as r };
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { R as RoomCore } from '../room-
|
|
2
|
-
export {
|
|
1
|
+
import { R as RoomCore } from '../room-CfnEjlcg.js';
|
|
2
|
+
export { k as initMatter, l as initPhysics } from '../room-CfnEjlcg.js';
|
|
3
3
|
import { ErrorCodeName, FrameType } from '@irtio/protocol';
|
|
4
|
-
import {
|
|
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-BjMsoJIV.js';
|
|
5
6
|
import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
|
|
6
|
-
import { LeaveReason, Room, RoomDefinition } from '@irtio/server';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The harness clock: a deterministic fake `now()` plus an ordered timer queue. `RoomCore` only
|
|
@@ -90,6 +90,23 @@ declare class HarnessHost implements RoomHost {
|
|
|
90
90
|
hostCallDelayMs: number;
|
|
91
91
|
/** The in-memory player KV, keyed the way the real table's composite primary key is. */
|
|
92
92
|
readonly kv: Map<string, string>;
|
|
93
|
+
/** D53: `"<board> <playerId>"` -> the best score submitted so far. */
|
|
94
|
+
readonly scores: Map<string, number>;
|
|
95
|
+
/** D63: every `room.ratings.report` the room asked for, in order. Recorded, never applied. */
|
|
96
|
+
readonly ratingReports: {
|
|
97
|
+
queue: string;
|
|
98
|
+
results: {
|
|
99
|
+
playerId: string;
|
|
100
|
+
place: number;
|
|
101
|
+
}[];
|
|
102
|
+
}[];
|
|
103
|
+
/** D63: every `room.ratings.set`, in order. */
|
|
104
|
+
readonly ratingSets: {
|
|
105
|
+
queue: string;
|
|
106
|
+
playerId: string;
|
|
107
|
+
rating: number;
|
|
108
|
+
deviation?: number;
|
|
109
|
+
}[];
|
|
93
110
|
/** Save generations this host minted: `saveId` -> the bytes it was handed. */
|
|
94
111
|
readonly saves: Map<string, Uint8Array<ArrayBufferLike>>;
|
|
95
112
|
/** What `room.save()` serializes. The harness sets it to `() => core.snapshot()` — the bytes
|
|
@@ -121,6 +138,36 @@ declare class HarnessHost implements RoomHost {
|
|
|
121
138
|
crashed(reason: string): void;
|
|
122
139
|
hostCall(reqId: number, call: HostCall): void;
|
|
123
140
|
private runHostCall;
|
|
141
|
+
/**
|
|
142
|
+
* D44: the in-process harness has no supervisor, no sockets and therefore no loopback session
|
|
143
|
+
* to open. Recording the request rather than faking a session is the honest option: a test that
|
|
144
|
+
* wants to see an NPC *play* needs a real tenant (`packages/supervisor/test/npc.test.ts`), and
|
|
145
|
+
* one that wants to see a room ask for one reads these.
|
|
146
|
+
*/
|
|
147
|
+
readonly spawnedNpcs: {
|
|
148
|
+
clientId: string;
|
|
149
|
+
config: NpcConfig;
|
|
150
|
+
}[];
|
|
151
|
+
readonly despawnedNpcs: string[];
|
|
152
|
+
spawnNpc(clientId: string, config: NpcConfig): void;
|
|
153
|
+
despawnNpc(clientId: string): void;
|
|
154
|
+
/** Every `room.bus.send` the room asked for, in order. */
|
|
155
|
+
readonly busSends: {
|
|
156
|
+
roomId: string;
|
|
157
|
+
payload: string;
|
|
158
|
+
}[];
|
|
159
|
+
/** Every `room.bus.publish`, in order. */
|
|
160
|
+
readonly busPublishes: {
|
|
161
|
+
channel: string;
|
|
162
|
+
payload: string;
|
|
163
|
+
}[];
|
|
164
|
+
/** The channels this room is currently subscribed to, in subscription order. */
|
|
165
|
+
readonly busSubscriptions: Set<string>;
|
|
166
|
+
/** D63-e: the harness has no room list to report on, so it records the value and stops. */
|
|
167
|
+
backfillOpen: boolean | undefined;
|
|
168
|
+
setBackfill(open: boolean): void;
|
|
169
|
+
busPublish(channel: string, payload: string): void;
|
|
170
|
+
busSubscribe(channel: string, subscribed: boolean): void;
|
|
124
171
|
setAlarm(name: string, atMs: number | undefined): void;
|
|
125
172
|
/**
|
|
126
173
|
* Fires every alarm due at or before `now`, in **name order** (D34 determinism), removing each
|
package/dist/test/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RoomCore,
|
|
3
3
|
createVisibilityPolicy,
|
|
4
|
+
initMatter,
|
|
4
5
|
initPhysics,
|
|
5
6
|
visibleNames
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-K42HA75G.js";
|
|
7
8
|
|
|
8
9
|
// src/test/clock.ts
|
|
9
10
|
var FakeClock = class {
|
|
@@ -79,6 +80,12 @@ var HarnessHost = class {
|
|
|
79
80
|
hostCallDelayMs = 0;
|
|
80
81
|
/** The in-memory player KV, keyed the way the real table's composite primary key is. */
|
|
81
82
|
kv = /* @__PURE__ */ new Map();
|
|
83
|
+
/** D53: `"<board> <playerId>"` -> the best score submitted so far. */
|
|
84
|
+
scores = /* @__PURE__ */ new Map();
|
|
85
|
+
/** D63: every `room.ratings.report` the room asked for, in order. Recorded, never applied. */
|
|
86
|
+
ratingReports = [];
|
|
87
|
+
/** D63: every `room.ratings.set`, in order. */
|
|
88
|
+
ratingSets = [];
|
|
82
89
|
/** Save generations this host minted: `saveId` -> the bytes it was handed. */
|
|
83
90
|
saves = /* @__PURE__ */ new Map();
|
|
84
91
|
/** What `room.save()` serializes. The harness sets it to `() => core.snapshot()` — the bytes
|
|
@@ -169,11 +176,72 @@ var HarnessHost = class {
|
|
|
169
176
|
case "kvDelete":
|
|
170
177
|
this.kv.delete(kvKey(call.playerId, call.key));
|
|
171
178
|
return { ok: true };
|
|
179
|
+
case "busSend": {
|
|
180
|
+
this.busSends.push({ roomId: call.roomId, payload: call.payload });
|
|
181
|
+
return { ok: true };
|
|
182
|
+
}
|
|
183
|
+
case "ratingReport": {
|
|
184
|
+
this.ratingReports.push({
|
|
185
|
+
queue: call.queue,
|
|
186
|
+
results: call.results.map((r) => ({ ...r }))
|
|
187
|
+
});
|
|
188
|
+
return { ok: true };
|
|
189
|
+
}
|
|
190
|
+
case "ratingSet": {
|
|
191
|
+
this.ratingSets.push({
|
|
192
|
+
queue: call.queue,
|
|
193
|
+
playerId: call.playerId,
|
|
194
|
+
rating: call.rating,
|
|
195
|
+
...call.deviation !== void 0 ? { deviation: call.deviation } : {}
|
|
196
|
+
});
|
|
197
|
+
return { ok: true };
|
|
198
|
+
}
|
|
199
|
+
case "lbSubmit": {
|
|
200
|
+
const key = `${call.board} ${call.playerId}`;
|
|
201
|
+
const existing = this.scores.get(key);
|
|
202
|
+
if (existing === void 0 || call.score > existing) this.scores.set(key, call.score);
|
|
203
|
+
return { ok: true };
|
|
204
|
+
}
|
|
172
205
|
}
|
|
173
206
|
}
|
|
174
207
|
// -------------------------------------------------------------------------
|
|
175
208
|
// Week 12: durable alarms (D26)
|
|
176
209
|
// -------------------------------------------------------------------------
|
|
210
|
+
/**
|
|
211
|
+
* D44: the in-process harness has no supervisor, no sockets and therefore no loopback session
|
|
212
|
+
* to open. Recording the request rather than faking a session is the honest option: a test that
|
|
213
|
+
* wants to see an NPC *play* needs a real tenant (`packages/supervisor/test/npc.test.ts`), and
|
|
214
|
+
* one that wants to see a room ask for one reads these.
|
|
215
|
+
*/
|
|
216
|
+
spawnedNpcs = [];
|
|
217
|
+
despawnedNpcs = [];
|
|
218
|
+
spawnNpc(clientId, config) {
|
|
219
|
+
this.spawnedNpcs.push({ clientId, config });
|
|
220
|
+
}
|
|
221
|
+
despawnNpc(clientId) {
|
|
222
|
+
this.despawnedNpcs.push(clientId);
|
|
223
|
+
}
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
// D59: the bus
|
|
226
|
+
// -------------------------------------------------------------------------
|
|
227
|
+
/** Every `room.bus.send` the room asked for, in order. */
|
|
228
|
+
busSends = [];
|
|
229
|
+
/** Every `room.bus.publish`, in order. */
|
|
230
|
+
busPublishes = [];
|
|
231
|
+
/** The channels this room is currently subscribed to, in subscription order. */
|
|
232
|
+
busSubscriptions = /* @__PURE__ */ new Set();
|
|
233
|
+
/** D63-e: the harness has no room list to report on, so it records the value and stops. */
|
|
234
|
+
backfillOpen;
|
|
235
|
+
setBackfill(open) {
|
|
236
|
+
this.backfillOpen = open;
|
|
237
|
+
}
|
|
238
|
+
busPublish(channel, payload) {
|
|
239
|
+
this.busPublishes.push({ channel, payload });
|
|
240
|
+
}
|
|
241
|
+
busSubscribe(channel, subscribed) {
|
|
242
|
+
if (subscribed) this.busSubscriptions.add(channel);
|
|
243
|
+
else this.busSubscriptions.delete(channel);
|
|
244
|
+
}
|
|
177
245
|
setAlarm(name, atMs) {
|
|
178
246
|
if (atMs === void 0) this.armedAlarms.delete(name);
|
|
179
247
|
else this.armedAlarms.set(name, atMs);
|
|
@@ -905,5 +973,6 @@ export {
|
|
|
905
973
|
HarnessHost,
|
|
906
974
|
createRoomHarness,
|
|
907
975
|
frameTypeName,
|
|
976
|
+
initMatter,
|
|
908
977
|
initPhysics
|
|
909
978
|
};
|