@irtio/runtime 0.11.0 → 1.0.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-GYPUNTQN.js → chunk-LT46QFLW.js} +751 -66
- package/dist/{chunk-ACC3SDIO.js → chunk-YWDRE77H.js} +1 -1
- package/dist/{contract-Hk4SkDtW.d.ts → contract-UEKTud1Z.d.ts} +44 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{room-0FZBGyke.d.ts → room-uhj3b-8E.d.ts} +24 -2
- package/dist/test/index.d.ts +9 -3
- package/dist/test/index.js +18 -1
- package/dist/worker/index.d.ts +37 -3
- package/dist/worker/index.js +34 -3
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnySchema, PlainState, State, Tracked } from '@irtio/schema';
|
|
2
2
|
import * as _irtio_protocol from '@irtio/protocol';
|
|
3
|
-
import { ErrorCodeName } from '@irtio/protocol';
|
|
3
|
+
import { PlatformNotice, ErrorCodeName } from '@irtio/protocol';
|
|
4
4
|
import { RoomDefinition, Room, LeaveReason, NpcConfig } from '@irtio/server';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -84,7 +84,9 @@ declare class TimelineRecorder {
|
|
|
84
84
|
|
|
85
85
|
type RoomEventKind = 'join' | 'leave' | 'write' | 'write-rejected' | 'correct' | 'call' | 'reply' | 'msg' | 'alarm'
|
|
86
86
|
/** D59: a bus event (a published message) or a directed bus send being delivered. */
|
|
87
|
-
| 'bus'
|
|
87
|
+
| 'bus'
|
|
88
|
+
/** M7 D-P2: a platform notice reaching `onPlatformNotice`; the detail is the phase. */
|
|
89
|
+
| 'notice' | 'error';
|
|
88
90
|
interface RoomEvent {
|
|
89
91
|
readonly tick: number;
|
|
90
92
|
readonly kind: RoomEventKind;
|
|
@@ -267,10 +269,42 @@ type HostCall = {
|
|
|
267
269
|
* number, but that bound lives on the side of the seam the tenant's own code owns.
|
|
268
270
|
*
|
|
269
271
|
* The result's `value` is the minted clip id.
|
|
272
|
+
*
|
|
273
|
+
* M6 clip-TTL packet: `expiresAtEpochS` is the unix second this clip should die, computed here
|
|
274
|
+
* because this is where `replay.ttl` is declared and read. The supervisor clamps it into
|
|
275
|
+
* `[now + 1m, now + 3650d]` before minting an id from it, so choosing it inside the tenant's
|
|
276
|
+
* own worker buys a hostile room nothing an honest one cannot declare.
|
|
270
277
|
*/
|
|
271
278
|
| {
|
|
272
279
|
readonly kind: 'clip';
|
|
273
280
|
readonly bytes: Uint8Array;
|
|
281
|
+
readonly expiresAtEpochS: number;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* D81's `record: true`: one numbered chunk of a continuous recording, on its way to storage.
|
|
285
|
+
*
|
|
286
|
+
* The chunk is the ring's own evictions, kept — so this call carries bulk bytes for the same
|
|
287
|
+
* reason `clip` does, is walled on the far side for the same reason, and is the only other host
|
|
288
|
+
* call that does either.
|
|
289
|
+
*
|
|
290
|
+
* `recId` is `undefined` on the first chunk of a recording and the supervisor's reply carries
|
|
291
|
+
* the id it minted; every chunk after it names that id. The mint is on the supervisor's side
|
|
292
|
+
* because that is where the entropy and the key algebra live (the `clip` precedent), and the
|
|
293
|
+
* runtime never invents one — which is also what keeps two rooms recording in one project from
|
|
294
|
+
* ever sharing a prefix.
|
|
295
|
+
*
|
|
296
|
+
* M6 clip-TTL packet: `expiresAtEpochS` is the expiry computed when the recording STARTED, and
|
|
297
|
+
* every chunk carries that same number for the life of the recording. It only decides anything
|
|
298
|
+
* on the first chunk — the mint — because every chunk after it is addressed by the `recId` the
|
|
299
|
+
* mint produced, which is what makes a 40-minute match expire atomically instead of losing its
|
|
300
|
+
* opening while its ending is still being watched.
|
|
301
|
+
*/
|
|
302
|
+
| {
|
|
303
|
+
readonly kind: 'record-chunk';
|
|
304
|
+
readonly recId: string | undefined;
|
|
305
|
+
readonly seq: number;
|
|
306
|
+
readonly bytes: Uint8Array;
|
|
307
|
+
readonly expiresAtEpochS: number;
|
|
274
308
|
};
|
|
275
309
|
/**
|
|
276
310
|
* What a `HostCall` produced. `value` carries the save id for `save` and the stored string for
|
|
@@ -533,6 +567,12 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
|
|
|
533
567
|
fireAlarm(name: string): void;
|
|
534
568
|
/** D59: deliver one published message on a channel this room is subscribed to. */
|
|
535
569
|
deliverBusEvent(channel: string, from: string, payload: string): void;
|
|
570
|
+
/**
|
|
571
|
+
* M7 D-P2: deliver a platform notice to `onPlatformNotice` and answer whether the supervisor
|
|
572
|
+
* should still broadcast it. `false` means the room vetoed; a throw, a missing handler and a
|
|
573
|
+
* stopped room all answer `true`.
|
|
574
|
+
*/
|
|
575
|
+
deliverPlatformNotice(notice: PlatformNotice): boolean;
|
|
536
576
|
/** D59: deliver one directed `room.bus.send`. At-least-once, so this may repeat a message. */
|
|
537
577
|
deliverBusMessage(from: string, payload: string): void;
|
|
538
578
|
/**
|
|
@@ -543,6 +583,8 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
|
|
|
543
583
|
stateBytes(): number | undefined;
|
|
544
584
|
/** D77: how many records this room is holding for players who have left. */
|
|
545
585
|
holdCount(): number;
|
|
586
|
+
/** How many `transfer: 'ask'` requests are waiting on an owner's answer. Never survives sleep. */
|
|
587
|
+
askCount(): number;
|
|
546
588
|
}
|
|
547
589
|
|
|
548
590
|
export { DEFAULT_RECORDS_PER_COLLECTION as D, EVENT_RING_SIZE as E, HOST_CALL_TIMEOUT_MS as H, type JoinOptions as J, type LogLevel as L, RELAY_RECORDS_PER_COLLECTION as R, type TimelineDump as T, type WriteCaps as W, DEFAULT_TIMELINE_MAX_RECORDS as a, DEFAULT_TIMELINE_MAX_TICKS as b, type HostCall as c, type HostCallResult as d, type JoinResult as e, RELAY_STATE_BYTES as f, RELAY_WRITES_PER_SECOND as g, RELAY_WRITE_CAPS as h, type RoomCoreApi as i, type RoomCoreOptions as j, type RoomEvent as k, type RoomEventKind as l, RoomFullError as m, type RoomHost as n, type RoomInspection as o, type RoomStats as p, type TimelineFrame as q, TimelineRecorder as r, type TimelineRecorderOptions as s, inspectState as t };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { 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, R as RELAY_RECORDS_PER_COLLECTION, f as RELAY_STATE_BYTES, g as RELAY_WRITES_PER_SECOND, h as RELAY_WRITE_CAPS, i as RoomCoreApi, j as RoomCoreOptions, k as RoomEvent, l as RoomEventKind, m as RoomFullError, n as RoomHost, o as RoomInspection, p as RoomStats, T as TimelineDump, q as TimelineFrame, r as TimelineRecorder, s as TimelineRecorderOptions, W as WriteCaps, t 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 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-
|
|
1
|
+
export { 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, R as RELAY_RECORDS_PER_COLLECTION, f as RELAY_STATE_BYTES, g as RELAY_WRITES_PER_SECOND, h as RELAY_WRITE_CAPS, i as RoomCoreApi, j as RoomCoreOptions, k as RoomEvent, l as RoomEventKind, m as RoomFullError, n as RoomHost, o as RoomInspection, p as RoomStats, T as TimelineDump, q as TimelineFrame, r as TimelineRecorder, s as TimelineRecorderOptions, W as WriteCaps, t as inspectState } from './contract-UEKTud1Z.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-uhj3b-8E.js';
|
|
3
3
|
import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
|
|
4
4
|
import '@irtio/protocol';
|
|
5
5
|
import '@irtio/server';
|
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-YWDRE77H.js";
|
|
6
6
|
import {
|
|
7
7
|
CRASH_AFTER_THROWS,
|
|
8
8
|
DEFAULT_RECORDS_PER_COLLECTION,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
visibleNames,
|
|
53
53
|
visibleTo,
|
|
54
54
|
writeHibernationBlob
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-LT46QFLW.js";
|
|
56
56
|
export {
|
|
57
57
|
CRASH_AFTER_THROWS,
|
|
58
58
|
DEFAULT_RECORDS_PER_COLLECTION,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AttributeOptions, ProfileSnapshot } from '@irtio/protocol';
|
|
1
|
+
import { PlatformNotice, AttributeOptions, ProfileSnapshot } from '@irtio/protocol';
|
|
2
2
|
import { DirtySet, AnySchema, PlainState, Tracked, State } from '@irtio/schema';
|
|
3
3
|
import { RoomDefinition, RoomMode, Room, RewindView, Ctx, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
|
|
4
|
-
import { n as RoomHost, p as RoomStats, L as LogLevel, i as RoomCoreApi, j as RoomCoreOptions, l as RoomEventKind, s as TimelineRecorderOptions, T as TimelineDump, o as RoomInspection, J as JoinOptions, e as JoinResult, d as HostCallResult } from './contract-
|
|
4
|
+
import { n as RoomHost, p as RoomStats, L as LogLevel, i as RoomCoreApi, j as RoomCoreOptions, l as RoomEventKind, s as TimelineRecorderOptions, T as TimelineDump, o as RoomInspection, J as JoinOptions, e as JoinResult, d as HostCallResult } from './contract-UEKTud1Z.js';
|
|
5
5
|
|
|
6
6
|
declare class Mulberry32 {
|
|
7
7
|
/** Current internal state (u32). Survives hibernation. */
|
|
@@ -734,6 +734,26 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
734
734
|
constructor(definition: RoomDefinition<S>, host: RoomHost, options: RoomCoreOptions);
|
|
735
735
|
private subscribeDeclaredChannels;
|
|
736
736
|
private get busConfig();
|
|
737
|
+
/**
|
|
738
|
+
* M7 D-P2: the platform notice, and the room's veto.
|
|
739
|
+
*
|
|
740
|
+
* Answers whether the supervisor should broadcast. `false` — the veto — comes back for exactly
|
|
741
|
+
* one input: a handler that ran and returned `false`. Every other path answers `true`, and each
|
|
742
|
+
* one is a different way of the room having said nothing:
|
|
743
|
+
*
|
|
744
|
+
* - **No handler.** A room that never wrote this hook. The design's whole point is that its
|
|
745
|
+
* players still hear the warning; a code-less room does not even reach this file.
|
|
746
|
+
* - **A handler that threw.** `tryRun` and not `guard`, so the throw counts toward the crash
|
|
747
|
+
* threshold like any other, and it still does not become a veto. This is the one place the
|
|
748
|
+
* convention differs from `onChat`, where a throw refuses, and the reason is the asymmetry of
|
|
749
|
+
* the two losses: a chat line nobody sees is small; a save-loss warning nobody sees is the
|
|
750
|
+
* feature not existing.
|
|
751
|
+
* - **A stopped room.** Nobody to ask, and for `closing` nobody left to veto for.
|
|
752
|
+
*
|
|
753
|
+
* The handler may also have written state, posted chat or started an end sequence. All of that
|
|
754
|
+
* is ordinary handler work and is flushed here the way an alarm's is.
|
|
755
|
+
*/
|
|
756
|
+
deliverPlatformNotice(notice: PlatformNotice): boolean;
|
|
737
757
|
/**
|
|
738
758
|
* D59: one published message arriving on a channel this room is subscribed to.
|
|
739
759
|
*
|
|
@@ -881,6 +901,8 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
|
|
|
881
901
|
stateBytes(): number | undefined;
|
|
882
902
|
/** D77: how many records are held for players who have left. */
|
|
883
903
|
holdCount(): number;
|
|
904
|
+
/** How many `transfer: 'ask'` requests are waiting on an owner's answer right now. */
|
|
905
|
+
askCount(): number;
|
|
884
906
|
profile(): ProfileSnapshot | undefined;
|
|
885
907
|
private badFrame;
|
|
886
908
|
receive(clientId: string, frame: Uint8Array): void;
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as RoomCore } from '../room-
|
|
2
|
-
export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-
|
|
1
|
+
import { R as RoomCore } from '../room-uhj3b-8E.js';
|
|
2
|
+
export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-uhj3b-8E.js';
|
|
3
3
|
import { ErrorCodeName, FrameType } from '@irtio/protocol';
|
|
4
4
|
import { NpcConfig, LeaveReason, Room, RoomDefinition } from '@irtio/server';
|
|
5
|
-
import { n as RoomHost, L as LogLevel, i as RoomCoreApi, c as HostCall, d as HostCallResult, W as WriteCaps, p as RoomStats } from '../contract-
|
|
5
|
+
import { n as RoomHost, L as LogLevel, i as RoomCoreApi, c as HostCall, d as HostCallResult, W as WriteCaps, p as RoomStats } from '../contract-UEKTud1Z.js';
|
|
6
6
|
import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -141,6 +141,12 @@ declare class HarnessHost implements RoomHost {
|
|
|
141
141
|
/** D81: every clip the room wrote, in order: `clipId` -> the blob it was handed. */
|
|
142
142
|
readonly clips: Map<string, Uint8Array<ArrayBufferLike>>;
|
|
143
143
|
private nextClipId;
|
|
144
|
+
/** D81: every recording this room wrote: `recId` -> its chunks in the order they were sent. */
|
|
145
|
+
readonly recordings: Map<string, {
|
|
146
|
+
seq: number;
|
|
147
|
+
bytes: Uint8Array;
|
|
148
|
+
}[]>;
|
|
149
|
+
private nextRecordingId;
|
|
144
150
|
/**
|
|
145
151
|
* D44: the in-process harness has no supervisor, no sockets and therefore no loopback session
|
|
146
152
|
* to open. Recording the request rather than faking a session is the honest option: a test that
|
package/dist/test/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
initPhysics,
|
|
6
6
|
initRapier2d,
|
|
7
7
|
visibleNames
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-LT46QFLW.js";
|
|
9
9
|
|
|
10
10
|
// src/test/clock.ts
|
|
11
11
|
var FakeClock = class {
|
|
@@ -209,6 +209,18 @@ var HarnessHost = class {
|
|
|
209
209
|
this.clips.set(clipId, call.bytes);
|
|
210
210
|
return { ok: true, value: clipId };
|
|
211
211
|
}
|
|
212
|
+
// ---- end M6 lane N ----
|
|
213
|
+
// ---- M6 record packet ----
|
|
214
|
+
case "record-chunk": {
|
|
215
|
+
const recId = call.recId ?? `rec${String(this.nextRecordingId++).padStart(4, "0")}`;
|
|
216
|
+
let chunks = this.recordings.get(recId);
|
|
217
|
+
if (!chunks) {
|
|
218
|
+
chunks = [];
|
|
219
|
+
this.recordings.set(recId, chunks);
|
|
220
|
+
}
|
|
221
|
+
chunks.push({ seq: call.seq, bytes: call.bytes });
|
|
222
|
+
return { ok: true, value: recId };
|
|
223
|
+
}
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
226
|
// ---- M6 lane N: replays ----
|
|
@@ -216,6 +228,11 @@ var HarnessHost = class {
|
|
|
216
228
|
clips = /* @__PURE__ */ new Map();
|
|
217
229
|
nextClipId = 1;
|
|
218
230
|
// ---- end M6 lane N ----
|
|
231
|
+
// ---- M6 record packet ----
|
|
232
|
+
/** D81: every recording this room wrote: `recId` -> its chunks in the order they were sent. */
|
|
233
|
+
recordings = /* @__PURE__ */ new Map();
|
|
234
|
+
nextRecordingId = 1;
|
|
235
|
+
// ---- end M6 record packet ----
|
|
219
236
|
// -------------------------------------------------------------------------
|
|
220
237
|
// Week 12: durable alarms (D26)
|
|
221
238
|
// -------------------------------------------------------------------------
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LogLevel, T as TimelineDump, p as RoomStats, c as HostCall, d as HostCallResult, i as RoomCoreApi, n as RoomHost } from '../contract-
|
|
2
|
-
import { ErrorCodeName, ProfileSnapshot } from '@irtio/protocol';
|
|
1
|
+
import { L as LogLevel, T as TimelineDump, p as RoomStats, c as HostCall, d as HostCallResult, i as RoomCoreApi, n as RoomHost } from '../contract-UEKTud1Z.js';
|
|
2
|
+
import { ErrorCodeName, ProfileSnapshot, PlatformNotice } from '@irtio/protocol';
|
|
3
3
|
import { NpcConfig, LeaveReason } from '@irtio/server';
|
|
4
4
|
import '@irtio/schema';
|
|
5
5
|
|
|
@@ -132,6 +132,17 @@ type ToWorker = {
|
|
|
132
132
|
name: string;
|
|
133
133
|
from: string;
|
|
134
134
|
payload: string;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* M7 D-P2: the platform has something to tell this room's players, and the room gets the first
|
|
138
|
+
* look. Carries a `reqId` because the answer decides whether the supervisor broadcasts, so this
|
|
139
|
+
* is a request and not a notification — a rare thing on this side of the wire, and the reason is
|
|
140
|
+
* that a veto only means anything before the frame goes out.
|
|
141
|
+
*/
|
|
142
|
+
| {
|
|
143
|
+
t: 'platformNotice';
|
|
144
|
+
reqId: number;
|
|
145
|
+
notice: PlatformNotice;
|
|
135
146
|
} | {
|
|
136
147
|
t: 'stop';
|
|
137
148
|
};
|
|
@@ -224,6 +235,17 @@ type FromWorker = {
|
|
|
224
235
|
reqId: number;
|
|
225
236
|
ok: boolean;
|
|
226
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* M7 D-P2: the room's answer to a `platformNotice`. `broadcast: false` only when the handler
|
|
240
|
+
* returned exactly `false`. A handler that threw, a room with no handler, and a room whose core
|
|
241
|
+
* is not up all answer `true`, because the veto is a room saying "I will tell them myself" and
|
|
242
|
+
* none of those three said anything.
|
|
243
|
+
*/
|
|
244
|
+
| {
|
|
245
|
+
t: 'platformNoticeResult';
|
|
246
|
+
reqId: number;
|
|
247
|
+
broadcast: boolean;
|
|
248
|
+
}
|
|
227
249
|
/** D41: the recorded timeline. `dump` absent when nobody armed the recorder. */
|
|
228
250
|
| {
|
|
229
251
|
t: 'timeline';
|
|
@@ -368,5 +390,17 @@ interface WorkerState {
|
|
|
368
390
|
* killing the worker.
|
|
369
391
|
*/
|
|
370
392
|
declare function handleMessage(state: WorkerState, host: RoomHost, post: (msg: FromWorker, transfer?: ArrayBuffer[]) => void, msg: ToWorker): Promise<void>;
|
|
393
|
+
/**
|
|
394
|
+
* Replaces `Math.random` with sfc32 seeded from 16 host-supplied bytes.
|
|
395
|
+
*
|
|
396
|
+
* Box day 2 (2026-09-08) measured why this exists: two VMs restored from one golden snapshot
|
|
397
|
+
* handed their room workers **bit-for-bit identical `Math.random` streams** — V8 seeds a new
|
|
398
|
+
* isolate's PRNG from process state that a memory snapshot clones, and the kernel's VMGenID
|
|
399
|
+
* reseed does not reach it. `Math.random` is the only random primitive room code has (the
|
|
400
|
+
* sandbox refuses `node:crypto`), so an unseeded clone gives two different projects the same
|
|
401
|
+
* dice. The seed comes from the supervisor's clone-safe entropy mixer via `workerData`, so
|
|
402
|
+
* every worker — cold boot or restore, same VM or sibling clones — rolls its own.
|
|
403
|
+
*/
|
|
404
|
+
declare function seedMathRandom(seedHex: string): void;
|
|
371
405
|
|
|
372
|
-
export { type FromWorker, type MigrationChainStep, type ToWorker, type WorkerState, createWorkerHost, handleMessage, transferList };
|
|
406
|
+
export { type FromWorker, type MigrationChainStep, type ToWorker, type WorkerState, createWorkerHost, handleMessage, seedMathRandom, transferList };
|
package/dist/worker/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
migrateSnapshot
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-YWDRE77H.js";
|
|
4
4
|
import {
|
|
5
5
|
RoomCore,
|
|
6
6
|
RoomFullError,
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
initRapier2d,
|
|
10
10
|
onFirstRapierStep,
|
|
11
11
|
rapierHasStepped
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-LT46QFLW.js";
|
|
13
13
|
|
|
14
14
|
// src/worker/index.ts
|
|
15
15
|
import { getHeapStatistics } from "v8";
|
|
16
|
-
import { isMainThread, parentPort } from "worker_threads";
|
|
16
|
+
import { isMainThread, parentPort, workerData } from "worker_threads";
|
|
17
17
|
import { isRoomDefinition } from "@irtio/server";
|
|
18
18
|
|
|
19
19
|
// src/worker/protocol.ts
|
|
@@ -352,6 +352,11 @@ async function handleMessage(state, host, post, msg) {
|
|
|
352
352
|
state.core?.fireAlarm(msg.name);
|
|
353
353
|
return;
|
|
354
354
|
}
|
|
355
|
+
case "platformNotice": {
|
|
356
|
+
const broadcast = state.core?.deliverPlatformNotice(msg.notice) ?? true;
|
|
357
|
+
post({ t: "platformNoticeResult", reqId: msg.reqId, broadcast });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
355
360
|
case "busEvent": {
|
|
356
361
|
state.core?.deliverBusEvent(msg.channel, msg.from, msg.payload);
|
|
357
362
|
return;
|
|
@@ -390,7 +395,32 @@ async function handleMessage(state, host, post, msg) {
|
|
|
390
395
|
});
|
|
391
396
|
}
|
|
392
397
|
}
|
|
398
|
+
function seedMathRandom(seedHex) {
|
|
399
|
+
if (!/^[0-9a-f]{32}$/.test(seedHex)) return;
|
|
400
|
+
const u32 = (i) => Number.parseInt(seedHex.slice(i * 8, i * 8 + 8), 16) >>> 0;
|
|
401
|
+
let a = u32(0);
|
|
402
|
+
let b = u32(1);
|
|
403
|
+
let c = u32(2);
|
|
404
|
+
let d = u32(3);
|
|
405
|
+
const sfc32 = () => {
|
|
406
|
+
a >>>= 0;
|
|
407
|
+
b >>>= 0;
|
|
408
|
+
c >>>= 0;
|
|
409
|
+
d >>>= 0;
|
|
410
|
+
const t = a + b + d >>> 0;
|
|
411
|
+
d = d + 1 >>> 0;
|
|
412
|
+
a = b ^ b >>> 9;
|
|
413
|
+
b = c + (c << 3) >>> 0;
|
|
414
|
+
c = (c << 21 | c >>> 11) >>> 0;
|
|
415
|
+
c = c + t >>> 0;
|
|
416
|
+
return t / 4294967296;
|
|
417
|
+
};
|
|
418
|
+
for (let i = 0; i < 12; i++) sfc32();
|
|
419
|
+
Math.random = sfc32;
|
|
420
|
+
}
|
|
393
421
|
if (!isMainThread && parentPort) {
|
|
422
|
+
const seed = workerData?.mathRandomSeed;
|
|
423
|
+
if (typeof seed === "string") seedMathRandom(seed);
|
|
394
424
|
const port = parentPort;
|
|
395
425
|
const post = (msg, transfer) => {
|
|
396
426
|
port.postMessage(msg, transfer ?? transferList(msg));
|
|
@@ -404,5 +434,6 @@ if (!isMainThread && parentPort) {
|
|
|
404
434
|
export {
|
|
405
435
|
createWorkerHost,
|
|
406
436
|
handleMessage,
|
|
437
|
+
seedMathRandom,
|
|
407
438
|
transferList
|
|
408
439
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "irtio room runtime: RoomCore, worker_threads host, in-process test harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@dimforge/rapier2d-compat": "0.20.0",
|
|
32
32
|
"@dimforge/rapier3d-compat": "0.20.0",
|
|
33
33
|
"matter-js": "0.20.0",
|
|
34
|
-
"@irtio/protocol": "0.
|
|
35
|
-
"@irtio/schema": "0.
|
|
36
|
-
"@irtio/server": "0.
|
|
34
|
+
"@irtio/protocol": "1.0.0",
|
|
35
|
+
"@irtio/schema": "1.0.0",
|
|
36
|
+
"@irtio/server": "1.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/matter-js": "0.20.2"
|