@irtio/runtime 4.4.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2478,8 +2478,9 @@ var Loop = class {
2478
2478
  * Whether this idle stretch has already spent its one deferral for a nearly-due room timer.
2479
2479
  *
2480
2480
  * Once, deliberately. A `setInterval(100)` in an otherwise silent room must not be able to pin
2481
- * the room resident forever — pending timers are explicitly *not* a reason to stay awake, the
2482
- * supervisor wakes the room at the due time instead. Reset by real activity.
2481
+ * the room resident forever — pending timers are explicitly *not* a reason to stay awake. A
2482
+ * one-shot deadline is handed to the supervisor, which wakes the room at it; an interval is
2483
+ * simply dropped, with a warning (bugs.md #142). Reset by real activity.
2483
2484
  */
2484
2485
  timerDeferred = false;
2485
2486
  get intervalMs() {
@@ -2750,22 +2751,51 @@ var Loop = class {
2750
2751
  }
2751
2752
  /**
2752
2753
  * The earliest pending room timer's due time on `host.now()`'s clock, or `undefined` when the
2753
- * room has none armed.
2754
+ * room has none armed. Every timer counts here, repeating ones included, because this is what
2755
+ * the idle check reads to decide whether a firing is close enough to defer the sleep past —
2756
+ * and a firing milliseconds away is worth waiting out whatever kind of timer produced it.
2754
2757
  *
2755
- * Read at `serialize()`, before `clearAllTimers()` drops the callbacks. The callbacks are
2756
- * closures and cannot be persisted — but the *time* can, and handing it to the supervisor is
2757
- * what turns "your timer is gone" into "your room is awake again by then", which is a contract
2758
- * room code can actually catch up against in `onWake`.
2758
+ * What the *departure* reports to the supervisor is a narrower question with a different
2759
+ * answer: see `departureTimers`.
2759
2760
  *
2760
2761
  * Event mode reads the absolute due time stamped on the record at arm time; tick mode derives
2761
2762
  * it from `dueTick`, because there a timer fires on the first tick at or after its deadline and
2762
2763
  * the tick counter is the clock that decides it.
2763
2764
  */
2764
2765
  earliestTimerAt() {
2766
+ return this.earliestAt(() => true);
2767
+ }
2768
+ /**
2769
+ * What `serialize()` hands the supervisor: the earliest **one-shot** deadline the departure is
2770
+ * about to clear, and how many *repeating* timers went with it.
2771
+ *
2772
+ * Read before `clearAllTimers()` drops the callbacks. The callbacks are closures and cannot be
2773
+ * persisted — but the *time* can, and handing it to the supervisor is what turns "your timer is
2774
+ * gone" into "your room is awake again by then", which is a contract room code can actually
2775
+ * catch up against in `onWake`.
2776
+ *
2777
+ * bugs.md #142: that contract is a statement about a **deadline**, and only a `setTimeout` has
2778
+ * one. A `setInterval` has a cadence, so its next due time is one period away no matter when
2779
+ * you ask — including one period after the room hibernates. Arming the wake for it made an
2780
+ * interval mean "never let this room stay asleep": the room slept, the wake brought it back
2781
+ * milliseconds or one period later, `onWake` re-armed the interval as the docs teach, and the
2782
+ * next departure armed the wake again, forever, at a full snapshot-terminate-spawn per cycle.
2783
+ * An explicit `room.sleep()` could not stick at all. So a repeating timer no longer arms the
2784
+ * wake — the room sleeps until real activity, an alarm, or a one-shot deadline it also holds —
2785
+ * and `serialize()` says so out loud, because the alternative to a dropped interval is a
2786
+ * durable alarm and a room author cannot choose it without being told.
2787
+ */
2788
+ departureTimers() {
2789
+ let repeating = 0;
2790
+ for (const t of this.timers.values()) if (t.repeat) repeating++;
2791
+ return { at: this.earliestAt((t) => !t.repeat), repeating };
2792
+ }
2793
+ earliestAt(want) {
2765
2794
  if (this.timers.size === 0) return void 0;
2766
2795
  const now = this.core.host.now();
2767
2796
  let best;
2768
2797
  for (const t of this.timers.values()) {
2798
+ if (!want(t)) continue;
2769
2799
  const at = this.core.mode === "event" ? t.dueAtMs : now + Math.max(0, t.dueTick - this.core.tick) * this.intervalMs;
2770
2800
  if (best === void 0 || at < best) best = at;
2771
2801
  }
@@ -7099,8 +7129,8 @@ var RoomCore = class _RoomCore {
7099
7129
  );
7100
7130
  }
7101
7131
  /**
7102
- * The earliest pending room timer's due time at the last `serialize()`, on `host.now()`'s
7103
- * clock, or `undefined` if nothing was armed.
7132
+ * The earliest pending **one-shot** timer deadline at the last `serialize()`, on `host.now()`'s
7133
+ * clock, or `undefined` if no `room.setTimeout` was armed.
7104
7134
  *
7105
7135
  * `serialize()` is the departure, and it drops every room timer because a callback is a closure
7106
7136
  * and there is nothing to persist. The *deadline* is persistable, though, and this is where the
@@ -7108,13 +7138,26 @@ var RoomCore = class _RoomCore {
7108
7138
  * gets: your callback does not survive hibernation, but your room is awake again at or shortly
7109
7139
  * after the time it would have fired — so keep the deadline in state and catch up in `onWake`.
7110
7140
  *
7141
+ * A repeating timer is deliberately not in here: it has a cadence rather than a deadline, and a
7142
+ * wake armed for it can only ever hand the room back its own next period (bugs.md #142, and
7143
+ * `Loop.departureTimers` for the whole argument). The warning below is what a room holding one
7144
+ * gets instead.
7145
+ *
7111
7146
  * Captured before `clearAllTimers()`, which is the only ordering that could ever have worked.
7112
7147
  */
7113
7148
  lastSerializeTimerAt;
7114
7149
  serialize() {
7115
7150
  const onSleep = this.definition.config.onSleep;
7116
7151
  if (onSleep) this.guard("onSleep", () => onSleep(this.state, this.room));
7117
- this.lastSerializeTimerAt = this.loop.earliestTimerAt();
7152
+ const departing = this.loop.departureTimers();
7153
+ this.lastSerializeTimerAt = departing.at;
7154
+ if (departing.repeating > 0) {
7155
+ const n = departing.repeating;
7156
+ this.log(
7157
+ "warn",
7158
+ `going to sleep with ${n} repeating room timer${n === 1 ? "" : "s"} armed (room.setInterval). The callback does not survive hibernation and an interval does not wake a sleeping room: re-arm it in onWake if the room only needs it while it is up, or use a durable alarm for a cadence that has to fire whether or not the room is awake. room.setTimeout is unaffected \u2014 a pending one still wakes the room at its deadline.`
7159
+ );
7160
+ }
7118
7161
  this.loop.clearAllTimers();
7119
7162
  cancelAllAsks(this.internals);
7120
7163
  rejectAllPending(this.internals, "room is hibernating");
@@ -3,7 +3,7 @@ import {
3
3
  parseHibernationBlob,
4
4
  sameSchemaHash,
5
5
  writeHibernationBlob
6
- } from "./chunk-JFC5BQ2N.js";
6
+ } from "./chunk-23TKVLTX.js";
7
7
 
8
8
  // src/migrate.ts
9
9
  import { PRESENCE_COLLECTION, withBuiltins } from "@irtio/protocol";
@@ -682,11 +682,13 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
682
682
  /** Full state + tick + rng for hibernation. Runs `onSleep`. Clears timers. */
683
683
  serialize(): Uint8Array;
684
684
  /**
685
- * The earliest pending room-timer deadline the last `serialize()` cleared, on `host.now()`'s
686
- * clock, or `undefined` if there was none. The worker translates it to wall clock and hands it
687
- * to the supervisor, which wakes the room at it — a `room.setTimeout` loses its callback to a
688
- * hibernation but no longer loses the room's chance to act on the deadline. `undefined` until
689
- * the first `serialize()`, and never touched by `snapshot()`.
685
+ * The earliest pending **one-shot** room-timer deadline the last `serialize()` cleared, on
686
+ * `host.now()`'s clock, or `undefined` if there was none. The worker translates it to wall
687
+ * clock and hands it to the supervisor, which wakes the room at it — a `room.setTimeout` loses
688
+ * its callback to a hibernation but no longer loses the room's chance to act on the deadline.
689
+ * A `room.setInterval` is not a deadline and is deliberately not reported (bugs.md #142); the
690
+ * departure logs a warning for one instead. `undefined` until the first `serialize()`, and
691
+ * never touched by `snapshot()`.
690
692
  */
691
693
  readonly lastSerializeTimerAt: number | undefined;
692
694
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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-BYhsLgQV.js';
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-B72MNcCa.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-DY5J0VZR.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-GK53BSWK.js";
5
+ } from "./chunk-VIN2SPZT.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-JFC5BQ2N.js";
62
+ } from "./chunk-23TKVLTX.js";
63
63
  export {
64
64
  AdmissionError,
65
65
  CRASH_AFTER_THROWS,
@@ -1,7 +1,7 @@
1
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, CustomStepper, CustomPose, MatterModule, MatterEngine, MatterBody, RapierModule, RapierWorld, RapierRigidBody, Rapier2dModule, Rapier2dWorld, Rapier2dRigidBody, ResolvedRoomConfig, LeaveReason } from '@irtio/server';
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';
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-B72MNcCa.js';
5
5
 
6
6
  declare class Mulberry32 {
7
7
  /** Current internal state (u32). Survives hibernation. */
@@ -193,10 +193,19 @@ interface LoopApi {
193
193
  clearAllTimers(): void;
194
194
  /**
195
195
  * The earliest pending room timer's due time on `host.now()`'s clock, or `undefined` when
196
- * nothing is armed. Read at `serialize()` — before the timers are cleared — so the supervisor
197
- * can guarantee the room is awake again by then. See `Loop.earliestTimerAt`.
196
+ * nothing is armed. The idle check's read, so every timer counts. See `Loop.earliestTimerAt`.
198
197
  */
199
198
  earliestTimerAt(): number | undefined;
199
+ /**
200
+ * The departure's read, taken at `serialize()` before the timers are cleared: the earliest
201
+ * **one-shot** deadline, which the supervisor guarantees the room is awake again by, and the
202
+ * number of repeating timers dropped with no such guarantee (bugs.md #142). See
203
+ * `Loop.departureTimers`.
204
+ */
205
+ departureTimers(): {
206
+ readonly at: number | undefined;
207
+ readonly repeating: number;
208
+ };
200
209
  /** Internal one-shot on the host clock (RPC timeouts in event mode). */
201
210
  after(ms: number, fn: () => void): void;
202
211
  }
@@ -420,8 +429,9 @@ declare class Loop implements LoopApi {
420
429
  * Whether this idle stretch has already spent its one deferral for a nearly-due room timer.
421
430
  *
422
431
  * Once, deliberately. A `setInterval(100)` in an otherwise silent room must not be able to pin
423
- * the room resident forever — pending timers are explicitly *not* a reason to stay awake, the
424
- * supervisor wakes the room at the due time instead. Reset by real activity.
432
+ * the room resident forever — pending timers are explicitly *not* a reason to stay awake. A
433
+ * one-shot deadline is handed to the supervisor, which wakes the room at it; an interval is
434
+ * simply dropped, with a warning (bugs.md #142). Reset by real activity.
425
435
  */
426
436
  private timerDeferred;
427
437
  constructor(core: RoomInternals);
@@ -468,18 +478,43 @@ declare class Loop implements LoopApi {
468
478
  clearTimer(handle: number): void;
469
479
  /**
470
480
  * The earliest pending room timer's due time on `host.now()`'s clock, or `undefined` when the
471
- * room has none armed.
481
+ * room has none armed. Every timer counts here, repeating ones included, because this is what
482
+ * the idle check reads to decide whether a firing is close enough to defer the sleep past —
483
+ * and a firing milliseconds away is worth waiting out whatever kind of timer produced it.
472
484
  *
473
- * Read at `serialize()`, before `clearAllTimers()` drops the callbacks. The callbacks are
474
- * closures and cannot be persisted — but the *time* can, and handing it to the supervisor is
475
- * what turns "your timer is gone" into "your room is awake again by then", which is a contract
476
- * room code can actually catch up against in `onWake`.
485
+ * What the *departure* reports to the supervisor is a narrower question with a different
486
+ * answer: see `departureTimers`.
477
487
  *
478
488
  * Event mode reads the absolute due time stamped on the record at arm time; tick mode derives
479
489
  * it from `dueTick`, because there a timer fires on the first tick at or after its deadline and
480
490
  * the tick counter is the clock that decides it.
481
491
  */
482
492
  earliestTimerAt(): number | undefined;
493
+ /**
494
+ * What `serialize()` hands the supervisor: the earliest **one-shot** deadline the departure is
495
+ * about to clear, and how many *repeating* timers went with it.
496
+ *
497
+ * Read before `clearAllTimers()` drops the callbacks. The callbacks are closures and cannot be
498
+ * persisted — but the *time* can, and handing it to the supervisor is what turns "your timer is
499
+ * gone" into "your room is awake again by then", which is a contract room code can actually
500
+ * catch up against in `onWake`.
501
+ *
502
+ * bugs.md #142: that contract is a statement about a **deadline**, and only a `setTimeout` has
503
+ * one. A `setInterval` has a cadence, so its next due time is one period away no matter when
504
+ * you ask — including one period after the room hibernates. Arming the wake for it made an
505
+ * interval mean "never let this room stay asleep": the room slept, the wake brought it back
506
+ * milliseconds or one period later, `onWake` re-armed the interval as the docs teach, and the
507
+ * next departure armed the wake again, forever, at a full snapshot-terminate-spawn per cycle.
508
+ * An explicit `room.sleep()` could not stick at all. So a repeating timer no longer arms the
509
+ * wake — the room sleeps until real activity, an alarm, or a one-shot deadline it also holds —
510
+ * and `serialize()` says so out loud, because the alternative to a dropped interval is a
511
+ * durable alarm and a room author cannot choose it without being told.
512
+ */
513
+ departureTimers(): {
514
+ readonly at: number | undefined;
515
+ readonly repeating: number;
516
+ };
517
+ private earliestAt;
483
518
  clearAllTimers(): void;
484
519
  after(ms: number, fn: () => void): void;
485
520
  }
@@ -1235,8 +1270,8 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
1235
1270
  */
1236
1271
  snapshot(): Uint8Array;
1237
1272
  /**
1238
- * The earliest pending room timer's due time at the last `serialize()`, on `host.now()`'s
1239
- * clock, or `undefined` if nothing was armed.
1273
+ * The earliest pending **one-shot** timer deadline at the last `serialize()`, on `host.now()`'s
1274
+ * clock, or `undefined` if no `room.setTimeout` was armed.
1240
1275
  *
1241
1276
  * `serialize()` is the departure, and it drops every room timer because a callback is a closure
1242
1277
  * and there is nothing to persist. The *deadline* is persistable, though, and this is where the
@@ -1244,6 +1279,11 @@ declare class RoomCore<S extends AnySchema = AnySchema> implements RoomCoreApi<S
1244
1279
  * gets: your callback does not survive hibernation, but your room is awake again at or shortly
1245
1280
  * after the time it would have fired — so keep the deadline in state and catch up in `onWake`.
1246
1281
  *
1282
+ * A repeating timer is deliberately not in here: it has a cadence rather than a deadline, and a
1283
+ * wake armed for it can only ever hand the room back its own next period (bugs.md #142, and
1284
+ * `Loop.departureTimers` for the whole argument). The warning below is what a room holding one
1285
+ * gets instead.
1286
+ *
1247
1287
  * Captured before `clearAllTimers()`, which is the only ordering that could ever have worked.
1248
1288
  */
1249
1289
  lastSerializeTimerAt: number | undefined;
@@ -1,8 +1,8 @@
1
- import { R as RoomCore } from '../room-BYhsLgQV.js';
2
- export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-BYhsLgQV.js';
1
+ import { R as RoomCore } from '../room-DY5J0VZR.js';
2
+ export { m as initMatter, n as initPhysics, o as initRapier2d } from '../room-DY5J0VZR.js';
3
3
  import { ErrorCodeName, FrameType } from '@irtio/protocol';
4
4
  import { NpcConfig, LeaveReason, Room, RoomDefinition } from '@irtio/server';
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';
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-B72MNcCa.js';
6
6
  import { AnySchema, PlainState, EntityCollection, State } from '@irtio/schema';
7
7
 
8
8
  /**
@@ -6,7 +6,7 @@ import {
6
6
  initRapier2d,
7
7
  isPerClient,
8
8
  visibleNames
9
- } from "../chunk-JFC5BQ2N.js";
9
+ } from "../chunk-23TKVLTX.js";
10
10
 
11
11
  // src/test/clock.ts
12
12
  var FakeClock = class {
@@ -1,4 +1,4 @@
1
- import { L as LogLevel, t as RpcTraceDump, T as TimelineDump, r as RoomStats, c as HostCall, d as HostCallResult, k as RoomCoreApi, p as RoomHost } from '../contract-DHWlURaq.js';
1
+ import { L as LogLevel, t as RpcTraceDump, T as TimelineDump, r as RoomStats, c as HostCall, d as HostCallResult, k as RoomCoreApi, p as RoomHost } from '../contract-B72MNcCa.js';
2
2
  import { ErrorCodeName, ProfileSnapshot, PlatformNotice } from '@irtio/protocol';
3
3
  import { NpcConfig, LeaveReason } from '@irtio/server';
4
4
  import '@irtio/schema';
@@ -230,13 +230,16 @@ type FromWorker = {
230
230
  reqId: number;
231
231
  bytes: Uint8Array;
232
232
  /**
233
- * The earliest pending `room.setTimeout`/`setInterval` deadline the departure serialize
234
- * just cleared, in **wall-clock ms** (`Date.now()`'s frame, translated in the worker the
235
- * same way `setAlarm`'s `atMs` is — the room's own clock is `performance.now()` and means
236
- * nothing to a supervisor that outlives the worker).
233
+ * The earliest pending `room.setTimeout` deadline the departure serialize just cleared, in
234
+ * **wall-clock ms** (`Date.now()`'s frame, translated in the worker the same way
235
+ * `setAlarm`'s `atMs` is — the room's own clock is `performance.now()` and means nothing to
236
+ * a supervisor that outlives the worker).
237
237
  *
238
- * Absent when the room had no timers armed, and always absent for `forSave`, which clears
239
- * nothing and therefore loses nothing. The supervisor arms an internal wake for it so the
238
+ * Absent when the room had no one-shot timer armed, and always absent for `forSave`, which
239
+ * clears nothing and therefore loses nothing. A `room.setInterval` never reports here: it
240
+ * is a cadence rather than a deadline, and a wake armed for one only ever hands the room
241
+ * its own next period, which is how an interval came to mean "this room never stays
242
+ * asleep" (bugs.md #142). The supervisor arms an internal wake for what does report, so the
240
243
  * room is up again by the time its timer would have fired.
241
244
  */
242
245
  nextTimerAt?: number;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  migrateSnapshot
3
- } from "../chunk-GK53BSWK.js";
3
+ } from "../chunk-VIN2SPZT.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-JFC5BQ2N.js";
13
+ } from "../chunk-23TKVLTX.js";
14
14
 
15
15
  // src/worker/index.ts
16
16
  import { getHeapStatistics } from "v8";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irtio/runtime",
3
- "version": "4.4.0",
3
+ "version": "4.5.1",
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.4.0",
40
- "@irtio/schema": "^4.4.0",
41
- "@irtio/server": "^4.4.0"
39
+ "@irtio/protocol": "^4.5.1",
40
+ "@irtio/schema": "^4.5.1",
41
+ "@irtio/server": "^4.5.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/matter-js": "0.20.2"