@sadhaka/loom-engine 0.12.0 → 0.14.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.
Files changed (82) hide show
  1. package/README.md +113 -1
  2. package/dist/components/peer-sprite.d.ts +25 -0
  3. package/dist/components/peer-sprite.d.ts.map +1 -0
  4. package/dist/components/peer-sprite.js +48 -0
  5. package/dist/components/peer-sprite.js.map +1 -0
  6. package/dist/director/ai/ai-plugin-registry.d.ts +20 -0
  7. package/dist/director/ai/ai-plugin-registry.d.ts.map +1 -0
  8. package/dist/director/ai/ai-plugin-registry.js +232 -0
  9. package/dist/director/ai/ai-plugin-registry.js.map +1 -0
  10. package/dist/director/ai/mock-ai-plugin.d.ts +24 -0
  11. package/dist/director/ai/mock-ai-plugin.d.ts.map +1 -0
  12. package/dist/director/ai/mock-ai-plugin.js +83 -0
  13. package/dist/director/ai/mock-ai-plugin.js.map +1 -0
  14. package/dist/director/ai/plugin-context.d.ts +27 -0
  15. package/dist/director/ai/plugin-context.d.ts.map +1 -0
  16. package/dist/director/ai/plugin-context.js +152 -0
  17. package/dist/director/ai/plugin-context.js.map +1 -0
  18. package/dist/director/ai/plugin.d.ts +57 -0
  19. package/dist/director/ai/plugin.d.ts.map +1 -0
  20. package/dist/director/ai/plugin.js +32 -0
  21. package/dist/director/ai/plugin.js.map +1 -0
  22. package/dist/director/index.d.ts +27 -0
  23. package/dist/director/index.d.ts.map +1 -0
  24. package/dist/director/index.js +26 -0
  25. package/dist/director/index.js.map +1 -0
  26. package/dist/director/zone/mock-zone-bridge.d.ts +22 -0
  27. package/dist/director/zone/mock-zone-bridge.d.ts.map +1 -0
  28. package/dist/director/zone/mock-zone-bridge.js +107 -0
  29. package/dist/director/zone/mock-zone-bridge.js.map +1 -0
  30. package/dist/director/zone/sse-zone-bridge.d.ts +40 -0
  31. package/dist/director/zone/sse-zone-bridge.d.ts.map +1 -0
  32. package/dist/director/zone/sse-zone-bridge.js +164 -0
  33. package/dist/director/zone/sse-zone-bridge.js.map +1 -0
  34. package/dist/director/zone/zone-event-bridge.d.ts +21 -0
  35. package/dist/director/zone/zone-event-bridge.d.ts.map +1 -0
  36. package/dist/director/zone/zone-event-bridge.js +24 -0
  37. package/dist/director/zone/zone-event-bridge.js.map +1 -0
  38. package/dist/director/zone/zone-event-envelope.d.ts +90 -0
  39. package/dist/director/zone/zone-event-envelope.d.ts.map +1 -0
  40. package/dist/director/zone/zone-event-envelope.js +104 -0
  41. package/dist/director/zone/zone-event-envelope.js.map +1 -0
  42. package/dist/director/zone/zone-event-log.d.ts +17 -0
  43. package/dist/director/zone/zone-event-log.d.ts.map +1 -0
  44. package/dist/director/zone/zone-event-log.js +46 -0
  45. package/dist/director/zone/zone-event-log.js.map +1 -0
  46. package/dist/director/zone/zone-event-system.d.ts +14 -0
  47. package/dist/director/zone/zone-event-system.d.ts.map +1 -0
  48. package/dist/director/zone/zone-event-system.js +179 -0
  49. package/dist/director/zone/zone-event-system.js.map +1 -0
  50. package/dist/director/zone/zone-state-resource.d.ts +15 -0
  51. package/dist/director/zone/zone-state-resource.d.ts.map +1 -0
  52. package/dist/director/zone/zone-state-resource.js +60 -0
  53. package/dist/director/zone/zone-state-resource.js.map +1 -0
  54. package/dist/index.d.ts +25 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +14 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/network/mock-multiplayer-bridge.d.ts +34 -0
  59. package/dist/network/mock-multiplayer-bridge.d.ts.map +1 -0
  60. package/dist/network/mock-multiplayer-bridge.js +91 -0
  61. package/dist/network/mock-multiplayer-bridge.js.map +1 -0
  62. package/dist/network/multiplayer-bridge.d.ts +45 -0
  63. package/dist/network/multiplayer-bridge.d.ts.map +1 -0
  64. package/dist/network/multiplayer-bridge.js +34 -0
  65. package/dist/network/multiplayer-bridge.js.map +1 -0
  66. package/dist/network/peer-pool.d.ts +46 -0
  67. package/dist/network/peer-pool.d.ts.map +1 -0
  68. package/dist/network/peer-pool.js +185 -0
  69. package/dist/network/peer-pool.js.map +1 -0
  70. package/dist/network/sse-multiplayer-bridge.d.ts +36 -0
  71. package/dist/network/sse-multiplayer-bridge.d.ts.map +1 -0
  72. package/dist/network/sse-multiplayer-bridge.js +264 -0
  73. package/dist/network/sse-multiplayer-bridge.js.map +1 -0
  74. package/dist/server/index.d.ts +7 -0
  75. package/dist/server/index.d.ts.map +1 -0
  76. package/dist/server/index.js +33 -0
  77. package/dist/server/index.js.map +1 -0
  78. package/dist/systems/peer-presence-system.d.ts +19 -0
  79. package/dist/systems/peer-presence-system.d.ts.map +1 -0
  80. package/dist/systems/peer-presence-system.js +118 -0
  81. package/dist/systems/peer-presence-system.js.map +1 -0
  82. package/package.json +6 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiplayer-bridge.js","sourceRoot":"","sources":["../../src/network/multiplayer-bridge.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,4BAA4B;AAC5B,qEAAqE;AACrE,oEAAoE;AACpE,uEAAuE;AACvE,+DAA+D;AAC/D,iEAAiE;AACjE,uEAAuE;AACvE,4CAA4C;AAC5C,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,uEAAuE;AACvE,kDAAkD;AAClD,EAAE;AACF,sEAAsE;AACtE,sFAAsF;AACtF,4DAA4D;AAC5D,wGAAwG;AACxG,+EAA+E;AAC/E,EAAE;AACF,mEAAmE;AACnE,iEAAiE;AA2EjE,iBAAiB;AACjB,MAAM,CAAC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAE9C,iEAAiE;AACjE,kEAAkE;AAClE,kEAAkE;AAClE,wBAAwB;AACxB,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,GAAG,YAAY,CAAC"}
@@ -0,0 +1,46 @@
1
+ export interface PeerEntry {
2
+ characterId: string;
3
+ zone: string;
4
+ name: string | null;
5
+ prevX: number;
6
+ prevY: number;
7
+ prevTsMs: number;
8
+ currentX: number;
9
+ currentY: number;
10
+ currentTsMs: number;
11
+ lastRenderedFrame: number;
12
+ }
13
+ export interface RenderedPeerView {
14
+ characterId: string;
15
+ x: number;
16
+ y: number;
17
+ zone: string;
18
+ name: string | null;
19
+ }
20
+ export declare class PeerPool {
21
+ private peers;
22
+ private localCharacterId;
23
+ private scratchView;
24
+ setLocalCharacterId(id: string | null): void;
25
+ getLocalCharacterId(): string | null;
26
+ upsert(characterId: string, x: number, y: number, zone: string, tsMs: number, name?: string): void;
27
+ applySnapshot(peers: ReadonlyArray<{
28
+ characterId: string;
29
+ x: number;
30
+ y: number;
31
+ zone: string;
32
+ tsMs: number;
33
+ name?: string;
34
+ }>): void;
35
+ remove(characterId: string): boolean;
36
+ has(characterId: string): boolean;
37
+ size(): number;
38
+ get(characterId: string): Readonly<PeerEntry> | undefined;
39
+ forEachRendered(nowMs: number, frame: number, fn: (view: Readonly<RenderedPeerView>) => void): void;
40
+ getRenderedPosition(characterId: string, nowMs: number): {
41
+ x: number;
42
+ y: number;
43
+ } | null;
44
+ clear(): void;
45
+ }
46
+ //# sourceMappingURL=peer-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-pool.d.ts","sourceRoot":"","sources":["../../src/network/peer-pool.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAGpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IAGpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAMD,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAYD,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,gBAAgB,CAAuB;IAI/C,OAAO,CAAC,WAAW,CAMjB;IAEF,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAO5C,mBAAmB,IAAI,MAAM,GAAG,IAAI;IASpC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IA0ClG,aAAa,CACX,KAAK,EAAE,aAAa,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,GACD,IAAI;IAwBP,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIpC,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIjC,IAAI,IAAI,MAAM;IAId,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,SAAS;IAOzD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,CAAC,KAAK,IAAI,GAAG,IAAI;IAsBnG,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAcxF,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,185 @@
1
+ // PeerPool - tracks all known remote peers and their interpolated
2
+ // world position.
3
+ //
4
+ // Each peer keeps the last two known positions (prev, current) with
5
+ // timestamps. At frame time the system asks the pool for the
6
+ // interpolated (x, y) per peer, which is computed as
7
+ // factor = clamp01((nowMs - prevTsMs) / (currentTsMs - prevTsMs))
8
+ // x = lerp(prevX, currentX, factor)
9
+ // y = lerp(prevY, currentY, factor)
10
+ //
11
+ // When a new presence.update arrives, prev <- current, current <- new.
12
+ // The factor saturates at 1 once nowMs passes currentTsMs, so a peer
13
+ // who stops sending updates simply freezes at their last known
14
+ // position rather than extrapolating off into the distance.
15
+ //
16
+ // "Acceptable lag" per the phase 15.1 spec is ~150ms (one update
17
+ // interval at the 10Hz wire rate), which is imperceptible at
18
+ // walk-speed. No CRDT, no client-side prediction beyond the
19
+ // straight-line lerp - those are deferred until shared state extends
20
+ // past raw position.
21
+ //
22
+ // Self-filter: the local character's own character_id should NOT
23
+ // appear among the rendered peers (we don't render ourselves as a
24
+ // ghost). The PeerPresenceSystem owns this filter via
25
+ // setLocalCharacterId(); peers with that id are silently skipped on
26
+ // upsert and removed if already present.
27
+ function lerp(a, b, t) {
28
+ return a + (b - a) * t;
29
+ }
30
+ function clamp01(t) {
31
+ if (t < 0)
32
+ return 0;
33
+ if (t > 1)
34
+ return 1;
35
+ return t;
36
+ }
37
+ export class PeerPool {
38
+ peers = new Map();
39
+ localCharacterId = null;
40
+ // Reused on every getRenderedPosition / forEachRendered call so the
41
+ // hot per-frame path is allocation-free.
42
+ scratchView = {
43
+ characterId: '',
44
+ x: 0,
45
+ y: 0,
46
+ zone: '',
47
+ name: null,
48
+ };
49
+ setLocalCharacterId(id) {
50
+ this.localCharacterId = id;
51
+ if (id !== null && this.peers.has(id)) {
52
+ this.peers.delete(id);
53
+ }
54
+ }
55
+ getLocalCharacterId() {
56
+ return this.localCharacterId;
57
+ }
58
+ // Apply a new presence update for a peer. If the peer is the local
59
+ // character, the update is ignored (self-filter). If this is the
60
+ // first update for the peer, prev = current = the new position so
61
+ // the lerp factor immediately saturates and the peer renders at the
62
+ // sent position.
63
+ upsert(characterId, x, y, zone, tsMs, name) {
64
+ if (this.localCharacterId !== null && characterId === this.localCharacterId) {
65
+ return;
66
+ }
67
+ const existing = this.peers.get(characterId);
68
+ if (!existing) {
69
+ this.peers.set(characterId, {
70
+ characterId,
71
+ zone,
72
+ name: name ?? null,
73
+ prevX: x,
74
+ prevY: y,
75
+ prevTsMs: tsMs,
76
+ currentX: x,
77
+ currentY: y,
78
+ currentTsMs: tsMs,
79
+ lastRenderedFrame: -1,
80
+ });
81
+ return;
82
+ }
83
+ // Out-of-order: drop messages older than current. Wire protocol
84
+ // is monotonic per character_id, but reorder buffers + reconnect
85
+ // replays can deliver an older ts after a newer one.
86
+ if (tsMs < existing.currentTsMs) {
87
+ return;
88
+ }
89
+ existing.prevX = existing.currentX;
90
+ existing.prevY = existing.currentY;
91
+ existing.prevTsMs = existing.currentTsMs;
92
+ existing.currentX = x;
93
+ existing.currentY = y;
94
+ existing.currentTsMs = tsMs;
95
+ existing.zone = zone;
96
+ if (name !== undefined) {
97
+ existing.name = name;
98
+ }
99
+ }
100
+ // Replace the entire roster with a snapshot. Peers not present in
101
+ // the snapshot are dropped; peers in the snapshot but not yet
102
+ // tracked are inserted (with prev = current so they render at the
103
+ // sent position immediately).
104
+ applySnapshot(peers) {
105
+ const seen = new Set();
106
+ for (let i = 0; i < peers.length; i++) {
107
+ const p = peers[i];
108
+ if (!p)
109
+ continue;
110
+ if (this.localCharacterId !== null && p.characterId === this.localCharacterId) {
111
+ continue;
112
+ }
113
+ seen.add(p.characterId);
114
+ this.upsert(p.characterId, p.x, p.y, p.zone, p.tsMs, p.name);
115
+ }
116
+ // Drop anyone not in the snapshot. Iterate keys snapshot first
117
+ // because Map.delete during iteration is fine but copying makes
118
+ // intent obvious.
119
+ const toRemove = [];
120
+ this.peers.forEach((_v, k) => {
121
+ if (!seen.has(k))
122
+ toRemove.push(k);
123
+ });
124
+ for (let i = 0; i < toRemove.length; i++) {
125
+ const k = toRemove[i];
126
+ if (k)
127
+ this.peers.delete(k);
128
+ }
129
+ }
130
+ remove(characterId) {
131
+ return this.peers.delete(characterId);
132
+ }
133
+ has(characterId) {
134
+ return this.peers.has(characterId);
135
+ }
136
+ size() {
137
+ return this.peers.size;
138
+ }
139
+ get(characterId) {
140
+ return this.peers.get(characterId);
141
+ }
142
+ // Iterate every tracked peer with their interpolated world
143
+ // position at nowMs. The view object is reused; consumers must
144
+ // copy any field they want to retain past the callback.
145
+ forEachRendered(nowMs, frame, fn) {
146
+ this.peers.forEach((entry) => {
147
+ const v = this.scratchView;
148
+ v.characterId = entry.characterId;
149
+ v.zone = entry.zone;
150
+ v.name = entry.name;
151
+ const dt = entry.currentTsMs - entry.prevTsMs;
152
+ if (dt <= 0) {
153
+ v.x = entry.currentX;
154
+ v.y = entry.currentY;
155
+ }
156
+ else {
157
+ const t = clamp01((nowMs - entry.prevTsMs) / dt);
158
+ v.x = lerp(entry.prevX, entry.currentX, t);
159
+ v.y = lerp(entry.prevY, entry.currentY, t);
160
+ }
161
+ entry.lastRenderedFrame = frame;
162
+ fn(v);
163
+ });
164
+ }
165
+ // Single-peer query for tests + the rare ad-hoc lookup. Hot paths
166
+ // use forEachRendered to avoid map lookups.
167
+ getRenderedPosition(characterId, nowMs) {
168
+ const entry = this.peers.get(characterId);
169
+ if (!entry)
170
+ return null;
171
+ const dt = entry.currentTsMs - entry.prevTsMs;
172
+ if (dt <= 0) {
173
+ return { x: entry.currentX, y: entry.currentY };
174
+ }
175
+ const t = clamp01((nowMs - entry.prevTsMs) / dt);
176
+ return {
177
+ x: lerp(entry.prevX, entry.currentX, t),
178
+ y: lerp(entry.prevY, entry.currentY, t),
179
+ };
180
+ }
181
+ clear() {
182
+ this.peers.clear();
183
+ }
184
+ }
185
+ //# sourceMappingURL=peer-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-pool.js","sourceRoot":"","sources":["../../src/network/peer-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type IMultiplayerBridge, type MultiplayerBridgeStatus, type MultiplayerBridgeStats, type PresenceMessage } from './multiplayer-bridge.js';
2
+ export interface SSEMultiplayerBridgeOptions {
3
+ baseUrl: string;
4
+ broadcastUrl?: string;
5
+ characterId: string;
6
+ zone: string;
7
+ eventSourceFactory?: (url: string) => EventSource;
8
+ fetchFn?: typeof fetch;
9
+ }
10
+ export declare class SSEMultiplayerBridge implements IMultiplayerBridge {
11
+ private readonly baseUrl;
12
+ private readonly broadcastUrl;
13
+ private readonly characterId;
14
+ private readonly zone;
15
+ private readonly eventSourceFactory;
16
+ private readonly fetchFn;
17
+ private es;
18
+ private queue;
19
+ private statusValue;
20
+ private statsValue;
21
+ private lastBroadcastMs;
22
+ constructor(opts: SSEMultiplayerBridgeOptions);
23
+ connect(): void;
24
+ disconnect(): void;
25
+ status(): MultiplayerBridgeStatus;
26
+ pollMessages(): PresenceMessage[];
27
+ broadcastPosition(x: number, y: number, zone: string, tsMs: number): void;
28
+ stats(): Readonly<MultiplayerBridgeStats>;
29
+ private buildUrl;
30
+ private openConnection;
31
+ private closeConnection;
32
+ private handleUpdate;
33
+ private handleDepart;
34
+ private handleSnapshot;
35
+ }
36
+ //# sourceMappingURL=sse-multiplayer-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-multiplayer-bridge.d.ts","sourceRoot":"","sources":["../../src/network/sse-multiplayer-bridge.ts"],"names":[],"mappings":"AA4BA,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,2BAA2B;IAI1C,OAAO,EAAE,MAAM,CAAC;IAKhB,YAAY,CAAC,EAAE,MAAM,CAAC;IAItB,WAAW,EAAE,MAAM,CAAC;IAEpB,IAAI,EAAE,MAAM,CAAC;IAGb,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC;IAClD,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,qBAAa,oBAAqB,YAAW,kBAAkB;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA+B;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,EAAE,CAA4B;IACtC,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,UAAU,CAKhB;IAEF,OAAO,CAAC,eAAe,CAAqB;gBAEhC,IAAI,EAAE,2BAA2B;IAwB7C,OAAO,IAAI,IAAI;IAMf,UAAU,IAAI,IAAI;IAKlB,MAAM,IAAI,uBAAuB;IAIjC,YAAY,IAAI,eAAe,EAAE;IAOjC,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IA0BzE,KAAK,IAAI,QAAQ,CAAC,sBAAsB,CAAC;IAMzC,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,cAAc;IAmCtB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,cAAc;CAsCvB"}
@@ -0,0 +1,264 @@
1
+ // SSEMultiplayerBridge - real EventSource subscription to the
2
+ // backend's presence endpoint, paired with a fetch POST for outbound
3
+ // position broadcasts.
4
+ //
5
+ // Wire protocol (paired with Track B server-side):
6
+ // GET <baseUrl>?character_id=...&zone=... opens an SSE stream that
7
+ // emits three event types:
8
+ // - 'presence.snapshot' { peers: [{ character_id, x, y, zone, ts_ms, name? }] }
9
+ // emitted once on connect with the full current peer roster.
10
+ // - 'presence.update' { character_id, x, y, zone, ts_ms, name? }
11
+ // emitted as peers move.
12
+ // - 'presence.depart' { character_id }
13
+ // emitted when a peer disconnects.
14
+ //
15
+ // POST <broadcastUrl> { character_id, x, y, zone, ts_ms }
16
+ // called by broadcastPosition at most BROADCAST_HZ per second.
17
+ // Engine-side rate limit; the bridge silently drops excess calls
18
+ // and increments rateLimitedDrops.
19
+ //
20
+ // Reconnect strategy: EventSource handles transport-layer reconnect
21
+ // internally. We observe via onerror/onopen and surface 'reconnecting'
22
+ // to the consumer. On reconnect the server is expected to re-emit a
23
+ // fresh 'presence.snapshot', which the PeerPool consumes and treats
24
+ // as authoritative (any peer not in the snapshot is dropped).
25
+ //
26
+ // Browser-only. Constructor throws if EventSource is undefined (Node
27
+ // test environment). Tests use MockMultiplayerBridge instead.
28
+ import { BROADCAST_MIN_INTERVAL_MS, } from './multiplayer-bridge.js';
29
+ export class SSEMultiplayerBridge {
30
+ baseUrl;
31
+ broadcastUrl;
32
+ characterId;
33
+ zone;
34
+ eventSourceFactory;
35
+ fetchFn;
36
+ es = null;
37
+ queue = [];
38
+ statusValue = 'idle';
39
+ statsValue = {
40
+ messagesReceived: 0,
41
+ messagesSent: 0,
42
+ rateLimitedDrops: 0,
43
+ reconnects: 0,
44
+ };
45
+ lastBroadcastMs = -Infinity;
46
+ constructor(opts) {
47
+ this.baseUrl = opts.baseUrl;
48
+ this.broadcastUrl = opts.broadcastUrl ?? defaultBroadcastUrl(opts.baseUrl);
49
+ this.characterId = opts.characterId;
50
+ this.zone = opts.zone;
51
+ if (opts.eventSourceFactory) {
52
+ this.eventSourceFactory = opts.eventSourceFactory;
53
+ }
54
+ else {
55
+ if (typeof EventSource === 'undefined') {
56
+ throw new Error('SSEMultiplayerBridge: EventSource is not available in this environment. Use MockMultiplayerBridge for tests.');
57
+ }
58
+ const ESCtor = EventSource;
59
+ this.eventSourceFactory = (u) => new ESCtor(u, { withCredentials: true });
60
+ }
61
+ if (opts.fetchFn) {
62
+ this.fetchFn = opts.fetchFn;
63
+ }
64
+ else {
65
+ if (typeof fetch === 'undefined') {
66
+ throw new Error('SSEMultiplayerBridge: fetch is not available in this environment.');
67
+ }
68
+ this.fetchFn = fetch.bind(globalThis);
69
+ }
70
+ }
71
+ connect() {
72
+ if (this.es)
73
+ return;
74
+ this.statusValue = 'connecting';
75
+ this.openConnection();
76
+ }
77
+ disconnect() {
78
+ this.statusValue = 'closed';
79
+ this.closeConnection();
80
+ }
81
+ status() {
82
+ return this.statusValue;
83
+ }
84
+ pollMessages() {
85
+ if (this.queue.length === 0)
86
+ return [];
87
+ const out = this.queue;
88
+ this.queue = [];
89
+ return out;
90
+ }
91
+ broadcastPosition(x, y, zone, tsMs) {
92
+ const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
93
+ if (now - this.lastBroadcastMs < BROADCAST_MIN_INTERVAL_MS) {
94
+ this.statsValue.rateLimitedDrops++;
95
+ return;
96
+ }
97
+ this.lastBroadcastMs = now;
98
+ this.statsValue.messagesSent++;
99
+ // Fire-and-forget POST. Errors are surfaced via stats only - the
100
+ // engine doesn't block on the network round trip. The body
101
+ // matches the server contract from the phase 15.1 spec.
102
+ const body = JSON.stringify({
103
+ character_id: this.characterId,
104
+ x,
105
+ y,
106
+ zone,
107
+ ts_ms: tsMs,
108
+ });
109
+ void this.fetchFn(this.broadcastUrl, {
110
+ method: 'POST',
111
+ credentials: 'include',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body,
114
+ }).catch(() => { });
115
+ }
116
+ stats() {
117
+ return this.statsValue;
118
+ }
119
+ // ----- Internal -----
120
+ buildUrl() {
121
+ const sep = this.baseUrl.includes('?') ? '&' : '?';
122
+ return (this.baseUrl +
123
+ sep +
124
+ 'character_id=' + encodeURIComponent(this.characterId) +
125
+ '&zone=' + encodeURIComponent(this.zone));
126
+ }
127
+ openConnection() {
128
+ const url = this.buildUrl();
129
+ let es;
130
+ try {
131
+ es = this.eventSourceFactory(url);
132
+ }
133
+ catch (err) {
134
+ this.statusValue = 'closed';
135
+ throw err;
136
+ }
137
+ this.es = es;
138
+ es.onopen = () => {
139
+ this.statusValue = 'connected';
140
+ };
141
+ es.onerror = () => {
142
+ const closed = es.readyState === 2; // EventSource.CLOSED
143
+ if (closed) {
144
+ this.statusValue = 'closed';
145
+ this.closeConnection();
146
+ return;
147
+ }
148
+ this.statusValue = 'reconnecting';
149
+ this.statsValue.reconnects++;
150
+ };
151
+ es.addEventListener('presence.update', (e) => {
152
+ this.handleUpdate(e);
153
+ });
154
+ es.addEventListener('presence.depart', (e) => {
155
+ this.handleDepart(e);
156
+ });
157
+ es.addEventListener('presence.snapshot', (e) => {
158
+ this.handleSnapshot(e);
159
+ });
160
+ }
161
+ closeConnection() {
162
+ if (!this.es)
163
+ return;
164
+ try {
165
+ this.es.close();
166
+ }
167
+ catch { /* ignore */ }
168
+ this.es = null;
169
+ }
170
+ handleUpdate(e) {
171
+ const data = parseJson(e.data);
172
+ if (!data || typeof data !== 'object')
173
+ return;
174
+ const characterId = data.character_id;
175
+ const x = data.x;
176
+ const y = data.y;
177
+ const zone = data.zone;
178
+ const tsMs = data.ts_ms;
179
+ if (typeof characterId !== 'string')
180
+ return;
181
+ if (typeof x !== 'number' || typeof y !== 'number')
182
+ return;
183
+ if (typeof zone !== 'string')
184
+ return;
185
+ if (typeof tsMs !== 'number')
186
+ return;
187
+ const name = data.name;
188
+ this.statsValue.messagesReceived++;
189
+ this.queue.push({
190
+ kind: 'update',
191
+ characterId,
192
+ x,
193
+ y,
194
+ zone,
195
+ tsMs,
196
+ ...(typeof name === 'string' ? { name } : {}),
197
+ });
198
+ }
199
+ handleDepart(e) {
200
+ const data = parseJson(e.data);
201
+ if (!data || typeof data !== 'object')
202
+ return;
203
+ const characterId = data.character_id;
204
+ if (typeof characterId !== 'string')
205
+ return;
206
+ this.statsValue.messagesReceived++;
207
+ this.queue.push({ kind: 'depart', characterId });
208
+ }
209
+ handleSnapshot(e) {
210
+ const data = parseJson(e.data);
211
+ if (!data || typeof data !== 'object')
212
+ return;
213
+ const peersRaw = data.peers;
214
+ if (!Array.isArray(peersRaw))
215
+ return;
216
+ const peers = [];
217
+ for (let i = 0; i < peersRaw.length; i++) {
218
+ const p = peersRaw[i];
219
+ if (!p || typeof p !== 'object')
220
+ continue;
221
+ const characterId = p.character_id;
222
+ const x = p.x;
223
+ const y = p.y;
224
+ const zone = p.zone;
225
+ const tsMs = p.ts_ms;
226
+ if (typeof characterId !== 'string')
227
+ continue;
228
+ if (typeof x !== 'number' || typeof y !== 'number')
229
+ continue;
230
+ if (typeof zone !== 'string')
231
+ continue;
232
+ if (typeof tsMs !== 'number')
233
+ continue;
234
+ const name = p.name;
235
+ peers.push({
236
+ characterId,
237
+ x,
238
+ y,
239
+ zone,
240
+ tsMs,
241
+ ...(typeof name === 'string' ? { name } : {}),
242
+ });
243
+ }
244
+ this.statsValue.messagesReceived++;
245
+ this.queue.push({ kind: 'snapshot', peers });
246
+ }
247
+ }
248
+ function parseJson(raw) {
249
+ if (typeof raw !== 'string')
250
+ return null;
251
+ try {
252
+ return JSON.parse(raw);
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }
258
+ function defaultBroadcastUrl(baseUrl) {
259
+ if (baseUrl.endsWith('/events')) {
260
+ return baseUrl.slice(0, -'/events'.length) + '/move';
261
+ }
262
+ return baseUrl + '/move';
263
+ }
264
+ //# sourceMappingURL=sse-multiplayer-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-multiplayer-bridge.js","sourceRoot":"","sources":["../../src/network/sse-multiplayer-bridge.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,qEAAqE;AACrE,uBAAuB;AACvB,EAAE;AACF,mDAAmD;AACnD,qEAAqE;AACrE,6BAA6B;AAC7B,oFAAoF;AACpF,qEAAqE;AACrE,uEAAuE;AACvE,iCAAiC;AACjC,6CAA6C;AAC7C,2CAA2C;AAC3C,EAAE;AACF,4DAA4D;AAC5D,mEAAmE;AACnE,qEAAqE;AACrE,uCAAuC;AACvC,EAAE;AACF,oEAAoE;AACpE,uEAAuE;AACvE,oEAAoE;AACpE,oEAAoE;AACpE,8DAA8D;AAC9D,EAAE;AACF,qEAAqE;AACrE,8DAA8D;AAE9D,OAAO,EAKL,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAwBjC,MAAM,OAAO,oBAAoB;IACd,OAAO,CAAS;IAChB,YAAY,CAAS;IACrB,WAAW,CAAS;IACpB,IAAI,CAAS;IACb,kBAAkB,CAA+B;IACjD,OAAO,CAAe;IAE/B,EAAE,GAAuB,IAAI,CAAC;IAC9B,KAAK,GAAsB,EAAE,CAAC;IAC9B,WAAW,GAA4B,MAAM,CAAC;IAC9C,UAAU,GAA2B;QAC3C,gBAAgB,EAAE,CAAC;QACnB,YAAY,EAAE,CAAC;QACf,gBAAgB,EAAE,CAAC;QACnB,UAAU,EAAE,CAAC;KACd,CAAC;IAEM,eAAe,GAAW,CAAC,QAAQ,CAAC;IAE5C,YAAY,IAAiC;QAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3E,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,IAAI,OAAO,WAAW,KAAK,WAAW,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,8GAA8G,CAAC,CAAC;YAClI,CAAC;YACD,MAAM,MAAM,GAAG,WAAW,CAAC;YAC3B,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,OAAO,KAAK,KAAK,WAAW,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;YACvF,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,EAAE;YAAE,OAAO;QACpB,IAAI,CAAC,WAAW,GAAG,YAAY,CAAC;QAChC,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAED,UAAU;QACR,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;QAC5B,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,YAAY;QACV,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,iBAAiB,CAAC,CAAS,EAAE,CAAS,EAAE,IAAY,EAAE,IAAY;QAChE,MAAM,GAAG,GAAG,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAChF,IAAI,GAAG,GAAG,IAAI,CAAC,eAAe,GAAG,yBAAyB,EAAE,CAAC;YAC3D,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;YACnC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC/B,iEAAiE;QACjE,2DAA2D;QAC3D,wDAAwD;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,YAAY,EAAE,IAAI,CAAC,WAAW;YAC9B,CAAC;YACD,CAAC;YACD,IAAI;YACJ,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE;YACnC,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI;SACL,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAyB,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,uBAAuB;IAEf,QAAQ;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACnD,OAAO,CACL,IAAI,CAAC,OAAO;YACZ,GAAG;YACH,eAAe,GAAG,kBAAkB,CAAC,IAAI,CAAC,WAAW,CAAC;YACtD,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CACzC,CAAC;IACJ,CAAC;IAEO,cAAc;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,IAAI,EAAe,CAAC;QACpB,IAAI,CAAC;YACH,EAAE,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;YAC5B,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE;YACf,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QACjC,CAAC,CAAC;QACF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,KAAK,CAAC,CAAC,CAAG,qBAAqB;YAC3D,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;gBAC5B,IAAI,CAAC,eAAe,EAAE,CAAC;gBACvB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,cAAc,CAAC;YAClC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAC/B,CAAC,CAAC;QAEF,EAAE,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,CAAC,CAAQ,EAAE,EAAE;YAClD,IAAI,CAAC,YAAY,CAAC,CAAiB,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,gBAAgB,CAAC,iBAAiB,EAAE,CAAC,CAAQ,EAAE,EAAE;YAClD,IAAI,CAAC,YAAY,CAAC,CAAiB,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,CAAC,CAAQ,EAAE,EAAE;YACpD,IAAI,CAAC,cAAc,CAAC,CAAiB,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO;QACrB,IAAI,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IACjB,CAAC;IAEO,YAAY,CAAC,CAAe;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC9C,MAAM,WAAW,GAAI,IAAmC,CAAC,YAAY,CAAC;QACtE,MAAM,CAAC,GAAI,IAAwB,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,GAAI,IAAwB,CAAC,CAAC,CAAC;QACtC,MAAM,IAAI,GAAI,IAA2B,CAAC,IAAI,CAAC;QAC/C,MAAM,IAAI,GAAI,IAA4B,CAAC,KAAK,CAAC;QACjD,IAAI,OAAO,WAAW,KAAK,QAAQ;YAAE,OAAO;QAC5C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO;QAC3D,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QACrC,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QACrC,MAAM,IAAI,GAAI,IAA2B,CAAC,IAAI,CAAC;QAC/C,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,QAAQ;YACd,WAAW;YACX,CAAC;YACD,CAAC;YACD,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAEO,YAAY,CAAC,CAAe;QAClC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC9C,MAAM,WAAW,GAAI,IAAmC,CAAC,YAAY,CAAC;QACtE,IAAI,OAAO,WAAW,KAAK,QAAQ;YAAE,OAAO;QAC5C,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;IAEO,cAAc,CAAC,CAAe;QACpC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC9C,MAAM,QAAQ,GAAI,IAA4B,CAAC,KAAK,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,OAAO;QACrC,MAAM,KAAK,GAON,EAAE,CAAC;QACR,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,SAAS;YAC1C,MAAM,WAAW,GAAI,CAAgC,CAAC,YAAY,CAAC;YACnE,MAAM,CAAC,GAAI,CAAqB,CAAC,CAAC,CAAC;YACnC,MAAM,CAAC,GAAI,CAAqB,CAAC,CAAC,CAAC;YACnC,MAAM,IAAI,GAAI,CAAwB,CAAC,IAAI,CAAC;YAC5C,MAAM,IAAI,GAAI,CAAyB,CAAC,KAAK,CAAC;YAC9C,IAAI,OAAO,WAAW,KAAK,QAAQ;gBAAE,SAAS;YAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,SAAS;YAC7D,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACvC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACvC,MAAM,IAAI,GAAI,CAAwB,CAAC,IAAI,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC;gBACT,WAAW;gBACX,CAAC;gBACD,CAAC;gBACD,IAAI;gBACJ,IAAI;gBACJ,GAAG,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9C,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;CACF;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACxD,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe;IAC1C,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,GAAG,OAAO,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,7 @@
1
+ export type { IAIPlugin, EmittedEvents, PluginContext, PeerInfo, PlayerAction, CharacterState, PluginStorage, PluginLogger, ZoneEvent, } from '../director/ai/plugin.js';
2
+ export { AIPluginRegistry, AIPluginDuplicateError, } from '../director/ai/ai-plugin-registry.js';
3
+ export { MapPluginStorage, ConsolePluginLogger, buildPluginContext, } from '../director/ai/plugin-context.js';
4
+ export type { BuildPluginContextOptions } from '../director/ai/plugin-context.js';
5
+ export { MockAIPlugin, } from '../director/ai/mock-ai-plugin.js';
6
+ export type { MockAIPluginOptions, MockAIPluginScriptEntry, } from '../director/ai/mock-ai-plugin.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AA6BA,YAAY,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EACZ,SAAS,GACV,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAG9C,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAElF,OAAO,EACL,YAAY,GACb,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EACV,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,kCAAkC,CAAC"}
@@ -0,0 +1,33 @@
1
+ // Loom Engine - server-side entry point.
2
+ //
3
+ // NOT imported by the browser bundle. The default browser export
4
+ // (`@sadhaka/loom-engine`) deliberately excludes everything in this
5
+ // module so LLM-backed plugins, server-side storage adapters, and
6
+ // other Node-only code never ship to the client.
7
+ //
8
+ // Consumers wire LLM-backed plugins by implementing IAIPlugin and
9
+ // registering with AIPluginRegistry. A typical server bootstrap:
10
+ //
11
+ // import {
12
+ // AIPluginRegistry,
13
+ // MapPluginStorage,
14
+ // ConsolePluginLogger,
15
+ // } from '@sadhaka/loom-engine/server';
16
+ //
17
+ // const registry = new AIPluginRegistry();
18
+ // const storage = new MapPluginStorage();
19
+ // registry.register(new MyAnthropicPlugin({...}));
20
+ // // per-tick / per-event:
21
+ // const ctx = buildPluginContext({ pluginName: 'my-plugin', storage });
22
+ // const emitted = await registry.dispatchTick(ctx);
23
+ // // emitted.characterEvents -> v1 stream
24
+ // // emitted.zoneEvents -> v2 zone log + presence fanout
25
+ //
26
+ // This entry corresponds to the "./server" exports field added in
27
+ // package.json (LOOM-DIRECTOR-PROTOCOL-V2 §5.5).
28
+ // ----- Registry -----
29
+ export { AIPluginRegistry, AIPluginDuplicateError, } from '../director/ai/ai-plugin-registry.js';
30
+ // ----- Reference impls -----
31
+ export { MapPluginStorage, ConsolePluginLogger, buildPluginContext, } from '../director/ai/plugin-context.js';
32
+ export { MockAIPlugin, } from '../director/ai/mock-ai-plugin.js';
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,iEAAiE;AACjE,oEAAoE;AACpE,kEAAkE;AAClE,iDAAiD;AACjD,EAAE;AACF,kEAAkE;AAClE,iEAAiE;AACjE,EAAE;AACF,aAAa;AACb,wBAAwB;AACxB,wBAAwB;AACxB,2BAA2B;AAC3B,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,4CAA4C;AAC5C,qDAAqD;AACrD,6BAA6B;AAC7B,0EAA0E;AAC1E,sDAAsD;AACtD,4CAA4C;AAC5C,gEAAgE;AAChE,EAAE;AACF,kEAAkE;AAClE,iDAAiD;AAejD,uBAAuB;AACvB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAE9C,8BAA8B;AAC9B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EACL,YAAY,GACb,MAAM,kCAAkC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { System } from '../system.js';
2
+ import type { World } from '../world.js';
3
+ export declare class PeerPresenceSystem implements System {
4
+ readonly name: string;
5
+ update(world: World, _dt: number): void;
6
+ }
7
+ export declare class PeerRenderSystem implements System {
8
+ readonly name: string;
9
+ private scratchTextStyle;
10
+ private scratchTint;
11
+ private readonly labelYOffset;
12
+ private readonly showNames;
13
+ constructor(opts?: {
14
+ labelYOffset?: number;
15
+ showNames?: boolean;
16
+ });
17
+ update(world: World, _dt: number): void;
18
+ }
19
+ //# sourceMappingURL=peer-presence-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer-presence-system.d.ts","sourceRoot":"","sources":["../../src/systems/peer-presence-system.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAiBzC,qBAAa,kBAAmB,YAAW,MAAM;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAmB;IAExC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAwBxC;AAKD,qBAAa,gBAAiB,YAAW,MAAM;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAiB;IAGtC,OAAO,CAAC,gBAAgB,CAKtB;IACF,OAAO,CAAC,WAAW,CAEjB;IAMF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;gBAExB,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO;IAKrE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;CAgCxC"}