@irtio/runtime 0.11.0 → 3.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.
@@ -1,8 +1,82 @@
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
+ /**
7
+ * M7 pf4: the per-room RPC trace ring.
8
+ *
9
+ * One entry per client→server RPC: who called, what they called, the two clocks the call was
10
+ * judged against (`ctx.tick` and `ctx.clientTick`), how it ended, and a truncated summary of what
11
+ * came back. It is a **separate** store from `EventRing`, on purpose. The event ring is always on
12
+ * and 100 entries wide, and it is the thing every deployed room pays for; making it rich enough to
13
+ * answer "why did this shot produce no tracer" would make every production room pay for a
14
+ * development question. So this ring is armed — `irtio dev --trace-rpc`, `SupervisorConfig.traceRpc`
15
+ * — and a room nobody armed never constructs one. `RoomCore.recordRpc` is a call into an
16
+ * `undefined`, which is the same cost `captureTimeline` pays when the recorder is unarmed.
17
+ *
18
+ * The stamp-lag table (ask b) rides here rather than in a second structure: the last traced call
19
+ * per client is kept in its own map, so a client whose entry has already been evicted from the ring
20
+ * still reports the lag its last call had. That is the whole reason the ask says "rides on whatever
21
+ * the RPC trace stores" — the server keeps no per-session stamp record otherwise.
22
+ */
23
+ /** Entries kept per room. Bounded because a dev session left running overnight is a real thing. */
24
+ declare const RPC_TRACE_SIZE = 200;
25
+ /**
26
+ * Longest result summary kept, in UTF-16 code units of JSON. A summary is a diagnostic, not a
27
+ * record: an RPC returning a 10 kB payload should cost the trace a line, not 10 kB × 200.
28
+ */
29
+ declare const RPC_TRACE_SUMMARY_MAX = 200;
30
+ /**
31
+ * How a call ended. `deny:<reason>` is `ctx.deny(reason)` — an application refusal, which is the
32
+ * distinction the whole ask exists for: a refusal is not an error, and a trace that spelled both
33
+ * `error` would answer the question with the answer that lost the evening.
34
+ */
35
+ type RpcOutcome = 'ok' | 'error' | `deny:${string}`;
36
+ interface RpcTraceEntry {
37
+ /** Monotonic within a room, so a reader can tell "no new calls" from "the ring rolled". */
38
+ readonly seq: number;
39
+ readonly clientId: string;
40
+ readonly rpc: string;
41
+ /** The server tick the call was applied at (`ctx.tick`). */
42
+ readonly tick: number;
43
+ /** The caller's own stamp (`ctx.clientTick`), absent when the client sent none. */
44
+ readonly clientTick?: number;
45
+ readonly outcome: RpcOutcome;
46
+ /** JSON-ish rendering of the reply, truncated at {@link RPC_TRACE_SUMMARY_MAX}. */
47
+ readonly result?: string;
48
+ }
49
+ /** One client's last traced call — what the inspector's stamp-lag column reads. */
50
+ interface StampLag {
51
+ readonly tick: number;
52
+ readonly clientTick: number | undefined;
53
+ /** `ctx.tick - ctx.clientTick`, or `undefined` when the call carried no stamp. */
54
+ readonly lag: number | undefined;
55
+ readonly rpc: string;
56
+ readonly seq: number;
57
+ }
58
+ /** Renders a reply for the trace: JSON, truncated, and never able to throw. */
59
+ declare function summarize(value: unknown): string | undefined;
60
+ declare class RpcTraceRing {
61
+ private readonly items;
62
+ private readonly lastByClient;
63
+ private seq;
64
+ push(entry: Omit<RpcTraceEntry, 'seq'>): RpcTraceEntry;
65
+ list(): readonly RpcTraceEntry[];
66
+ /** Client id → its last traced call. Survives the entry itself being evicted from the ring. */
67
+ stampLag(): ReadonlyMap<string, StampLag>;
68
+ /** A client that left takes its stamp row with it; the ring entries stay. */
69
+ forget(clientId: string): void;
70
+ /** JSON shape served by `/__irt/rpc-trace.json` and carried on `inspect`. */
71
+ dump(): RpcTraceDump;
72
+ }
73
+ interface RpcTraceDump {
74
+ readonly entries: readonly RpcTraceEntry[];
75
+ readonly stampLag: readonly ({
76
+ readonly clientId: string;
77
+ } & StampLag)[];
78
+ }
79
+
6
80
  /**
7
81
  * D41: the recorded authoritative timeline.
8
82
  *
@@ -84,7 +158,9 @@ declare class TimelineRecorder {
84
158
 
85
159
  type RoomEventKind = 'join' | 'leave' | 'write' | 'write-rejected' | 'correct' | 'call' | 'reply' | 'msg' | 'alarm'
86
160
  /** D59: a bus event (a published message) or a directed bus send being delivered. */
87
- | 'bus' | 'error';
161
+ | 'bus'
162
+ /** M7 D-P2: a platform notice reaching `onPlatformNotice`; the detail is the phase. */
163
+ | 'notice' | 'error';
88
164
  interface RoomEvent {
89
165
  readonly tick: number;
90
166
  readonly kind: RoomEventKind;
@@ -267,10 +343,42 @@ type HostCall = {
267
343
  * number, but that bound lives on the side of the seam the tenant's own code owns.
268
344
  *
269
345
  * The result's `value` is the minted clip id.
346
+ *
347
+ * M6 clip-TTL packet: `expiresAtEpochS` is the unix second this clip should die, computed here
348
+ * because this is where `replay.ttl` is declared and read. The supervisor clamps it into
349
+ * `[now + 1m, now + 3650d]` before minting an id from it, so choosing it inside the tenant's
350
+ * own worker buys a hostile room nothing an honest one cannot declare.
270
351
  */
271
352
  | {
272
353
  readonly kind: 'clip';
273
354
  readonly bytes: Uint8Array;
355
+ readonly expiresAtEpochS: number;
356
+ }
357
+ /**
358
+ * D81's `record: true`: one numbered chunk of a continuous recording, on its way to storage.
359
+ *
360
+ * The chunk is the ring's own evictions, kept — so this call carries bulk bytes for the same
361
+ * reason `clip` does, is walled on the far side for the same reason, and is the only other host
362
+ * call that does either.
363
+ *
364
+ * `recId` is `undefined` on the first chunk of a recording and the supervisor's reply carries
365
+ * the id it minted; every chunk after it names that id. The mint is on the supervisor's side
366
+ * because that is where the entropy and the key algebra live (the `clip` precedent), and the
367
+ * runtime never invents one — which is also what keeps two rooms recording in one project from
368
+ * ever sharing a prefix.
369
+ *
370
+ * M6 clip-TTL packet: `expiresAtEpochS` is the expiry computed when the recording STARTED, and
371
+ * every chunk carries that same number for the life of the recording. It only decides anything
372
+ * on the first chunk — the mint — because every chunk after it is addressed by the `recId` the
373
+ * mint produced, which is what makes a 40-minute match expire atomically instead of losing its
374
+ * opening while its ending is still being watched.
375
+ */
376
+ | {
377
+ readonly kind: 'record-chunk';
378
+ readonly recId: string | undefined;
379
+ readonly seq: number;
380
+ readonly bytes: Uint8Array;
381
+ readonly expiresAtEpochS: number;
274
382
  };
275
383
  /**
276
384
  * What a `HostCall` produced. `value` carries the save id for `save` and the stored string for
@@ -395,6 +503,14 @@ interface RoomCoreOptions {
395
503
  * `testRoom({ profile: true })` are what turn it on.
396
504
  */
397
505
  readonly profile?: boolean;
506
+ /**
507
+ * M7 pf4: keep a bounded RPC trace ring for this room. Off by default and free when off — a
508
+ * room nobody armed constructs no ring and `recordRpc` is a call into an `undefined`, exactly
509
+ * as `captureTimeline` is when the D41 recorder is unarmed. `irtio dev --trace-rpc`,
510
+ * `SupervisorConfig.traceRpc` and `testRoom({ traceRpc: true })` are what turn it on; nothing
511
+ * in a deployed tenant does.
512
+ */
513
+ readonly traceRpc?: boolean;
398
514
  /**
399
515
  * D77 / O28: the write core's caps — encoded state bytes, records per collection, writes per
400
516
  * client per second. A host that sets none gets the record cap and nothing else, which is what
@@ -426,6 +542,16 @@ interface JoinOptions {
426
542
  readonly playerId?: string;
427
543
  /** D44: this join is a scripted NPC's loopback session. */
428
544
  readonly npc?: boolean;
545
+ /**
546
+ * The **verified** identity subject behind this join, or `undefined` when there is none.
547
+ *
548
+ * Deliberately not `playerId`. `playerId` always has a value — it falls back to the client id —
549
+ * and that fallback is exactly what a moderation decision must not be fooled by: a key join's
550
+ * `playerId` is a resume token's client id, which a returning stranger simply mints again. This
551
+ * is the narrower fact: present only when a credential was verified, absent for a key join, and
552
+ * therefore the only thing `room.ban` can key a cooldown on honestly.
553
+ */
554
+ readonly subject?: string;
429
555
  }
430
556
  interface JoinResult {
431
557
  readonly tick: number;
@@ -438,6 +564,29 @@ declare class RoomFullError extends Error {
438
564
  readonly name = "RoomFullError";
439
565
  constructor(maxClients: number);
440
566
  }
567
+ /**
568
+ * M8 lane 5: this join was refused before it had a seat.
569
+ *
570
+ * Thrown by `RoomCore.join` for all four of the room's own refusals — a subject inside a
571
+ * `room.ban` cooldown, an `onAdmit` that called `ctx.deny`, an `onAdmit` that never answered
572
+ * (`admission timed out`), and an `onAdmit` that threw (`admission check failed`) — and mapped to
573
+ * `E_ADMISSION` at the
574
+ * worker boundary, exactly where `RoomFullError` becomes `E_ROOM_FULL`. Its own class rather than
575
+ * a `DenyError` reaching the host, so the host can tell a refusal it should answer with a typed
576
+ * error from a handler that broke.
577
+ */
578
+ declare class AdmissionError extends Error {
579
+ readonly reason: string;
580
+ readonly name = "AdmissionError";
581
+ constructor(reason: string);
582
+ }
583
+ /**
584
+ * M7 pf3 (f): the smoothing on `RoomStats.physicsStepMsAvg`.
585
+ *
586
+ * 0.1 is roughly a ten-tick window: half a second at 20 Hz, which is short enough to show a pile
587
+ * of crates settling and long enough that one preempted step does not read as a regression.
588
+ */
589
+ declare const PHYSICS_STEP_EMA_ALPHA = 0.1;
441
590
  interface RoomStats {
442
591
  ticks: number;
443
592
  lastTickMs: number;
@@ -454,6 +603,22 @@ interface RoomStats {
454
603
  gridBuildMs: number;
455
604
  gridQueryMs: number;
456
605
  aoiEncodeMs: number;
606
+ /**
607
+ * Wall milliseconds the last world step took, measured around the engine's own `step()` and
608
+ * nothing else: not `reconcile`, not `sync`, not the room's `tick()` handler. `0` in a room
609
+ * with no physics, and `0` until the first step.
610
+ *
611
+ * Deliberately narrow. `lastTickMs` already covers the whole tick, and the question this
612
+ * answers — "is the engine the thing that is slow?" — is the one a room cannot answer by
613
+ * subtracting numbers it does not have.
614
+ */
615
+ physicsStepMs: number;
616
+ /**
617
+ * An exponential moving average of {@link physicsStepMs}, alpha {@link PHYSICS_STEP_EMA_ALPHA}.
618
+ * A single step's timing is mostly noise on a shared machine; this is the number to compare
619
+ * against a budget or to plot.
620
+ */
621
+ physicsStepMsAvg: number;
457
622
  visibleIdsTotal: number;
458
623
  membershipEnters: number;
459
624
  membershipLeaves: number;
@@ -496,6 +661,14 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
496
661
  receive(clientId: string, frame: Uint8Array): void;
497
662
  /** Full state + tick + rng for hibernation. Runs `onSleep`. Clears timers. */
498
663
  serialize(): Uint8Array;
664
+ /**
665
+ * The earliest pending room-timer deadline the last `serialize()` cleared, on `host.now()`'s
666
+ * clock, or `undefined` if there was none. The worker translates it to wall clock and hands it
667
+ * to the supervisor, which wakes the room at it — a `room.setTimeout` loses its callback to a
668
+ * hibernation but no longer loses the room's chance to act on the deadline. `undefined` until
669
+ * the first `serialize()`, and never touched by `snapshot()`.
670
+ */
671
+ readonly lastSerializeTimerAt: number | undefined;
499
672
  /**
500
673
  * D24: the same bytes `serialize()` produces, with none of its side effects — the room carries
501
674
  * on exactly as it was. This is what a save generation is written from; `serialize()` is what
@@ -519,6 +692,12 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
519
692
  recording(): TimelineDump | undefined;
520
693
  /** D41: capture one tick. Called by the loop; a no-op while the recorder is unarmed. */
521
694
  captureTimeline(): void;
695
+ /**
696
+ * M7 pf4: the RPC trace so far, or `undefined` when this room was not started with
697
+ * `traceRpc: true`. Like `profile()`, `undefined` and "empty" are different readings: a room
698
+ * nobody armed and a room nobody called must not look the same.
699
+ */
700
+ rpcTrace(): RpcTraceDump | undefined;
522
701
  /**
523
702
  * Week 12: settles the promise `hostCall(reqId, …)` returned. The host calls this from its own
524
703
  * turn; the runtime runs the continuation as a discrete event and flushes afterwards, so state
@@ -533,6 +712,12 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
533
712
  fireAlarm(name: string): void;
534
713
  /** D59: deliver one published message on a channel this room is subscribed to. */
535
714
  deliverBusEvent(channel: string, from: string, payload: string): void;
715
+ /**
716
+ * M7 D-P2: deliver a platform notice to `onPlatformNotice` and answer whether the supervisor
717
+ * should still broadcast it. `false` means the room vetoed; a throw, a missing handler and a
718
+ * stopped room all answer `true`.
719
+ */
720
+ deliverPlatformNotice(notice: PlatformNotice): boolean;
536
721
  /** D59: deliver one directed `room.bus.send`. At-least-once, so this may repeat a message. */
537
722
  deliverBusMessage(from: string, payload: string): void;
538
723
  /**
@@ -543,6 +728,8 @@ interface RoomCoreApi<S extends AnySchema = AnySchema> {
543
728
  stateBytes(): number | undefined;
544
729
  /** D77: how many records this room is holding for players who have left. */
545
730
  holdCount(): number;
731
+ /** How many `transfer: 'ask'` requests are waiting on an owner's answer. Never survives sleep. */
732
+ askCount(): number;
546
733
  }
547
734
 
548
- 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 };
735
+ export { AdmissionError as A, summarize as B, 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, PHYSICS_STEP_EMA_ALPHA as P, RELAY_RECORDS_PER_COLLECTION as R, type StampLag as S, 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, RPC_TRACE_SIZE as i, RPC_TRACE_SUMMARY_MAX as j, type RoomCoreApi as k, type RoomCoreOptions as l, type RoomEvent as m, type RoomEventKind as n, RoomFullError as o, type RoomHost as p, type RoomInspection as q, type RoomStats as r, type RpcOutcome as s, type RpcTraceDump as t, type RpcTraceEntry as u, RpcTraceRing as v, type TimelineFrame as w, TimelineRecorder as x, type TimelineRecorderOptions as y, inspectState as z };
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-Hk4SkDtW.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-0FZBGyke.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-BoCNAPZT.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-CGbiPxi3.js';
3
3
  import { CollectionDesc, AnySchema, PlainState, DirtySet } from '@irtio/schema';
4
4
  import '@irtio/protocol';
5
5
  import '@irtio/server';
@@ -28,7 +28,10 @@ declare const RPC_TIMEOUT_MS = 5000;
28
28
  * adds and removes, so a client never learns such a record exists.
29
29
  */
30
30
 
31
- /** Is `c` visible to `role`? */
31
+ /**
32
+ * Is `c` visible to `role`? A `visibility: 'server'` collection is visible to none — the same
33
+ * answer `visibility: 'role'` with an empty `roles` list has always given, said on purpose.
34
+ */
32
35
  declare function isVisible(c: CollectionDesc, role: string): boolean;
33
36
  /** A `SnapshotOptions.collections` predicate for one role. */
34
37
  declare function visibleTo(role: string): (c: CollectionDesc) => boolean;
package/dist/index.js CHANGED
@@ -2,8 +2,9 @@ import {
2
2
  fromMigrationState,
3
3
  migrateSnapshot,
4
4
  toMigrationState
5
- } from "./chunk-ACC3SDIO.js";
5
+ } from "./chunk-3BCTP4YS.js";
6
6
  import {
7
+ AdmissionError,
7
8
  CRASH_AFTER_THROWS,
8
9
  DEFAULT_RECORDS_PER_COLLECTION,
9
10
  DEFAULT_TIMELINE_MAX_RECORDS,
@@ -12,14 +13,18 @@ import {
12
13
  HOST_CALL_TIMEOUT_MS,
13
14
  MAX_CATCHUP,
14
15
  Mulberry32,
16
+ PHYSICS_STEP_EMA_ALPHA,
15
17
  READABLE_SNAPSHOT_VERSIONS,
16
18
  RELAY_RECORDS_PER_COLLECTION,
17
19
  RELAY_STATE_BYTES,
18
20
  RELAY_WRITES_PER_SECOND,
19
21
  RELAY_WRITE_CAPS,
20
22
  RPC_TIMEOUT_MS,
23
+ RPC_TRACE_SIZE,
24
+ RPC_TRACE_SUMMARY_MAX,
21
25
  RoomCore,
22
26
  RoomFullError,
27
+ RpcTraceRing,
23
28
  SNAPSHOT_FORMAT_VERSION,
24
29
  TimelineRecorder,
25
30
  catchUpDirty,
@@ -48,12 +53,14 @@ import {
48
53
  resetMatterForTests,
49
54
  resetPhysicsForTests,
50
55
  resetRapier2dForTests,
56
+ summarize,
51
57
  viewKeyFor,
52
58
  visibleNames,
53
59
  visibleTo,
54
60
  writeHibernationBlob
55
- } from "./chunk-GYPUNTQN.js";
61
+ } from "./chunk-NPRIYYNM.js";
56
62
  export {
63
+ AdmissionError,
57
64
  CRASH_AFTER_THROWS,
58
65
  DEFAULT_RECORDS_PER_COLLECTION,
59
66
  DEFAULT_TIMELINE_MAX_RECORDS,
@@ -62,14 +69,18 @@ export {
62
69
  HOST_CALL_TIMEOUT_MS,
63
70
  MAX_CATCHUP,
64
71
  Mulberry32,
72
+ PHYSICS_STEP_EMA_ALPHA,
65
73
  READABLE_SNAPSHOT_VERSIONS,
66
74
  RELAY_RECORDS_PER_COLLECTION,
67
75
  RELAY_STATE_BYTES,
68
76
  RELAY_WRITES_PER_SECOND,
69
77
  RELAY_WRITE_CAPS,
70
78
  RPC_TIMEOUT_MS,
79
+ RPC_TRACE_SIZE,
80
+ RPC_TRACE_SUMMARY_MAX,
71
81
  RoomCore,
72
82
  RoomFullError,
83
+ RpcTraceRing,
73
84
  SNAPSHOT_FORMAT_VERSION,
74
85
  TimelineRecorder,
75
86
  catchUpDirty,
@@ -100,6 +111,7 @@ export {
100
111
  resetMatterForTests,
101
112
  resetPhysicsForTests,
102
113
  resetRapier2dForTests,
114
+ summarize as summarizeRpcResult,
103
115
  toMigrationState,
104
116
  viewKeyFor,
105
117
  visibleNames,