@irtio/runtime 4.3.0 → 4.4.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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { A as AdmissionError, D as DEFAULT_RECORDS_PER_COLLECTION, a as DEFAULT_TIMELINE_MAX_RECORDS, b as DEFAULT_TIMELINE_MAX_TICKS, E as EVENT_RING_SIZE, H as HOST_CALL_TIMEOUT_MS, c as HostCall, d as HostCallResult, J as JoinOptions, e as JoinResult, L as LogLevel, P as PHYSICS_STEP_EMA_ALPHA, R as RELAY_RECORDS_PER_COLLECTION, f as RELAY_STATE_BYTES, g as RELAY_WRITES_PER_SECOND, h as RELAY_WRITE_CAPS, i as RPC_TRACE_SIZE, j as RPC_TRACE_SUMMARY_MAX, k as RoomCoreApi, l as RoomCoreOptions, m as RoomEvent, n as RoomEventKind, o as RoomFullError, p as RoomHost, q as RoomInspection, r as RoomStats, s as RpcOutcome, t as RpcTraceDump, u as RpcTraceEntry, v as RpcTraceRing, S as StampLag, T as TimelineDump, w as TimelineFrame, x as TimelineRecorder, y as TimelineRecorderOptions, W as WriteCaps, z as inspectState, B as summarizeRpcResult } from './contract-DHWlURaq.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-Bpo_nqlO.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-BYhsLgQV.js';
3
3
  import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
4
4
  import '@irtio/protocol';
5
5
  import '@irtio/server';
@@ -155,7 +155,7 @@ interface DecodedSave extends SnapshotHeader {
155
155
  * there is no section at all. A pre-D45 blob has no discriminant and reads as `'rapier3d'`,
156
156
  * which is what it is.
157
157
  */
158
- readonly physicsEngine: 'rapier3d' | 'matter2d' | 'rapier2d' | undefined;
158
+ readonly physicsEngine: 'rapier3d' | 'matter2d' | 'rapier2d' | 'custom' | undefined;
159
159
  /**
160
160
  * The room's authoritative state, in exactly the shape `inspectState` and the recorded
161
161
  * 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-ZGJY4E3G.js";
5
+ } from "./chunk-GK53BSWK.js";
6
6
  import {
7
7
  AdmissionError,
8
8
  CRASH_AFTER_THROWS,
@@ -59,7 +59,7 @@ import {
59
59
  visibleNames,
60
60
  visibleTo,
61
61
  writeHibernationBlob
62
- } from "./chunk-KJN62J37.js";
62
+ } from "./chunk-JFC5BQ2N.js";
63
63
  export {
64
64
  AdmissionError,
65
65
  CRASH_AFTER_THROWS,
@@ -1,6 +1,6 @@
1
1
  import { PlatformNotice, 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, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
3
+ import { RoomDefinition, RoomMode, Room, RewindView, Ctx, CustomStepper, CustomPose, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
4
4
  import { p as RoomHost, r as RoomStats, u as RpcTraceEntry, L as LogLevel, k as RoomCoreApi, l as RoomCoreOptions, n as RoomEventKind, y as TimelineRecorderOptions, T as TimelineDump, t as RpcTraceDump, q as RoomInspection, J as JoinOptions, e as JoinResult, d as HostCallResult } from './contract-DHWlURaq.js';
5
5
 
6
6
  declare class Mulberry32 {
@@ -123,10 +123,29 @@ interface UnsyncedRegistry {
123
123
  keys(): IterableIterator<string>;
124
124
  [Symbol.iterator](): IterableIterator<readonly [string, unknown]>;
125
125
  }
126
+ /**
127
+ * One body's pose in the thirteen canonical channels. Structurally `Pose` in `core/history.ts` and
128
+ * `CustomPose` in `@irtio/server`; restated here so this seam declares no dependency on either.
129
+ */
130
+ interface PosePlain {
131
+ x: number;
132
+ y: number;
133
+ z: number;
134
+ qx: number;
135
+ qy: number;
136
+ qz: number;
137
+ qw: number;
138
+ vx: number;
139
+ vy: number;
140
+ vz: number;
141
+ wx: number;
142
+ wy: number;
143
+ wz: number;
144
+ }
126
145
  /** What the core modules may do with the physics world (`core/physics.ts` implements it). */
127
146
  interface PhysicsApi {
128
- /** D45: which of the blessed engines this room is running. */
129
- readonly engineKind: 'rapier3d' | 'matter2d' | 'rapier2d';
147
+ /** D45: which engine this room is running. `'custom'` is the game's own stepper, not an engine. */
148
+ readonly engineKind: 'rapier3d' | 'matter2d' | 'rapier2d' | 'custom';
130
149
  readonly timestep: number;
131
150
  /** Creates bodies for new instances, destroys bodies whose instance is gone. */
132
151
  reconcile(): void;
@@ -147,6 +166,12 @@ interface PhysicsApi {
147
166
  * `bodyFor`, and it never hands out the map itself.
148
167
  */
149
168
  eachTrackedBody(fn: (collection: string, id: string, body: unknown, instance: Record<string, unknown>) => void): void;
169
+ /**
170
+ * D72: this runtime's own engine-neutral pose read, when it has one. The pose history prefers it
171
+ * over its per-engine switch, which is how an engine the history file knows nothing about — the
172
+ * custom stepper — gets rewind without `history.ts` gaining a branch that can read a body.
173
+ */
174
+ readPose?(body: unknown, out: PosePlain): void;
150
175
  /** rapier3d and rapier2d: the engine namespace. `engineKind` says which module it is. */
151
176
  readonly rapier: unknown;
152
177
  /** rapier3d and rapier2d: the live `World`. */
@@ -247,6 +272,99 @@ interface RoomInternals {
247
272
  invalidateClients(): void;
248
273
  }
249
274
 
275
+ /**
276
+ * The fourth engine: the game's own, as a plain-function stepper.
277
+ *
278
+ * It sits in exactly the place `core/physics.ts` describes — reconcile, step, sync, between the
279
+ * `tick()` handler and the flush — and writes the same schema channels. What differs is that there
280
+ * is no engine underneath at all, and three consequences follow.
281
+ *
282
+ * ## There is nothing to load and nothing to snapshot
283
+ *
284
+ * No WASM, no module, no `init*()` before the room is constructed: `config.world(room)` builds the
285
+ * stepper out of code the bundle already has. And there is no world state to serialize — a custom
286
+ * room's physics section is a tag and no payload. A woken room rebuilds its bodies from schema
287
+ * state on the first reconcile, which is faithful to whatever the body channels carry, and loses
288
+ * whatever the stepper kept that the schema does not name. That is the same contract a matter2d
289
+ * room already lives under, said with less machinery.
290
+ *
291
+ * ## Intents are applied here, unlike the blessed engines
292
+ *
293
+ * `physics.intents` on a Rapier or matter2d room is a *naming*: the runtime never calls it and the
294
+ * room's `tick()` does. A stepper's `applyIntent` is different — the runtime runs it over every
295
+ * tracked body immediately before `step()`. The reason is that the shared-code argument is the
296
+ * whole of why the field exists, and the client's predictor already calls `applyIntent` per body
297
+ * per step; a server that left it to `tick()` would make "same code both sides" a thing the room
298
+ * author has to get right by hand, on the one engine where nothing else is checkable. A room that
299
+ * would rather steer from `tick()` simply does not define `applyIntent`.
300
+ *
301
+ * ## Poses are the whole seam
302
+ *
303
+ * `readPose`/`writePose` speak the thirteen canonical channels by name, so `sync()` is a copy and
304
+ * the pose history needs no engine-specific reader — which is what makes `room.rewind` work here
305
+ * for free. See `core/history.ts`.
306
+ */
307
+
308
+ interface CustomRuntimeOptions {
309
+ readonly defaultTimestep: number;
310
+ }
311
+ declare class CustomRuntime {
312
+ readonly engineKind: "custom";
313
+ /** The three blessed-engine handles, present so every runtime satisfies one internal shape. */
314
+ readonly rapier: undefined;
315
+ readonly world: undefined;
316
+ readonly matter: undefined;
317
+ readonly matterEngine: undefined;
318
+ /** Always: a custom room has no world snapshot, so its bodies are always rebuilt from state. */
319
+ readonly rebuilt = true;
320
+ readonly needsSetup = false;
321
+ private readonly core;
322
+ private readonly config;
323
+ private readonly collections;
324
+ private readonly stepSeconds;
325
+ private readonly bodies;
326
+ /** See `PhysicsRuntime.sourceRecords`: identity, so a pooled re-add rebuilds its body. */
327
+ private readonly sourceRecords;
328
+ private readonly skippedKeys;
329
+ private readonly scratch;
330
+ private stepper;
331
+ /**
332
+ * M7 pf3 (a): a custom room's unsynced bodies are the stepper's own — it created them, it steps
333
+ * them, and nothing here knows what one is — so the registry keeps names for them and calls no
334
+ * engine. It does not survive hibernation, for the same reason nothing else here does.
335
+ */
336
+ private readonly unsyncedBodies;
337
+ readonly unsynced: UnsyncedRegistry;
338
+ constructor(core: RoomInternals, options: CustomRuntimeOptions);
339
+ get timestep(): number;
340
+ /**
341
+ * Builds the stepper. Named `runSetup` because that is the hook `RoomCore` calls at the same
342
+ * point for every engine; for this one it is the world's construction and not a hook on top of
343
+ * one, which is why it runs before the first reconcile rather than optionally.
344
+ */
345
+ runSetup(room: Room): void;
346
+ free(): void;
347
+ /** `room.physicsCustom.stepper`. Throws rather than handing back `undefined` half a world. */
348
+ get liveStepper(): CustomStepper;
349
+ setSkipped(collection: string, id: string, on: boolean): void;
350
+ isSkipped(collection: string, id: string): boolean;
351
+ bodyFor(collection: string, id: string): object | undefined;
352
+ private create;
353
+ /** The schema's mapped channels, written onto the body over whatever it already held. */
354
+ private applyRecordToBody;
355
+ eachTrackedBody(fn: (collection: string, id: string, body: object, instance: Record<string, unknown>) => void): void;
356
+ /**
357
+ * D72's engine-neutral pose read, implemented rather than switched on. The history ring prefers
358
+ * this hook when a runtime carries one, which is what keeps `core/history.ts` from having to
359
+ * know what a custom body is.
360
+ */
361
+ readPose(body: unknown, out: CustomPose): void;
362
+ reconcile(): void;
363
+ step(): void;
364
+ /** Body → schema, through the tracked proxies, `Math.fround`ed for f32 fields as Rapier's is. */
365
+ sync(): void;
366
+ }
367
+
250
368
  /**
251
369
  * Scheduling: the tick-mode fixed-step loop with catch-up, event-mode idle detection, and
252
370
  * `room.setTimeout` / `room.setInterval`.
@@ -617,7 +735,7 @@ declare function encodePhysicsSection(section: PhysicsSection): Uint8Array;
617
735
  * pre-D45 blob is therefore byte-identical and reads as Rapier without a version bump, which is
618
736
  * what the recommendation in the plan asked for and why format 3 was not needed.
619
737
  */
620
- type PhysicsEngineTag = 'rapier3d' | 'matter2d' | 'rapier2d';
738
+ type PhysicsEngineTag = 'rapier3d' | 'matter2d' | 'rapier2d' | 'custom';
621
739
  /**
622
740
  * Reads the engine out of an encoded section without decoding the rest of it.
623
741
  *
@@ -941,7 +1059,7 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
941
1059
  * where an unsynced body belongs — it is static geometry with a handle — and it is the only
942
1060
  * place a matter2d room can put one and have it come back after a wake.
943
1061
  */
944
- physics: PhysicsRuntime | MatterRuntime | Rapier2dRuntime | undefined;
1062
+ physics: PhysicsRuntime | MatterRuntime | Rapier2dRuntime | CustomRuntime | undefined;
945
1063
  readonly stats: RoomStats;
946
1064
  tick: number;
947
1065
  stopped: boolean;
@@ -1049,6 +1167,12 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
1049
1167
  * again. The only thing that differs from `buildPhysics` is which engine and which envelope.
1050
1168
  */
1051
1169
  private buildRapier2d;
1170
+ /**
1171
+ * The custom half. There is no engine to check for, no world to restore and no `setup` beyond
1172
+ * building the stepper itself, so this is the shortest of the four: build it, say so if a blob
1173
+ * arrived carrying somebody else's world, reconcile the bodies out of schema state.
1174
+ */
1175
+ private buildCustom;
1052
1176
  /** Convenience for hosts: `RoomCore.restore(def, bytes, host, opts)`. */
1053
1177
  static restore<S2 extends AnySchema>(definition: RoomDefinition<S2>, bytes: Uint8Array, host: RoomHost, options: Omit<RoomCoreOptions, 'restoreFrom'>): RoomCore<S2>;
1054
1178
  get schema(): S;
@@ -1,5 +1,5 @@
1
- import { R as RoomCore } from '../room-Bpo_nqlO.js';
2
- export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-Bpo_nqlO.js';
1
+ import { R as RoomCore } from '../room-BYhsLgQV.js';
2
+ export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-BYhsLgQV.js';
3
3
  import { ErrorCodeName, FrameType } from '@irtio/protocol';
4
4
  import { NpcConfig, LeaveReason, Room, RoomDefinition } from '@irtio/server';
5
5
  import { p as RoomHost, L as LogLevel, k as RoomCoreApi, c as HostCall, d as HostCallResult, W as WriteCaps, r as RoomStats } from '../contract-DHWlURaq.js';
@@ -6,7 +6,7 @@ import {
6
6
  initRapier2d,
7
7
  isPerClient,
8
8
  visibleNames
9
- } from "../chunk-KJN62J37.js";
9
+ } from "../chunk-JFC5BQ2N.js";
10
10
 
11
11
  // src/test/clock.ts
12
12
  var FakeClock = class {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  migrateSnapshot
3
- } from "../chunk-ZGJY4E3G.js";
3
+ } from "../chunk-GK53BSWK.js";
4
4
  import {
5
5
  AdmissionError,
6
6
  RoomCore,
@@ -10,7 +10,7 @@ import {
10
10
  initRapier2d,
11
11
  onFirstRapierStep,
12
12
  rapierHasStepped
13
- } from "../chunk-KJN62J37.js";
13
+ } from "../chunk-JFC5BQ2N.js";
14
14
 
15
15
  // src/worker/index.ts
16
16
  import { getHeapStatistics } from "v8";
@@ -157,7 +157,8 @@ async function handleMessage(state, host, post, msg) {
157
157
  }
158
158
  if (def.config.physics) {
159
159
  try {
160
- if (def.config.physics.engine === "matter2d") await initMatter();
160
+ if (def.config.physics.engine === "custom") {
161
+ } else if (def.config.physics.engine === "matter2d") await initMatter();
161
162
  else {
162
163
  if (def.config.physics.engine === "rapier2d") await initRapier2d();
163
164
  else await initPhysics();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irtio/runtime",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "irtio room runtime: RoomCore, worker_threads host, in-process test harness",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "@dimforge/rapier2d-compat": "0.20.0",
37
37
  "@dimforge/rapier3d-compat": "0.20.0",
38
38
  "matter-js": "0.20.0",
39
- "@irtio/protocol": "^4.3.0",
40
- "@irtio/schema": "^4.3.0",
41
- "@irtio/server": "^4.3.0"
39
+ "@irtio/protocol": "^4.4.0",
40
+ "@irtio/schema": "^4.4.0",
41
+ "@irtio/server": "^4.4.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/matter-js": "0.20.2"