@sadhaka/loom-engine 0.14.0 → 0.16.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 (45) hide show
  1. package/dist/audio/audio-asset-cache.d.ts +12 -0
  2. package/dist/audio/audio-asset-cache.d.ts.map +1 -0
  3. package/dist/audio/audio-asset-cache.js +53 -0
  4. package/dist/audio/audio-asset-cache.js.map +1 -0
  5. package/dist/audio/audio-asset-loader.d.ts +16 -0
  6. package/dist/audio/audio-asset-loader.d.ts.map +1 -0
  7. package/dist/audio/audio-asset-loader.js +100 -0
  8. package/dist/audio/audio-asset-loader.js.map +1 -0
  9. package/dist/audio/audio-listener-resource.d.ts +19 -0
  10. package/dist/audio/audio-listener-resource.d.ts.map +1 -0
  11. package/dist/audio/audio-listener-resource.js +57 -0
  12. package/dist/audio/audio-listener-resource.js.map +1 -0
  13. package/dist/audio/cue-catalog.d.ts +39 -0
  14. package/dist/audio/cue-catalog.d.ts.map +1 -0
  15. package/dist/audio/cue-catalog.js +186 -0
  16. package/dist/audio/cue-catalog.js.map +1 -0
  17. package/dist/audio/music-director.d.ts +18 -0
  18. package/dist/audio/music-director.d.ts.map +1 -0
  19. package/dist/audio/music-director.js +177 -0
  20. package/dist/audio/music-director.js.map +1 -0
  21. package/dist/audio/spatial-audio-bus.d.ts +66 -0
  22. package/dist/audio/spatial-audio-bus.d.ts.map +1 -0
  23. package/dist/audio/spatial-audio-bus.js +341 -0
  24. package/dist/audio/spatial-audio-bus.js.map +1 -0
  25. package/dist/audio/spatial-audio-system.d.ts +17 -0
  26. package/dist/audio/spatial-audio-system.d.ts.map +1 -0
  27. package/dist/audio/spatial-audio-system.js +98 -0
  28. package/dist/audio/spatial-audio-system.js.map +1 -0
  29. package/dist/audio/zone-audio-system.d.ts +78 -0
  30. package/dist/audio/zone-audio-system.d.ts.map +1 -0
  31. package/dist/audio/zone-audio-system.js +206 -0
  32. package/dist/audio/zone-audio-system.js.map +1 -0
  33. package/dist/director/zone/zone-boss-entity-system.d.ts +9 -0
  34. package/dist/director/zone/zone-boss-entity-system.d.ts.map +1 -0
  35. package/dist/director/zone/zone-boss-entity-system.js +114 -0
  36. package/dist/director/zone/zone-boss-entity-system.js.map +1 -0
  37. package/dist/director/zone/zone-boss-entity.d.ts +30 -0
  38. package/dist/director/zone/zone-boss-entity.d.ts.map +1 -0
  39. package/dist/director/zone/zone-boss-entity.js +103 -0
  40. package/dist/director/zone/zone-boss-entity.js.map +1 -0
  41. package/dist/index.d.ts +17 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +18 -1
  44. package/dist/index.js.map +1 -1
  45. package/package.json +2 -2
@@ -0,0 +1,98 @@
1
+ // SpatialAudioSystem - pushes the local character's transform into
2
+ // the AudioListener resource and the SpatialAudioBus each frame.
3
+ //
4
+ // Phasing rationale (matches PeerRenderSystem / animation-system):
5
+ // PHASE_RENDER, AFTER any camera/transform sync systems, BEFORE
6
+ // the renderer submits draw calls. The listener pose for THIS frame
7
+ // reflects the THIS-FRAME world transform of the player - so a
8
+ // positional sound triggered by a render-phase system (e.g. cue
9
+ // reactions to zone events drained earlier in PHASE_LOGIC) hears
10
+ // the world from the up-to-date listener pose.
11
+ //
12
+ // Per LOOM-AUDIO-SPEC.md §3.3 the work is one-line: read the local
13
+ // character's TransformPool entry by entity id, write its (x, y, z)
14
+ // into the AudioListenerResource.pose, stamp lastUpdateFrame, and
15
+ // hand the pose to SpatialAudioBus.setListener.
16
+ //
17
+ // Identity binding: the engine doesn't know which entity is the local
18
+ // character - that's a consumer concern (TWT wires it after server
19
+ // auth completes). Consumers call setLocalCharacterEntity(entity) on
20
+ // the system. setLocalCharacterEntity(null) puts the system back into
21
+ // no-op mode (e.g. between zones during a teleport).
22
+ //
23
+ // Tolerates:
24
+ // - missing AudioListenerResource (silent no-op; engine probably
25
+ // not yet fully wired)
26
+ // - missing SpatialAudioBus (silent no-op; consumer might have
27
+ // opted out of spatial audio entirely - the system still keeps
28
+ // the resource fresh for debug/HUD readers)
29
+ // - missing TransformPool (silent no-op; engine without ECS
30
+ // transforms is unusual but valid in headless server tests)
31
+ // - local character set but transform not attached yet (no-op)
32
+ //
33
+ // What this system does NOT do (deferred to future phases):
34
+ // - rotate the listener with the player's facing (spec §8.4 fixes
35
+ // forward = (0, 0, -1))
36
+ // - apply velocity-based Doppler (PannerNode.setVelocity is
37
+ // deprecated; v1 ships flat doppler-off audio)
38
+ // - any per-source position update (handle.setPosition owns that)
39
+ import { entityIndex } from '../entity.js';
40
+ import { POOL_TRANSFORM } from '../world.js';
41
+ import { RESOURCE_TIME, } from '../resources.js';
42
+ import { RESOURCE_AUDIO_LISTENER, } from './audio-listener-resource.js';
43
+ export class SpatialAudioSystem {
44
+ name = 'spatial-audio';
45
+ localCharacter = null;
46
+ spatialBus = null;
47
+ // Optional spatial bus reference. The system can also work without
48
+ // a bus (resource-only mode) - useful for tests and for consumers
49
+ // who do not want positional audio but still want a tracked listener
50
+ // pose (e.g. for visual SFX triggered relative to the player).
51
+ constructor(opts = {}) {
52
+ this.spatialBus = opts.spatialBus ?? null;
53
+ }
54
+ // Wire (or rewire) the spatial bus. Useful when the AudioBus is
55
+ // created lazily after first user gesture - the system can be
56
+ // registered upfront and the bus attached later.
57
+ setSpatialBus(bus) {
58
+ this.spatialBus = bus;
59
+ }
60
+ // Bind the local character entity. null means "no local character"
61
+ // (e.g. mid-teleport, character not yet allocated, headless mode).
62
+ // The system tolerates an entity that doesn't have a transform yet
63
+ // by silently skipping the update.
64
+ setLocalCharacterEntity(entity) {
65
+ this.localCharacter = entity;
66
+ }
67
+ getLocalCharacterEntity() {
68
+ return this.localCharacter;
69
+ }
70
+ update(world, _dt) {
71
+ if (this.localCharacter === null)
72
+ return;
73
+ var listener = world.resources.get(RESOURCE_AUDIO_LISTENER);
74
+ if (!listener)
75
+ return;
76
+ var transforms = world.getPool(POOL_TRANSFORM);
77
+ if (!transforms)
78
+ return;
79
+ var idx = entityIndex(this.localCharacter);
80
+ // Bounds check against the high-water mark so a fresh entity that
81
+ // the consumer registered before attaching a transform doesn't
82
+ // produce undefined reads on the typed arrays.
83
+ if (idx >= transforms.getHighWaterMark())
84
+ return;
85
+ var x = transforms.x[idx] ?? 0;
86
+ var y = transforms.y[idx] ?? 0;
87
+ var z = transforms.z[idx] ?? 0;
88
+ listener.pose.x = x;
89
+ listener.pose.y = y;
90
+ listener.pose.z = z;
91
+ var time = world.resources.get(RESOURCE_TIME);
92
+ listener.lastUpdateFrame = time ? time.frame : listener.lastUpdateFrame + 1;
93
+ if (this.spatialBus) {
94
+ this.spatialBus.setListener(listener.pose);
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=spatial-audio-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-audio-system.js","sourceRoot":"","sources":["../../src/audio/spatial-audio-system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { System } from '../system.js';
2
+ import type { World } from '../world.js';
3
+ import type { ZoneEvent, ZoneEventType } from '../director/zone/zone-event-envelope.js';
4
+ export interface PositionalPlayOptionsStub {
5
+ x?: number;
6
+ y?: number;
7
+ z?: number;
8
+ gain?: number;
9
+ rate?: number;
10
+ loop?: boolean;
11
+ refDistance?: number;
12
+ maxDistance?: number;
13
+ rolloffFactor?: number;
14
+ distanceModel?: 'linear' | 'inverse' | 'exponential';
15
+ }
16
+ export interface AudioListenerPoseStub {
17
+ x: number;
18
+ y: number;
19
+ z?: number;
20
+ forward?: {
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ };
25
+ up?: {
26
+ x: number;
27
+ y: number;
28
+ z: number;
29
+ };
30
+ }
31
+ export interface AudioListenerResourceStub {
32
+ pose: AudioListenerPoseStub;
33
+ lastUpdateFrame: number;
34
+ }
35
+ export interface CueCatalogStub {
36
+ play(name: string, options?: PositionalPlayOptionsStub): unknown;
37
+ }
38
+ export interface MusicDirectorStub {
39
+ playMusic(name: string, fadeInMs?: number): void;
40
+ stopMusic(fadeOutMs?: number): Promise<void> | void;
41
+ crossfadeMusic(name: string, fadeMs?: number): void;
42
+ currentMusic(): string | null;
43
+ }
44
+ export declare const RESOURCE_AUDIO_LISTENER_STUB = "audio_listener";
45
+ export declare const RESOURCE_CUE_CATALOG_STUB = "cue_catalog";
46
+ export declare const RESOURCE_MUSIC_DIRECTOR_STUB = "music_director";
47
+ export interface ZoneCuePlay {
48
+ cue: string;
49
+ options?: PositionalPlayOptionsStub;
50
+ }
51
+ export interface ZoneAudioContext {
52
+ cues: CueCatalogStub | null;
53
+ music: MusicDirectorStub | null;
54
+ localZone: string | null;
55
+ listener: AudioListenerPoseStub;
56
+ }
57
+ export interface ZoneAudioMapping {
58
+ eventType: ZoneEventType;
59
+ handle(event: ZoneEvent, ctx: ZoneAudioContext): ZoneCuePlay | null;
60
+ }
61
+ export interface ZoneAudioSystemOptions {
62
+ currentZone?: () => string | null;
63
+ verbose?: boolean;
64
+ }
65
+ export declare class ZoneAudioSystem implements System {
66
+ readonly name: string;
67
+ private readonly mappings;
68
+ private readonly currentZone;
69
+ private readonly verbose;
70
+ private readonly lastProcessedIdByZone;
71
+ constructor(opts?: ZoneAudioSystemOptions);
72
+ registerMapping(mapping: ZoneAudioMapping): void;
73
+ unregisterMapping(eventType: ZoneEventType): void;
74
+ hasMapping(eventType: ZoneEventType): boolean;
75
+ mappingCount(): number;
76
+ update(world: World, _dt: number): void;
77
+ }
78
+ //# sourceMappingURL=zone-audio-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-audio-system.d.ts","sourceRoot":"","sources":["../../src/audio/zone-audio-system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,206 @@
1
+ // ZoneAudioSystem - zone-event audio integration shell (Phase 17, Track C).
2
+ //
3
+ // Runs PHASE_RENDER, AFTER ZoneEventSystem (which appends every
4
+ // observed zone event to the per-zone ring buffer in
5
+ // ZoneEventLog.byZone[<zone>].recent). Each tick the audio system
6
+ // drains the local zone's ring buffer for events that landed THIS
7
+ // frame (i.e. events with id strictly greater than the last id we
8
+ // processed for that zone), looks up the consumer-registered mapping
9
+ // for each event type, and dispatches the resulting cue play through
10
+ // the registered CueCatalog / MusicDirector resources.
11
+ //
12
+ // This file ships ZERO mappings. Engine consumers (TheWorldTable.ai,
13
+ // etc.) register their own mappings for the cues they want to fire on
14
+ // each zone event type.
15
+ //
16
+ // Tolerances:
17
+ // - Missing mapping for an event type: log + skip silently. Most
18
+ // consumers will register only a subset of the seven zone event
19
+ // types.
20
+ // - Missing CueCatalog / MusicDirector resources: no-op gracefully.
21
+ // The system keeps draining events but cue plays evaporate. This
22
+ // lets a consumer wire ZoneAudioSystem before the catalog is built
23
+ // (e.g. while audio assets are still loading) without crashing.
24
+ // - Missing ZoneEventLog: no-op (nothing to drain).
25
+ // - Missing AudioListener resource: cue plays still dispatch but
26
+ // `listener` in the handler context falls back to a zero pose.
27
+ // - Multiple mappings for the SAME event type: registerMapping
28
+ // overwrites. unregisterMapping by eventType.
29
+ //
30
+ // Dispatch ORDER: registerMapping registration order is irrelevant
31
+ // since each event type has at most ONE handler. The order in which
32
+ // events are processed within a tick is the order they appear in
33
+ // ZoneEventLog.byZone[<zone>].recent (newest first, per Phase 16
34
+ // log semantics). Per spec we replay newest-first so a consumer that
35
+ // registers a music crossfade reacts to the latest knot pulse before
36
+ // older ones; the zone-events log is a deduped ring, so duplicates
37
+ // are not a concern.
38
+ import { RESOURCE_ZONE_EVENT_LOG, } from '../director/zone/zone-event-log.js';
39
+ // Resource keys (defined here as duplicate string constants to keep
40
+ // the system standalone). The values MATCH the ones declared in the
41
+ // Track A / Track B files; coordination merge will swap to a single
42
+ // import after 0.15.0 assembles.
43
+ // TODO[phase-17-merge]: replace these constants with re-imports from
44
+ // audio-listener-resource.ts / cue-catalog.ts / music-director.ts.
45
+ export const RESOURCE_AUDIO_LISTENER_STUB = 'audio_listener';
46
+ export const RESOURCE_CUE_CATALOG_STUB = 'cue_catalog';
47
+ export const RESOURCE_MUSIC_DIRECTOR_STUB = 'music_director';
48
+ // ---- The system ----
49
+ export class ZoneAudioSystem {
50
+ name = 'zone-audio';
51
+ mappings;
52
+ currentZone;
53
+ verbose;
54
+ // Per-zone last processed event id. Drives the "events landed this
55
+ // frame" filter so the system reads the per-zone ring buffer once
56
+ // per tick and fires only fresh events.
57
+ lastProcessedIdByZone;
58
+ constructor(opts = {}) {
59
+ this.mappings = new Map();
60
+ this.currentZone = opts.currentZone ?? null;
61
+ this.verbose = opts.verbose ?? false;
62
+ this.lastProcessedIdByZone = new Map();
63
+ }
64
+ registerMapping(mapping) {
65
+ if (!mapping || typeof mapping.eventType !== 'string'
66
+ || typeof mapping.handle !== 'function') {
67
+ return;
68
+ }
69
+ this.mappings.set(mapping.eventType, mapping);
70
+ }
71
+ unregisterMapping(eventType) {
72
+ this.mappings.delete(eventType);
73
+ }
74
+ // For tests + introspection.
75
+ hasMapping(eventType) {
76
+ return this.mappings.has(eventType);
77
+ }
78
+ mappingCount() {
79
+ return this.mappings.size;
80
+ }
81
+ update(world, _dt) {
82
+ const log = world.resources.get(RESOURCE_ZONE_EVENT_LOG);
83
+ if (!log)
84
+ return;
85
+ const cues = world.resources.get(RESOURCE_CUE_CATALOG_STUB) ?? null;
86
+ const music = world.resources.get(RESOURCE_MUSIC_DIRECTOR_STUB) ?? null;
87
+ const listenerRes = world.resources.get(RESOURCE_AUDIO_LISTENER_STUB);
88
+ const listener = listenerRes
89
+ ? listenerRes.pose
90
+ : { x: 0, y: 0, z: 0 };
91
+ const localZone = safeCurrentZone(this.currentZone);
92
+ // Decide which zones to drain. With a local-zone filter set, we
93
+ // drain ONLY that zone; without it, we drain every zone present
94
+ // in the log (single-zone consumers + tests).
95
+ const zonesToDrain = [];
96
+ if (localZone !== null) {
97
+ if (log.byZone.has(localZone))
98
+ zonesToDrain.push(localZone);
99
+ }
100
+ else {
101
+ for (const z of log.byZone.keys())
102
+ zonesToDrain.push(z);
103
+ }
104
+ for (let zi = 0; zi < zonesToDrain.length; zi++) {
105
+ const zone = zonesToDrain[zi];
106
+ const entry = log.byZone.get(zone);
107
+ if (!entry)
108
+ continue;
109
+ // recent[] is newest-first per Phase 16 ZoneEventLog convention.
110
+ // We walk from oldest -> newest among UNSEEN events so handlers
111
+ // see events in chronological order (e.g. boss.spawn before
112
+ // boss.tick). Determine the cutoff by lastProcessedId for this
113
+ // zone.
114
+ const lastId = this.lastProcessedIdByZone.get(zone) ?? 0;
115
+ let maxIdSeen = lastId;
116
+ // Walk newest->oldest, collect unseen, then reverse so dispatch
117
+ // is oldest->newest.
118
+ const fresh = [];
119
+ for (let i = 0; i < entry.recent.length; i++) {
120
+ const ev = entry.recent[i];
121
+ if (!ev)
122
+ continue;
123
+ if (ev.id > lastId) {
124
+ fresh.push(ev);
125
+ if (ev.id > maxIdSeen)
126
+ maxIdSeen = ev.id;
127
+ }
128
+ else {
129
+ // Past the boundary; older events have already been
130
+ // processed.
131
+ break;
132
+ }
133
+ }
134
+ if (fresh.length === 0)
135
+ continue;
136
+ // Reverse to oldest-first.
137
+ for (let i = fresh.length - 1; i >= 0; i--) {
138
+ const ev = fresh[i];
139
+ const mapping = this.mappings.get(ev.type);
140
+ if (!mapping) {
141
+ if (this.verbose) {
142
+ try {
143
+ console.log('[zone-audio] no mapping for ' + ev.type + ' (zone='
144
+ + ev.zone_id + ', id=' + ev.id + ')');
145
+ }
146
+ catch { /* ignore */ }
147
+ }
148
+ continue;
149
+ }
150
+ const ctx = {
151
+ cues,
152
+ music,
153
+ localZone,
154
+ listener,
155
+ };
156
+ let result = null;
157
+ try {
158
+ result = mapping.handle(ev, ctx);
159
+ }
160
+ catch (err) {
161
+ if (this.verbose) {
162
+ try {
163
+ console.warn('[zone-audio] mapping handle threw', err);
164
+ }
165
+ catch { /* ignore */ }
166
+ }
167
+ result = null;
168
+ }
169
+ if (!result)
170
+ continue;
171
+ if (typeof result.cue !== 'string' || result.cue.length === 0)
172
+ continue;
173
+ if (!cues) {
174
+ // Catalog absent: silently drop the cue. The handler
175
+ // already ran (e.g. it called ctx.music.crossfadeMusic) so
176
+ // music side-effects still happen.
177
+ continue;
178
+ }
179
+ try {
180
+ cues.play(result.cue, result.options);
181
+ }
182
+ catch (err) {
183
+ if (this.verbose) {
184
+ try {
185
+ console.warn('[zone-audio] cues.play threw', err);
186
+ }
187
+ catch { /* ignore */ }
188
+ }
189
+ }
190
+ }
191
+ this.lastProcessedIdByZone.set(zone, maxIdSeen);
192
+ }
193
+ }
194
+ }
195
+ function safeCurrentZone(fn) {
196
+ if (!fn)
197
+ return null;
198
+ try {
199
+ const z = fn();
200
+ return typeof z === 'string' && z.length > 0 ? z : null;
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ }
206
+ //# sourceMappingURL=zone-audio-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-audio-system.js","sourceRoot":"","sources":["../../src/audio/zone-audio-system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { System } from '../../system.js';
2
+ import type { World } from '../../world.js';
3
+ export declare class ZoneBossEntitySystem implements System {
4
+ readonly name: string;
5
+ private readonly cursors;
6
+ update(world: World, _dt: number): void;
7
+ cursorFor(zoneId: string): number;
8
+ }
9
+ //# sourceMappingURL=zone-boss-entity-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-boss-entity-system.d.ts","sourceRoot":"","sources":["../../../src/director/zone/zone-boss-entity-system.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAgB5C,qBAAa,oBAAqB,YAAW,MAAM;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAsB;IAI3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAkCvC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAGlC"}
@@ -0,0 +1,114 @@
1
+ // ZoneBossEntitySystem - converts zone events into a typed
2
+ // ZoneBossEntityResource that any renderer (Three.js, Canvas2D, etc.)
3
+ // can poll each frame without parsing SSE protocol envelopes
4
+ // (LOOM-BOSS-RENDER-SPEC §3.2).
5
+ //
6
+ // Runs in PHASE_LOGIC, AFTER ZoneEventSystem (which runs in PHASE_INPUT
7
+ // and is the source of truth on event ordering + ZoneEventLog state).
8
+ // This system is a downstream projection of ZoneEventLog into a
9
+ // renderer-friendly entity shape.
10
+ //
11
+ // Cursor strategy: per-zone lastProcessedEventId. Each tick we scan
12
+ // ZoneEventLog.byZone[zid].recent (newest-first ring) and process any
13
+ // events with id > cursor, then advance the cursor. The ring caps at
14
+ // ZONE_RING_SIZE = 32 entries; if more than 32 events landed for one
15
+ // zone in a single frame, some are lost. Acceptable in practice
16
+ // (60 Hz tick vs server bursts of <32/frame) since zone.boss.end +
17
+ // zone.snapshot are P0 priority and will not be dropped by the bridge.
18
+ //
19
+ // Dispatch table:
20
+ //
21
+ // zone.boss.spawn -> entities.byZone[zid] = buildEntityFromSpawn(env)
22
+ // zone.boss.tick -> if boss_id matches current, applyTick; else ignore
23
+ // zone.boss.end -> if boss_id matches current, clear to null
24
+ // zone.snapshot -> replace wholesale (entity if active_boss non-null,
25
+ // null otherwise)
26
+ // other -> no-op
27
+ //
28
+ // v1 supports at most one active boss per zone (matches Phase 16 spec).
29
+ import { RESOURCE_ZONE_EVENT_LOG, } from './zone-event-log.js';
30
+ import { RESOURCE_ZONE_BOSS_ENTITY, buildEntityFromSpawn, applyTick, } from './zone-boss-entity.js';
31
+ export class ZoneBossEntitySystem {
32
+ name = 'zone-boss-entity';
33
+ // Per-zone last-processed event id. Events with id > cursor are new
34
+ // since the previous frame.
35
+ cursors = new Map();
36
+ update(world, _dt) {
37
+ const log = world.resources.get(RESOURCE_ZONE_EVENT_LOG);
38
+ const entities = world.resources.get(RESOURCE_ZONE_BOSS_ENTITY);
39
+ if (!log || !entities)
40
+ return;
41
+ for (const [zoneId, entry] of log.byZone) {
42
+ const cursor = this.cursors.get(zoneId) ?? 0;
43
+ // Walk the newest-first ring. Collect events with id > cursor,
44
+ // then reverse to apply oldest-first.
45
+ const fresh = [];
46
+ for (let i = 0; i < entry.recent.length; i++) {
47
+ const ev = entry.recent[i];
48
+ if (!ev)
49
+ continue;
50
+ if (ev.id <= cursor)
51
+ break;
52
+ fresh.push(ev);
53
+ }
54
+ if (fresh.length === 0)
55
+ continue;
56
+ fresh.reverse();
57
+ let highestId = cursor;
58
+ for (let i = 0; i < fresh.length; i++) {
59
+ const ev = fresh[i];
60
+ if (!ev)
61
+ continue;
62
+ if (ev.id > highestId)
63
+ highestId = ev.id;
64
+ applyEventToEntity(ev, zoneId, entities);
65
+ }
66
+ this.cursors.set(zoneId, highestId);
67
+ }
68
+ }
69
+ // Diagnostic accessor for tests + debug HUDs.
70
+ cursorFor(zoneId) {
71
+ return this.cursors.get(zoneId) ?? 0;
72
+ }
73
+ }
74
+ function applyEventToEntity(ev, zoneId, entities) {
75
+ switch (ev.type) {
76
+ case 'zone.boss.spawn': {
77
+ entities.byZone.set(zoneId, buildEntityFromSpawn(ev));
78
+ break;
79
+ }
80
+ case 'zone.boss.tick': {
81
+ const e = ev;
82
+ const current = entities.byZone.get(zoneId);
83
+ if (!current)
84
+ return;
85
+ if (current.boss_id !== e.data.boss_id)
86
+ return;
87
+ applyTick(current, ev);
88
+ break;
89
+ }
90
+ case 'zone.boss.end': {
91
+ const e = ev;
92
+ const current = entities.byZone.get(zoneId);
93
+ if (current && current.boss_id === e.data.boss_id) {
94
+ entities.byZone.set(zoneId, null);
95
+ }
96
+ break;
97
+ }
98
+ case 'zone.snapshot': {
99
+ const e = ev;
100
+ if (e.data.active_boss) {
101
+ entities.byZone.set(zoneId, buildEntityFromSpawn(ev));
102
+ }
103
+ else {
104
+ entities.byZone.set(zoneId, null);
105
+ }
106
+ break;
107
+ }
108
+ default:
109
+ // Other event types (zone.narrator, zone.knot, zone.state) do
110
+ // not affect the boss entity. Pure no-op.
111
+ break;
112
+ }
113
+ }
114
+ //# sourceMappingURL=zone-boss-entity-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-boss-entity-system.js","sourceRoot":"","sources":["../../../src/director/zone/zone-boss-entity-system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { ZoneEvent } from './zone-event-envelope.js';
2
+ export interface ZoneBossHitRecord {
3
+ amount: number;
4
+ at_ms: number;
5
+ from_character_id: string;
6
+ }
7
+ export interface ZoneBossEntity {
8
+ zone_id: string;
9
+ boss_id: string;
10
+ name: string;
11
+ type: string;
12
+ hp_max: number;
13
+ hp_current: number;
14
+ dmg: number;
15
+ x: number;
16
+ y: number;
17
+ knot_flavor: string;
18
+ spawned_at_ms: number;
19
+ last_tick_ms: number;
20
+ recent_hits: ZoneBossHitRecord[];
21
+ }
22
+ export interface ZoneBossEntityResource {
23
+ byZone: Map<string, ZoneBossEntity | null>;
24
+ }
25
+ export declare const RESOURCE_ZONE_BOSS_ENTITY = "zone_boss_entity";
26
+ export declare const RECENT_HITS_RING_SIZE = 32;
27
+ export declare function createZoneBossEntityResource(): ZoneBossEntityResource;
28
+ export declare function buildEntityFromSpawn(env: ZoneEvent): ZoneBossEntity;
29
+ export declare function applyTick(entity: ZoneBossEntity, env: ZoneEvent): void;
30
+ //# sourceMappingURL=zone-boss-entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-boss-entity.d.ts","sourceRoot":"","sources":["../../../src/director/zone/zone-boss-entity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAEV,SAAS,EAEV,MAAM,0BAA0B,CAAC;AAIlC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAGb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,WAAW,EAAE,MAAM,CAAC;IAGpB,aAAa,EAAE,MAAM,CAAC;IAItB,YAAY,EAAE,MAAM,CAAC;IAKrB,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IAGrC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC;CAC5C;AAED,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAI5D,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAIxC,wBAAgB,4BAA4B,IAAI,sBAAsB,CAErE;AAcD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,SAAS,GAAG,cAAc,CAcnE;AAyBD,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CAyBtE"}