@sadhaka/loom-engine 0.13.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 (57) hide show
  1. package/dist/director/ai/ai-plugin-registry.d.ts +20 -0
  2. package/dist/director/ai/ai-plugin-registry.d.ts.map +1 -0
  3. package/dist/director/ai/ai-plugin-registry.js +232 -0
  4. package/dist/director/ai/ai-plugin-registry.js.map +1 -0
  5. package/dist/director/ai/mock-ai-plugin.d.ts +24 -0
  6. package/dist/director/ai/mock-ai-plugin.d.ts.map +1 -0
  7. package/dist/director/ai/mock-ai-plugin.js +83 -0
  8. package/dist/director/ai/mock-ai-plugin.js.map +1 -0
  9. package/dist/director/ai/plugin-context.d.ts +27 -0
  10. package/dist/director/ai/plugin-context.d.ts.map +1 -0
  11. package/dist/director/ai/plugin-context.js +152 -0
  12. package/dist/director/ai/plugin-context.js.map +1 -0
  13. package/dist/director/ai/plugin.d.ts +57 -0
  14. package/dist/director/ai/plugin.d.ts.map +1 -0
  15. package/dist/director/ai/plugin.js +32 -0
  16. package/dist/director/ai/plugin.js.map +1 -0
  17. package/dist/director/index.d.ts +27 -0
  18. package/dist/director/index.d.ts.map +1 -0
  19. package/dist/director/index.js +26 -0
  20. package/dist/director/index.js.map +1 -0
  21. package/dist/director/zone/mock-zone-bridge.d.ts +22 -0
  22. package/dist/director/zone/mock-zone-bridge.d.ts.map +1 -0
  23. package/dist/director/zone/mock-zone-bridge.js +107 -0
  24. package/dist/director/zone/mock-zone-bridge.js.map +1 -0
  25. package/dist/director/zone/sse-zone-bridge.d.ts +40 -0
  26. package/dist/director/zone/sse-zone-bridge.d.ts.map +1 -0
  27. package/dist/director/zone/sse-zone-bridge.js +164 -0
  28. package/dist/director/zone/sse-zone-bridge.js.map +1 -0
  29. package/dist/director/zone/zone-event-bridge.d.ts +21 -0
  30. package/dist/director/zone/zone-event-bridge.d.ts.map +1 -0
  31. package/dist/director/zone/zone-event-bridge.js +24 -0
  32. package/dist/director/zone/zone-event-bridge.js.map +1 -0
  33. package/dist/director/zone/zone-event-envelope.d.ts +90 -0
  34. package/dist/director/zone/zone-event-envelope.d.ts.map +1 -0
  35. package/dist/director/zone/zone-event-envelope.js +104 -0
  36. package/dist/director/zone/zone-event-envelope.js.map +1 -0
  37. package/dist/director/zone/zone-event-log.d.ts +17 -0
  38. package/dist/director/zone/zone-event-log.d.ts.map +1 -0
  39. package/dist/director/zone/zone-event-log.js +46 -0
  40. package/dist/director/zone/zone-event-log.js.map +1 -0
  41. package/dist/director/zone/zone-event-system.d.ts +14 -0
  42. package/dist/director/zone/zone-event-system.d.ts.map +1 -0
  43. package/dist/director/zone/zone-event-system.js +179 -0
  44. package/dist/director/zone/zone-event-system.js.map +1 -0
  45. package/dist/director/zone/zone-state-resource.d.ts +15 -0
  46. package/dist/director/zone/zone-state-resource.d.ts.map +1 -0
  47. package/dist/director/zone/zone-state-resource.js +60 -0
  48. package/dist/director/zone/zone-state-resource.js.map +1 -0
  49. package/dist/index.d.ts +13 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/server/index.d.ts +7 -0
  54. package/dist/server/index.d.ts.map +1 -0
  55. package/dist/server/index.js +33 -0
  56. package/dist/server/index.js.map +1 -0
  57. package/package.json +6 -2
@@ -0,0 +1,32 @@
1
+ // IAIPlugin - server-side plugin SPI for the Director.
2
+ //
3
+ // Per LOOM-DIRECTOR-PROTOCOL-V2 Section 5: the engine no longer
4
+ // hardcodes a single Anthropic flow. Consumers register any number of
5
+ // IAIPlugin implementations against the AIPluginRegistry, and the
6
+ // runtime dispatches lifecycle hooks (tick, peer join/leave, zone
7
+ // enter, player action). Each hook returns EmittedEvents - either
8
+ // per-character v1 envelopes, v2 zone events, or both.
9
+ //
10
+ // This module is server-only. The browser bundle never imports it.
11
+ // It is exposed only via the `@sadhaka/loom-engine/server` entry
12
+ // point so consumers can wire LLM-backed plugins (Anthropic, OpenAI,
13
+ // local models, deterministic state machines, ...) on the Node side
14
+ // while the browser engine stays small.
15
+ //
16
+ // `ZoneEvent` (LOOM-DIRECTOR-PROTOCOL-V2 §3) is defined in
17
+ // `src/director/zone/zone-event-envelope.ts` (Track A, merged into
18
+ // 0.14.0 alongside this module). The registry carries zone events
19
+ // opaquely - it never dereferences fields beyond the discriminated
20
+ // union - so the only consumer of the strict type is plugin authors
21
+ // constructing ZoneEvent values in their hook returns.
22
+ //
23
+ // Spec invariants preserved:
24
+ // - All hooks are async; they return EmittedEvents.
25
+ // - Hooks not implemented by a plugin are simply omitted; the
26
+ // registry checks `typeof plugin.onX === 'function'` before call.
27
+ // - Plugins are pure-ish: given a context, they return events.
28
+ // State mutation is the engine's job (open question 8.2 resolved).
29
+ // - Plugin-direct event emission outside its returned EmittedEvents
30
+ // is not supported; the registry is the single funnel.
31
+ export {};
32
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/director/ai/plugin.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,gEAAgE;AAChE,sEAAsE;AACtE,kEAAkE;AAClE,kEAAkE;AAClE,kEAAkE;AAClE,uDAAuD;AACvD,EAAE;AACF,mEAAmE;AACnE,iEAAiE;AACjE,qEAAqE;AACrE,oEAAoE;AACpE,wCAAwC;AACxC,EAAE;AACF,2DAA2D;AAC3D,mEAAmE;AACnE,kEAAkE;AAClE,mEAAmE;AACnE,oEAAoE;AACpE,uDAAuD;AACvD,EAAE;AACF,6BAA6B;AAC7B,sDAAsD;AACtD,gEAAgE;AAChE,sEAAsE;AACtE,iEAAiE;AACjE,uEAAuE;AACvE,sEAAsE;AACtE,2DAA2D"}
@@ -0,0 +1,27 @@
1
+ export type { EventEnvelope, DirectorEvent, DirectorEventType, DirectorEventDataMap, EventPriority, EncounterSpawnData, EncounterTickData, EncounterEndData, EncounterLootData, KnotContextData, KnotPaletteHex, KnotMood, VeBudgetUpdateData, VeilTier, SceneTransitionData, SceneTransitionKind, NarratorLineData, NarratorVoice, SystemHeartbeatData, SystemReplayCompleteData, SystemSnapshotRequiredData, MobSpec, BossSpec, DropSpec, } from './event-envelope.js';
2
+ export { parseEnvelope, parseEnvelopeJson, priorityFor, EventEnvelopeParseError, } from './event-envelope.js';
3
+ export type { IDirectorBridge, DirectorBridgeStatus, DirectorBridgeStats, } from './director-bridge.js';
4
+ export { RESOURCE_DIRECTOR_BRIDGE, RESOURCE_KNOT_CONTEXT, } from './director-bridge.js';
5
+ export { MockDirectorBridge } from './mock-director-bridge.js';
6
+ export type { SSEDirectorBridgeOptions } from './sse-director-bridge.js';
7
+ export { SSEDirectorBridge } from './sse-director-bridge.js';
8
+ export type { KnotPaletteRgba } from './knot-context-resource.js';
9
+ export { KnotContextResource } from './knot-context-resource.js';
10
+ export type { DirectorEventLog } from './director-system.js';
11
+ export { DirectorSystem, RESOURCE_DIRECTOR_LOG, createDirectorEventLog, } from './director-system.js';
12
+ export type { DirectorEncounterSystemOptions } from './director-encounter-system.js';
13
+ export { DirectorEncounterSystem } from './director-encounter-system.js';
14
+ export type { ZoneEventEnvelope, ZoneEvent, ZoneEventType, ZoneEventDataMap, ZoneBossSpec, ZoneBossSpawnData, ZoneBossTickData, ZoneBossEndData, ZoneBossOutcome, ZoneBossHit, ZoneNarratorData, ZoneKnotData, ZoneStateData, ZoneSnapshotData, ZoneStateChange, } from './zone/zone-event-envelope.js';
15
+ export { parseZoneEnvelope, parseZoneEnvelopeJson, priorityFor as zonePriorityFor, ZoneEventEnvelopeParseError, } from './zone/zone-event-envelope.js';
16
+ export type { IZoneEventBridge, ZoneEventBridgeStatus, ZoneEventBridgeStats, } from './zone/zone-event-bridge.js';
17
+ export { RESOURCE_ZONE_EVENT_BRIDGE } from './zone/zone-event-bridge.js';
18
+ export { MockZoneBridge } from './zone/mock-zone-bridge.js';
19
+ export type { SSEZoneBridgeOptions, SSEZoneBridgeEventSource, } from './zone/sse-zone-bridge.js';
20
+ export { SSEZoneBridge } from './zone/sse-zone-bridge.js';
21
+ export type { ZoneEventLog, ZoneEventLogEntry, } from './zone/zone-event-log.js';
22
+ export { RESOURCE_ZONE_EVENT_LOG, ZONE_RING_SIZE, createZoneEventLog, getOrCreateZoneEntry, pushZoneEvent, } from './zone/zone-event-log.js';
23
+ export type { DirectorZoneStateResource } from './zone/zone-state-resource.js';
24
+ export { RESOURCE_DIRECTOR_ZONE_STATE, createDirectorZoneStateResource, getOrCreateZoneStateMap, applyZoneStateChanges, replaceZoneStateFromSnapshot, } from './zone/zone-state-resource.js';
25
+ export type { ZoneEventSystemOptions } from './zone/zone-event-system.js';
26
+ export { ZoneEventSystem } from './zone/zone-event-system.js';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/director/index.ts"],"names":[],"mappings":"AAaA,YAAY,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,QAAQ,EACR,kBAAkB,EAClB,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,OAAO,EACP,QAAQ,EACR,QAAQ,GACT,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAGzE,YAAY,EACV,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,WAAW,IAAI,eAAe,EAC9B,2BAA2B,GAC5B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EACV,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EACV,YAAY,EACZ,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,EAC/B,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AACvC,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,26 @@
1
+ // Director module barrel - re-exports the v1 (Phase 6) surface plus
2
+ // the v2 (Phase 16) zone-event surface.
3
+ //
4
+ // v1 lives at the top level of src/director/. v2 lives under
5
+ // src/director/zone/. Track B's AI Plugin SPI lives under
6
+ // src/director/ai/ and is re-exported via @sadhaka/loom-engine/server,
7
+ // not from here. We do not pull AI plugin types into this barrel so
8
+ // the browser bundle stays small.
9
+ //
10
+ // Engine-level public re-exports happen in ../index.ts. Internal
11
+ // callers can import from this barrel for convenience.
12
+ export { parseEnvelope, parseEnvelopeJson, priorityFor, EventEnvelopeParseError, } from './event-envelope.js';
13
+ export { RESOURCE_DIRECTOR_BRIDGE, RESOURCE_KNOT_CONTEXT, } from './director-bridge.js';
14
+ export { MockDirectorBridge } from './mock-director-bridge.js';
15
+ export { SSEDirectorBridge } from './sse-director-bridge.js';
16
+ export { KnotContextResource } from './knot-context-resource.js';
17
+ export { DirectorSystem, RESOURCE_DIRECTOR_LOG, createDirectorEventLog, } from './director-system.js';
18
+ export { DirectorEncounterSystem } from './director-encounter-system.js';
19
+ export { parseZoneEnvelope, parseZoneEnvelopeJson, priorityFor as zonePriorityFor, ZoneEventEnvelopeParseError, } from './zone/zone-event-envelope.js';
20
+ export { RESOURCE_ZONE_EVENT_BRIDGE } from './zone/zone-event-bridge.js';
21
+ export { MockZoneBridge } from './zone/mock-zone-bridge.js';
22
+ export { SSEZoneBridge } from './zone/sse-zone-bridge.js';
23
+ export { RESOURCE_ZONE_EVENT_LOG, ZONE_RING_SIZE, createZoneEventLog, getOrCreateZoneEntry, pushZoneEvent, } from './zone/zone-event-log.js';
24
+ export { RESOURCE_DIRECTOR_ZONE_STATE, createDirectorZoneStateResource, getOrCreateZoneStateMap, applyZoneStateChanges, replaceZoneStateFromSnapshot, } from './zone/zone-state-resource.js';
25
+ export { ZoneEventSystem } from './zone/zone-event-system.js';
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/director/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wCAAwC;AACxC,EAAE;AACF,6DAA6D;AAC7D,0DAA0D;AAC1D,uEAAuE;AACvE,oEAAoE;AACpE,kCAAkC;AAClC,EAAE;AACF,iEAAiE;AACjE,uDAAuD;AA6BvD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAoBzE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,WAAW,IAAI,eAAe,EAC9B,2BAA2B,GAC5B,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAK1D,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,4BAA4B,EAC5B,+BAA+B,EAC/B,uBAAuB,EACvB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ZoneEvent } from './zone-event-envelope.js';
2
+ import { type IZoneEventBridge, type ZoneEventBridgeStatus, type ZoneEventBridgeStats } from './zone-event-bridge.js';
3
+ export declare class MockZoneBridge implements IZoneEventBridge {
4
+ private queue;
5
+ private statusValue;
6
+ private readonly lastEventIdByZone;
7
+ private readonly statsValue;
8
+ start(): void;
9
+ stop(): void;
10
+ status(): ZoneEventBridgeStatus;
11
+ isConnected(): boolean;
12
+ getLastEventId(zone: string): number;
13
+ pollEvents(): ZoneEvent[];
14
+ stats(): Readonly<ZoneEventBridgeStats>;
15
+ enqueueIncoming(event: ZoneEvent): void;
16
+ enqueueIncomingJson(json: string): boolean;
17
+ enqueueAll(events: ReadonlyArray<ZoneEvent>): void;
18
+ bumpReconnect(): void;
19
+ setServerDrops(p1: number, p2: number): void;
20
+ pending(): number;
21
+ }
22
+ //# sourceMappingURL=mock-zone-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-zone-bridge.d.ts","sourceRoot":"","sources":["../../../src/director/zone/mock-zone-bridge.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,cAAe,YAAW,gBAAgB;IACrD,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAYzB;IAEF,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAIZ,MAAM,IAAI,qBAAqB;IAI/B,WAAW,IAAI,OAAO;IAItB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIpC,UAAU,IAAI,SAAS,EAAE;IAOzB,KAAK,IAAI,QAAQ,CAAC,oBAAoB,CAAC;IAmBvC,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAavC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQ1C,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI;IAQlD,aAAa,IAAI,IAAI;IAKrB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAM5C,OAAO,IAAI,MAAM;CAGlB"}
@@ -0,0 +1,107 @@
1
+ // MockZoneBridge - in-memory v2 zone-event source for tests + offline
2
+ // demos.
3
+ //
4
+ // Tests inject envelopes via enqueueIncoming() and let ZoneEventSystem
5
+ // drain them. The demo path can also use this when running without a
6
+ // backend connection. pollEvents() drains the queue in FIFO order.
7
+ //
8
+ // Tracks per-zone lastEventId so the IZoneEventBridge contract is
9
+ // satisfied. Does not reconnect, does not validate envelopes when fed
10
+ // objects directly - that's parseZoneEnvelope's job upstream of
11
+ // enqueueIncoming(). enqueueIncomingJson() is a convenience that runs
12
+ // the parser first and silently drops malformed payloads (mirrors how
13
+ // SSEZoneBridge would handle a bad frame).
14
+ import { parseZoneEnvelopeJson } from './zone-event-envelope.js';
15
+ export class MockZoneBridge {
16
+ queue = [];
17
+ statusValue = 'idle';
18
+ lastEventIdByZone = new Map();
19
+ statsValue = {
20
+ eventsReceived: 0,
21
+ reconnects: 0,
22
+ outOfOrderEvents: 0,
23
+ serverDropsP1: 0,
24
+ serverDropsP2: 0,
25
+ };
26
+ start() {
27
+ this.statusValue = 'connected';
28
+ }
29
+ stop() {
30
+ this.statusValue = 'closed';
31
+ }
32
+ status() {
33
+ return this.statusValue;
34
+ }
35
+ isConnected() {
36
+ return this.statusValue === 'connected';
37
+ }
38
+ getLastEventId(zone) {
39
+ return this.lastEventIdByZone.get(zone) ?? 0;
40
+ }
41
+ pollEvents() {
42
+ if (this.queue.length === 0)
43
+ return [];
44
+ const out = this.queue;
45
+ this.queue = [];
46
+ return out;
47
+ }
48
+ stats() {
49
+ // Allocate the read-only view fresh each call so mutations after
50
+ // the call don't leak through. lastEventIdByZone is wrapped in a
51
+ // shallow clone; spec calls this rare so cost is fine.
52
+ return {
53
+ eventsReceived: this.statsValue.eventsReceived,
54
+ reconnects: this.statsValue.reconnects,
55
+ outOfOrderEvents: this.statsValue.outOfOrderEvents,
56
+ serverDropsP1: this.statsValue.serverDropsP1,
57
+ serverDropsP2: this.statsValue.serverDropsP2,
58
+ lastEventIdByZone: new Map(this.lastEventIdByZone),
59
+ };
60
+ }
61
+ // ----- Mock-only injection helpers -----
62
+ // Enqueue a parsed envelope as if the server pushed it. Out-of-order
63
+ // injection is allowed; the consumer's per-zone gap detection should
64
+ // handle it.
65
+ enqueueIncoming(event) {
66
+ this.queue.push(event);
67
+ this.statsValue.eventsReceived++;
68
+ const prev = this.lastEventIdByZone.get(event.zone_id) ?? 0;
69
+ if (event.id > prev) {
70
+ this.lastEventIdByZone.set(event.zone_id, event.id);
71
+ }
72
+ else {
73
+ this.statsValue.outOfOrderEvents++;
74
+ }
75
+ }
76
+ // Convenience: enqueue from a JSON string. Silently drops malformed
77
+ // payloads, matching the SSE bridge's parse-error behaviour.
78
+ enqueueIncomingJson(json) {
79
+ const ev = parseZoneEnvelopeJson(json);
80
+ if (!ev)
81
+ return false;
82
+ this.enqueueIncoming(ev);
83
+ return true;
84
+ }
85
+ // Convenience: bulk enqueue.
86
+ enqueueAll(events) {
87
+ for (let i = 0; i < events.length; i++) {
88
+ const e = events[i];
89
+ if (e)
90
+ this.enqueueIncoming(e);
91
+ }
92
+ }
93
+ // Mock-only: simulate the server crediting a reconnect.
94
+ bumpReconnect() {
95
+ this.statsValue.reconnects++;
96
+ }
97
+ // Mock-only: simulate server-side drop counters from a heartbeat.
98
+ setServerDrops(p1, p2) {
99
+ this.statsValue.serverDropsP1 = p1;
100
+ this.statsValue.serverDropsP2 = p2;
101
+ }
102
+ // Inspect-only: how many events are buffered waiting for a poll.
103
+ pending() {
104
+ return this.queue.length;
105
+ }
106
+ }
107
+ //# sourceMappingURL=mock-zone-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-zone-bridge.js","sourceRoot":"","sources":["../../../src/director/zone/mock-zone-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ZoneEvent } from './zone-event-envelope.js';
2
+ import { type IZoneEventBridge, type ZoneEventBridgeStatus, type ZoneEventBridgeStats } from './zone-event-bridge.js';
3
+ export interface SSEZoneBridgeEventSource {
4
+ readonly readyState: number;
5
+ addEventListener(type: string, listener: (event: {
6
+ data?: unknown;
7
+ }) => void): void;
8
+ removeEventListener?(type: string, listener: (event: {
9
+ data?: unknown;
10
+ }) => void): void;
11
+ }
12
+ export interface SSEZoneBridgeOptions {
13
+ eventSource: SSEZoneBridgeEventSource;
14
+ characterId: string;
15
+ currentZone: () => string;
16
+ eventName?: string;
17
+ filterAtReceive?: boolean;
18
+ }
19
+ export declare class SSEZoneBridge implements IZoneEventBridge {
20
+ private readonly es;
21
+ private readonly characterId;
22
+ private readonly currentZone;
23
+ private readonly eventName;
24
+ private readonly filterAtReceive;
25
+ private listener;
26
+ private queue;
27
+ private statusValue;
28
+ private readonly lastEventIdByZone;
29
+ private readonly statsValue;
30
+ constructor(opts: SSEZoneBridgeOptions);
31
+ start(): void;
32
+ stop(): void;
33
+ status(): ZoneEventBridgeStatus;
34
+ isConnected(): boolean;
35
+ getLastEventId(zone: string): number;
36
+ pollEvents(): ZoneEvent[];
37
+ stats(): Readonly<ZoneEventBridgeStats>;
38
+ private handleRaw;
39
+ }
40
+ //# sourceMappingURL=sse-zone-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-zone-bridge.d.ts","sourceRoot":"","sources":["../../../src/director/zone/sse-zone-bridge.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAIhC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5C,IAAI,CAAC;IACR,mBAAmB,CAAC,CAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5C,IAAI,CAAC;CAGT;AAED,MAAM,WAAW,oBAAoB;IAInC,WAAW,EAAE,wBAAwB,CAAC;IAItC,WAAW,EAAE,MAAM,CAAC;IAMpB,WAAW,EAAE,MAAM,MAAM,CAAC;IAG1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAInB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAOD,qBAAa,aAAc,YAAW,gBAAgB;IACpD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAA2B;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAE1C,OAAO,CAAC,QAAQ,CAAsD;IACtE,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAYzB;gBAEU,IAAI,EAAE,oBAAoB;IAWtC,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAQZ,MAAM,IAAI,qBAAqB;IAe/B,WAAW,IAAI,OAAO;IAItB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIpC,UAAU,IAAI,SAAS,EAAE;IAOzB,KAAK,IAAI,QAAQ,CAAC,oBAAoB,CAAC;IAavC,OAAO,CAAC,SAAS;CA6BlB"}
@@ -0,0 +1,164 @@
1
+ // SSEZoneBridge - multiplexes v2 zone-event frames onto an EXISTING
2
+ // presence EventSource (per LOOM-DIRECTOR-PROTOCOL-V2 §2 + §4.2).
3
+ //
4
+ // CRITICAL: this bridge does NOT open its own EventSource. The
5
+ // presence transport (15.x SSEMultiplayerBridge) already has a live
6
+ // connection per peer; we just attach a listener for SSE frames whose
7
+ // `event:` line is `zone.event`. Per spec §1.1: "Transport reuses
8
+ // presence SSE. Same channel as 15.x presence updates, multiplexed by
9
+ // event topic. No second EventSource per peer."
10
+ //
11
+ // Wire shape per frame:
12
+ // event: zone.event
13
+ // data: <ZoneEventEnvelope JSON>
14
+ //
15
+ // Local-zone filter: the system layer (ZoneEventSystem) is the
16
+ // authoritative filter on "is this event for the local player's
17
+ // zone". The bridge buffers ALL zone events it receives so the system
18
+ // can log other-zone events for diagnostics if it chooses, then drop
19
+ // them before applying. The currentZone() callback is provided so the
20
+ // bridge can additionally short-circuit obvious-other-zone events at
21
+ // receive time when stats matter (e.g. a high-volume foreign zone).
22
+ // The default behaviour is to keep everything and let the system
23
+ // decide.
24
+ //
25
+ // Browser-only (constructor accepts EventSource which is a DOM API).
26
+ // In Node tests we exercise this via an injected fake EventSource
27
+ // (pattern parallel to SSEDirectorBridge.eventSourceFactory).
28
+ import { parseZoneEnvelopeJson } from './zone-event-envelope.js';
29
+ // SSE EventSource readyState constants. Mirrored locally to avoid
30
+ // pulling DOM types into the engine's strict Node test config.
31
+ const ES_OPEN = 1;
32
+ const ES_CLOSED = 2;
33
+ export class SSEZoneBridge {
34
+ es;
35
+ characterId;
36
+ currentZone;
37
+ eventName;
38
+ filterAtReceive;
39
+ listener = null;
40
+ queue = [];
41
+ statusValue = 'idle';
42
+ lastEventIdByZone = new Map();
43
+ statsValue = {
44
+ eventsReceived: 0,
45
+ reconnects: 0,
46
+ outOfOrderEvents: 0,
47
+ serverDropsP1: 0,
48
+ serverDropsP2: 0,
49
+ };
50
+ constructor(opts) {
51
+ this.es = opts.eventSource;
52
+ this.characterId = opts.characterId;
53
+ this.currentZone = opts.currentZone;
54
+ this.eventName = opts.eventName ?? 'zone.event';
55
+ this.filterAtReceive = opts.filterAtReceive ?? false;
56
+ // characterId is currently used for diagnostics only - reserved
57
+ // for future emitter_id-based UX hooks (e.g. local-cause cues).
58
+ void this.characterId;
59
+ }
60
+ start() {
61
+ if (this.listener)
62
+ return;
63
+ this.listener = (e) => { this.handleRaw(e); };
64
+ this.es.addEventListener(this.eventName, this.listener);
65
+ // The presence EventSource owns connect lifecycle. We mirror its
66
+ // current readyState so consumers can reason about isConnected().
67
+ if (this.es.readyState === ES_OPEN) {
68
+ this.statusValue = 'connected';
69
+ }
70
+ else if (this.es.readyState === ES_CLOSED) {
71
+ this.statusValue = 'closed';
72
+ }
73
+ else {
74
+ this.statusValue = 'connecting';
75
+ }
76
+ }
77
+ stop() {
78
+ this.statusValue = 'closed';
79
+ if (this.listener && this.es.removeEventListener) {
80
+ this.es.removeEventListener(this.eventName, this.listener);
81
+ }
82
+ this.listener = null;
83
+ }
84
+ status() {
85
+ // Refresh from the underlying ES if we're attached - the presence
86
+ // layer handles reconnects, so our cached status can lag.
87
+ if (this.listener) {
88
+ if (this.es.readyState === ES_OPEN) {
89
+ if (this.statusValue === 'connecting' || this.statusValue === 'reconnecting') {
90
+ this.statusValue = 'connected';
91
+ }
92
+ }
93
+ else if (this.es.readyState === ES_CLOSED) {
94
+ this.statusValue = 'closed';
95
+ }
96
+ }
97
+ return this.statusValue;
98
+ }
99
+ isConnected() {
100
+ return this.status() === 'connected';
101
+ }
102
+ getLastEventId(zone) {
103
+ return this.lastEventIdByZone.get(zone) ?? 0;
104
+ }
105
+ pollEvents() {
106
+ if (this.queue.length === 0)
107
+ return [];
108
+ const out = this.queue;
109
+ this.queue = [];
110
+ return out;
111
+ }
112
+ stats() {
113
+ return {
114
+ eventsReceived: this.statsValue.eventsReceived,
115
+ reconnects: this.statsValue.reconnects,
116
+ outOfOrderEvents: this.statsValue.outOfOrderEvents,
117
+ serverDropsP1: this.statsValue.serverDropsP1,
118
+ serverDropsP2: this.statsValue.serverDropsP2,
119
+ lastEventIdByZone: new Map(this.lastEventIdByZone),
120
+ };
121
+ }
122
+ // ----- Internal -----
123
+ handleRaw(e) {
124
+ const dataStr = typeof e.data === 'string' ? e.data : '';
125
+ const ev = parseZoneEnvelopeJson(dataStr);
126
+ if (!ev) {
127
+ // Malformed envelope - drop. The presence layer's EventSource
128
+ // keeps flowing; one bad frame doesn't kill the channel.
129
+ return;
130
+ }
131
+ if (this.filterAtReceive) {
132
+ // Optional optimization: drop foreign-zone events before they
133
+ // hit the queue. The system would have done it anyway.
134
+ const localZone = safeCurrentZone(this.currentZone);
135
+ if (localZone && ev.zone_id !== localZone) {
136
+ // Still track the highest id for that foreign zone so the
137
+ // bridge's per-zone last-id map is honest about what arrived.
138
+ const prev = this.lastEventIdByZone.get(ev.zone_id) ?? 0;
139
+ if (ev.id > prev)
140
+ this.lastEventIdByZone.set(ev.zone_id, ev.id);
141
+ return;
142
+ }
143
+ }
144
+ this.statsValue.eventsReceived++;
145
+ const prev = this.lastEventIdByZone.get(ev.zone_id) ?? 0;
146
+ if (ev.id > prev) {
147
+ this.lastEventIdByZone.set(ev.zone_id, ev.id);
148
+ }
149
+ else {
150
+ this.statsValue.outOfOrderEvents++;
151
+ }
152
+ this.queue.push(ev);
153
+ }
154
+ }
155
+ function safeCurrentZone(fn) {
156
+ try {
157
+ const z = fn();
158
+ return typeof z === 'string' && z.length > 0 ? z : null;
159
+ }
160
+ catch {
161
+ return null;
162
+ }
163
+ }
164
+ //# sourceMappingURL=sse-zone-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-zone-bridge.js","sourceRoot":"","sources":["../../../src/director/zone/sse-zone-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ZoneEvent } from './zone-event-envelope.js';
2
+ export type ZoneEventBridgeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'snapshot-required' | 'closed';
3
+ export interface ZoneEventBridgeStats {
4
+ eventsReceived: number;
5
+ reconnects: number;
6
+ outOfOrderEvents: number;
7
+ serverDropsP1: number;
8
+ serverDropsP2: number;
9
+ lastEventIdByZone: ReadonlyMap<string, number>;
10
+ }
11
+ export interface IZoneEventBridge {
12
+ start(): void;
13
+ stop(): void;
14
+ status(): ZoneEventBridgeStatus;
15
+ isConnected(): boolean;
16
+ getLastEventId(zone: string): number;
17
+ pollEvents(): ZoneEvent[];
18
+ stats(): Readonly<ZoneEventBridgeStats>;
19
+ }
20
+ export declare const RESOURCE_ZONE_EVENT_BRIDGE = "zone_event_bridge";
21
+ //# sourceMappingURL=zone-event-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-event-bridge.d.ts","sourceRoot":"","sources":["../../../src/director/zone/zone-event-bridge.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAC7B,MAAM,GACN,YAAY,GACZ,WAAW,GACX,cAAc,GACd,mBAAmB,GACnB,QAAQ,CAAC;AAEb,MAAM,WAAW,oBAAoB;IAEnC,cAAc,EAAE,MAAM,CAAC;IAEvB,UAAU,EAAE,MAAM,CAAC;IAEnB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IAGtB,iBAAiB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,IAAI,qBAAqB,CAAC;IAChC,WAAW,IAAI,OAAO,CAAC;IAEvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAErC,UAAU,IAAI,SAAS,EAAE,CAAC;IAC1B,KAAK,IAAI,QAAQ,CAAC,oBAAoB,CAAC,CAAC;CACzC;AAGD,eAAO,MAAM,0BAA0B,sBAAsB,CAAC"}
@@ -0,0 +1,24 @@
1
+ // IZoneEventBridge - abstraction over the v2 zone-event source.
2
+ //
3
+ // Concrete implementations:
4
+ // MockZoneBridge - in-process. enqueueIncoming(event) simulates
5
+ // server pushes for tests + offline demo.
6
+ // SSEZoneBridge - multiplexes onto an EXISTING presence
7
+ // EventSource. Listens for SSE frames whose
8
+ // `event:` line is `zone.event` (per spec §2.1).
9
+ //
10
+ // The ZoneEventSystem (PHASE_INPUT, AFTER DirectorSystem and
11
+ // PeerPresenceSystem) calls pollEvents() once per tick to drain queued
12
+ // events. Bridges buffer events between polls so the system's per-tick
13
+ // read is bounded.
14
+ //
15
+ // Per-zone monotonic id semantics (spec §8.1):
16
+ // - Each zone has its own id sequence starting at 1.
17
+ // - getLastEventId(zone) returns the highest id observed for that
18
+ // zone, or 0 if no events seen.
19
+ // - Out-of-order events are tracked in stats, but bridge-level
20
+ // reorder buffering lives in concrete impls (the contract here is
21
+ // the polled queue is best-effort ordered).
22
+ // Resource keys.
23
+ export const RESOURCE_ZONE_EVENT_BRIDGE = 'zone_event_bridge';
24
+ //# sourceMappingURL=zone-event-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-event-bridge.js","sourceRoot":"","sources":["../../../src/director/zone/zone-event-bridge.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,4BAA4B;AAC5B,kEAAkE;AAClE,6DAA6D;AAC7D,2DAA2D;AAC3D,+DAA+D;AAC/D,oEAAoE;AACpE,EAAE;AACF,6DAA6D;AAC7D,uEAAuE;AACvE,uEAAuE;AACvE,mBAAmB;AACnB,EAAE;AACF,+CAA+C;AAC/C,uDAAuD;AACvD,oEAAoE;AACpE,oCAAoC;AACpC,iEAAiE;AACjE,sEAAsE;AACtE,gDAAgD;AAwChD,iBAAiB;AACjB,MAAM,CAAC,MAAM,0BAA0B,GAAG,mBAAmB,CAAC"}
@@ -0,0 +1,90 @@
1
+ import type { DropSpec, EventPriority, KnotMood, KnotPaletteHex, NarratorVoice } from '../event-envelope.js';
2
+ export interface ZoneEventEnvelope<T extends ZoneEventType = ZoneEventType> {
3
+ id: number;
4
+ ts: number;
5
+ type: T;
6
+ zone_id: string;
7
+ emitter_id: string | null;
8
+ priority?: EventPriority;
9
+ data: ZoneEventDataMap[T];
10
+ }
11
+ export interface ZoneBossSpec {
12
+ boss_id: string;
13
+ type: string;
14
+ name: string;
15
+ hp_max: number;
16
+ hp_current: number;
17
+ dmg: number;
18
+ x: number;
19
+ y: number;
20
+ knot_flavor: string;
21
+ }
22
+ export interface ZoneBossSpawnData {
23
+ boss: ZoneBossSpec;
24
+ narrator_line: string | null;
25
+ }
26
+ export interface ZoneBossHit {
27
+ from_character_id: string;
28
+ amount: number;
29
+ ts_ms: number;
30
+ }
31
+ export interface ZoneBossTickData {
32
+ boss_id: string;
33
+ hp_current: number;
34
+ x: number;
35
+ y: number;
36
+ recent_hits: ReadonlyArray<ZoneBossHit>;
37
+ }
38
+ export type ZoneBossOutcome = 'killed' | 'despawned' | 'fled';
39
+ export interface ZoneBossEndData {
40
+ boss_id: string;
41
+ outcome: ZoneBossOutcome;
42
+ killer_character_id: string | null;
43
+ loot: ReadonlyArray<DropSpec>;
44
+ duration_ms: number;
45
+ }
46
+ export interface ZoneNarratorData {
47
+ line: string;
48
+ voice: NarratorVoice;
49
+ ttl_ms: number;
50
+ }
51
+ export interface ZoneKnotData {
52
+ knot: string;
53
+ palette: KnotPaletteHex;
54
+ mood: KnotMood;
55
+ fade_ms: number;
56
+ }
57
+ export interface ZoneStateChange {
58
+ key: string;
59
+ value: unknown;
60
+ }
61
+ export interface ZoneStateData {
62
+ changes: ReadonlyArray<ZoneStateChange>;
63
+ }
64
+ export interface ZoneSnapshotData {
65
+ active_boss: ZoneBossSpec | null;
66
+ knot: ZoneKnotData | null;
67
+ state: ReadonlyArray<ZoneStateChange>;
68
+ last_event_id: number;
69
+ }
70
+ export type ZoneEventType = 'zone.boss.spawn' | 'zone.boss.tick' | 'zone.boss.end' | 'zone.narrator' | 'zone.knot' | 'zone.state' | 'zone.snapshot';
71
+ export interface ZoneEventDataMap {
72
+ 'zone.boss.spawn': ZoneBossSpawnData;
73
+ 'zone.boss.tick': ZoneBossTickData;
74
+ 'zone.boss.end': ZoneBossEndData;
75
+ 'zone.narrator': ZoneNarratorData;
76
+ 'zone.knot': ZoneKnotData;
77
+ 'zone.state': ZoneStateData;
78
+ 'zone.snapshot': ZoneSnapshotData;
79
+ }
80
+ export type ZoneEvent = {
81
+ [K in ZoneEventType]: ZoneEventEnvelope<K>;
82
+ }[ZoneEventType];
83
+ export declare function priorityFor(type: ZoneEventType): EventPriority;
84
+ export declare class ZoneEventEnvelopeParseError extends Error {
85
+ readonly raw: unknown;
86
+ constructor(message: string, raw: unknown);
87
+ }
88
+ export declare function parseZoneEnvelope(raw: unknown): ZoneEvent;
89
+ export declare function parseZoneEnvelopeJson(json: string): ZoneEvent | null;
90
+ //# sourceMappingURL=zone-event-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-event-envelope.d.ts","sourceRoot":"","sources":["../../../src/director/zone/zone-event-envelope.ts"],"names":[],"mappings":"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"}