@skewedaspect/sage-core 0.10.0-beta.1 → 0.10.0-beta.2
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.
- package/dist/gameEntity.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/sage-core.js +54 -48
- package/dist/sage-core.js.map +1 -1
- package/dist/stateMachineBehavior.d.ts +15 -0
- package/package.json +1 -1
package/dist/gameEntity.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type DeepReadonly<Value> = Value extends (infer Item)[] ? readonly DeepRe
|
|
|
11
11
|
export interface GameEntityState {
|
|
12
12
|
tags: string[];
|
|
13
13
|
}
|
|
14
|
-
export declare function copyStateBag(bag: Readonly<StateBag
|
|
14
|
+
export declare function copyStateBag(bag: Readonly<StateBag>, id: string): StateBag;
|
|
15
15
|
type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Intersection) => void ? Intersection : never;
|
|
16
16
|
type StateOf<Class> = Class extends new () => {
|
|
17
17
|
state: infer State;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export { Behavior } from "./behavior.d.ts";
|
|
|
5
5
|
export type { BehaviorClass } from "./behavior.d.ts";
|
|
6
6
|
export { NamedBehavior } from "./namedBehavior.d.ts";
|
|
7
7
|
export type { NamedState } from "./namedBehavior.d.ts";
|
|
8
|
+
export { defineStates, StateMachineBehavior } from "./stateMachineBehavior.d.ts";
|
|
9
|
+
export type { StateDefinition } from "./stateMachineBehavior.d.ts";
|
|
8
10
|
export { dilationLimit, drainPassLimit, operationCallDepthLimit } from "./constants.d.ts";
|
|
9
11
|
export type { EventSubscription, GameEvent, Subscriber, Unsubscribe } from "./events.d.ts";
|
|
10
12
|
export { EventBus } from "./eventBus.d.ts";
|
package/dist/sage-core.js
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
//#region src/behavior.ts
|
|
2
2
|
var e = class {}, t = class extends e {
|
|
3
3
|
defaults = { displayName: "" };
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/stateMachineBehavior.ts
|
|
7
|
+
function n(e, t) {
|
|
8
|
+
return t;
|
|
9
|
+
}
|
|
10
|
+
var r = class extends e {
|
|
11
|
+
static ops = ["transitionTo"];
|
|
12
|
+
onUpdate(e) {
|
|
13
|
+
let t = this.state.currentState;
|
|
14
|
+
this.states[t].update?.call(this, e);
|
|
15
|
+
}
|
|
16
|
+
transitionTo(e) {
|
|
17
|
+
if (!(e in this.states)) throw Error(`StateMachineBehavior: no state named "${e}" is declared.`);
|
|
18
|
+
if (e === this.state.currentState) return;
|
|
19
|
+
let t = this.state.currentState;
|
|
20
|
+
this.states[t].exit?.call(this, e), this.state.currentState = e, this.states[e].enter?.call(this, t);
|
|
21
|
+
}
|
|
22
|
+
}, i = 32, a = 64, o = 5;
|
|
5
23
|
//#endregion
|
|
6
24
|
//#region src/events.ts
|
|
7
|
-
function
|
|
25
|
+
function s(e) {
|
|
8
26
|
let t = e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
|
|
9
27
|
return RegExp(`^${t}$`);
|
|
10
28
|
}
|
|
11
29
|
//#endregion
|
|
12
30
|
//#region src/eventBus.ts
|
|
13
|
-
var
|
|
31
|
+
var c = class e {
|
|
14
32
|
#e = [];
|
|
15
33
|
#t = [];
|
|
16
34
|
#n = /* @__PURE__ */ new Map();
|
|
@@ -23,7 +41,7 @@ var o = class e {
|
|
|
23
41
|
let t = i;
|
|
24
42
|
if (t.endsWith(":*")) {
|
|
25
43
|
let n = {
|
|
26
|
-
pattern:
|
|
44
|
+
pattern: s(t),
|
|
27
45
|
subscriber: e
|
|
28
46
|
};
|
|
29
47
|
r.push(n), this.#t.push(n);
|
|
@@ -91,23 +109,31 @@ var o = class e {
|
|
|
91
109
|
};
|
|
92
110
|
//#endregion
|
|
93
111
|
//#region src/gameEntity.ts
|
|
94
|
-
function
|
|
112
|
+
function l(e, t) {
|
|
113
|
+
throw TypeError(`Entity "${e}" state holds a value that is not finite JSON-compatible data at "${t}".`);
|
|
114
|
+
}
|
|
115
|
+
function u(e, t, n, r) {
|
|
116
|
+
if (e === null || typeof e == "string" || typeof e == "boolean") return e;
|
|
117
|
+
if (typeof e == "number") return Number.isFinite(e) || l(t, n), e;
|
|
95
118
|
if (Array.isArray(e)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
r.has(e) && l(t, n), r.add(e);
|
|
120
|
+
let i = [];
|
|
121
|
+
for (let [a, o] of e.entries()) i.push(u(o, t, `${n}[${a}]`, r));
|
|
122
|
+
return r.delete(e), i;
|
|
123
|
+
}
|
|
124
|
+
if (typeof e == "object") {
|
|
125
|
+
let i = Object.getPrototypeOf(e);
|
|
126
|
+
(i !== Object.prototype && i !== null || r.has(e)) && l(t, n), r.add(e);
|
|
127
|
+
let a = Object.create(null);
|
|
128
|
+
for (let [i, o] of Object.entries(e)) a[i] = u(o, t, n === "" ? i : `${n}.${i}`, r);
|
|
129
|
+
return r.delete(e), a;
|
|
130
|
+
}
|
|
131
|
+
return l(t, n);
|
|
106
132
|
}
|
|
107
|
-
function
|
|
108
|
-
return
|
|
133
|
+
function d(e, t) {
|
|
134
|
+
return u(e, t, "", /* @__PURE__ */ new Set());
|
|
109
135
|
}
|
|
110
|
-
var
|
|
136
|
+
var f = class e {
|
|
111
137
|
id;
|
|
112
138
|
name;
|
|
113
139
|
context;
|
|
@@ -172,7 +198,7 @@ var l = class e {
|
|
|
172
198
|
}
|
|
173
199
|
receive(t) {
|
|
174
200
|
let n = t;
|
|
175
|
-
for (let r of this.#t) if (e.#
|
|
201
|
+
for (let r of this.#t) if (e.#f(r, n.type) && r.instance.onEvent?.(t) === !0) return !0;
|
|
176
202
|
return !1;
|
|
177
203
|
}
|
|
178
204
|
destroy() {
|
|
@@ -193,7 +219,7 @@ var l = class e {
|
|
|
193
219
|
let r = [], i = [];
|
|
194
220
|
for (let e of t.events ?? []) {
|
|
195
221
|
let t = e;
|
|
196
|
-
t.endsWith(":*") ? i.push(
|
|
222
|
+
t.endsWith(":*") ? i.push(s(t)) : r.push(t), n.includes(e) || n.push(e);
|
|
197
223
|
}
|
|
198
224
|
(r.length > 0 || i.length > 0) && this.#t.push({
|
|
199
225
|
instance: e,
|
|
@@ -226,14 +252,10 @@ var l = class e {
|
|
|
226
252
|
static #u(t, n, r) {
|
|
227
253
|
let i = Object.create(null);
|
|
228
254
|
i.tags = [];
|
|
229
|
-
for (let
|
|
230
|
-
return Object.assign(i,
|
|
255
|
+
for (let e of t) Object.assign(i, d(e.defaults, r));
|
|
256
|
+
return Object.assign(i, d(n, r)), e.#d(i, r), i;
|
|
231
257
|
}
|
|
232
|
-
static #d(
|
|
233
|
-
if (!e.#h(t, /* @__PURE__ */ new Set())) throw TypeError(`Entity "${n}" state holds a value that is not finite JSON-compatible data.`);
|
|
234
|
-
return c(t);
|
|
235
|
-
}
|
|
236
|
-
static #f(e, t) {
|
|
258
|
+
static #d(e, t) {
|
|
237
259
|
let n = e.tags;
|
|
238
260
|
if (!Array.isArray(n)) throw TypeError(`Entity "${t}" tags must be a list of strings.`);
|
|
239
261
|
let r = [];
|
|
@@ -243,29 +265,13 @@ var l = class e {
|
|
|
243
265
|
r.push(e);
|
|
244
266
|
}
|
|
245
267
|
}
|
|
246
|
-
static #
|
|
247
|
-
return t === null || typeof t == "string" || typeof t == "boolean" ? !0 : typeof t == "number" ? Number.isFinite(t) : Array.isArray(t) ? e.#m(t, n) : typeof t == "object" && e.#h(t, n);
|
|
248
|
-
}
|
|
249
|
-
static #m(t, n) {
|
|
250
|
-
if (n.has(t)) return !1;
|
|
251
|
-
n.add(t);
|
|
252
|
-
for (let r of t) if (!e.#p(r, n)) return n.delete(t), !1;
|
|
253
|
-
return n.delete(t), !0;
|
|
254
|
-
}
|
|
255
|
-
static #h(t, n) {
|
|
256
|
-
let r = Object.getPrototypeOf(t);
|
|
257
|
-
if (r !== Object.prototype && r !== null || n.has(t)) return !1;
|
|
258
|
-
n.add(t);
|
|
259
|
-
for (let r of Object.values(t)) if (!e.#p(r, n)) return n.delete(t), !1;
|
|
260
|
-
return n.delete(t), !0;
|
|
261
|
-
}
|
|
262
|
-
static #g(e, t) {
|
|
268
|
+
static #f(e, t) {
|
|
263
269
|
if (e.exact.includes(t)) return !0;
|
|
264
270
|
for (let n of e.patterns) if (n.test(t)) return !0;
|
|
265
271
|
return !1;
|
|
266
272
|
}
|
|
267
|
-
},
|
|
268
|
-
#e = new
|
|
273
|
+
}, p = class e {
|
|
274
|
+
#e = new c();
|
|
269
275
|
#t;
|
|
270
276
|
#n;
|
|
271
277
|
#r;
|
|
@@ -317,7 +323,7 @@ var l = class e {
|
|
|
317
323
|
for (let t of this.#s) e.push({
|
|
318
324
|
id: t.id,
|
|
319
325
|
name: t.name,
|
|
320
|
-
state:
|
|
326
|
+
state: d(t.state, t.id)
|
|
321
327
|
});
|
|
322
328
|
return {
|
|
323
329
|
tick: this.#i,
|
|
@@ -373,6 +379,6 @@ var l = class e {
|
|
|
373
379
|
}
|
|
374
380
|
};
|
|
375
381
|
//#endregion
|
|
376
|
-
export { e as Behavior,
|
|
382
|
+
export { e as Behavior, c as EventBus, f as GameEntity, p as GameSimulationManager, t as NamedBehavior, r as StateMachineBehavior, n as defineStates, o as dilationLimit, a as drainPassLimit, i as operationCallDepthLimit };
|
|
377
383
|
|
|
378
384
|
//# sourceMappingURL=sage-core.js.map
|
package/dist/sage-core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sage-core.js","names":[],"sources":["../src/behavior.ts","../src/namedBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<State extends object, Requires extends object = object>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : GameEntity;\n\n abstract readonly defaults : State;\n\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n}\n\nexport interface BehaviorClass\n{\n new () : Behavior<object, object>;\n readonly ops ?: readonly PropertyKey[];\n readonly events ?: readonly EventSubscription[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NamedBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface NamedState\n{\n displayName : string;\n}\n\nexport class NamedBehavior extends Behavior<NamedState>\n{\n readonly defaults : NamedState = { displayName: '' };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const operationCallDepthLimit = 32;\n\nexport const drainPassLimit = 64;\n\nexport const dilationLimit = 5;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Events\n//\n// `EventMap` is declared in the package root because a `declare module` augmentation only merges into the module that\n// declares the interface, and the root is the only module a consumer can address.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventMap } from './index.ts';\n\nexport type { EventMap };\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GameEvent = {\n [Type in keyof EventMap & string] : {\n type : Type;\n senderID : string;\n targetID ?: string;\n payload : EventMap[Type];\n }\n}[keyof EventMap & string];\n\ntype EventPrefix<Type extends string> = Type extends `${ infer Prefix }:${ infer Rest }`\n ? Prefix | `${ Prefix }:${ EventPrefix<Rest> }`\n : never;\n\nexport type EventSubscription = (keyof EventMap & string) | `${ EventPrefix<keyof EventMap & string> }:*`;\n\nexport type Unsubscribe = () => void;\n\nexport interface Subscriber\n{\n readonly id : string;\n receive(event : GameEvent) : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The empty map boundary\n//----------------------------------------------------------------------------------------------------------------------\n\n// Until a game augments `EventMap`, it holds nothing, `GameEvent` is `never` everywhere inside this package, and no\n// event value can be built against it. `RawGameEvent` is the shape an event has whatever the map holds, and it is the\n// only place core steps outside the typed vocabulary: it stays internal, and every public signature keeps `GameEvent`.\nexport interface RawGameEvent\n{\n type : string;\n senderID : string;\n targetID ?: string;\n payload : unknown;\n}\n\n// A pattern spans every remaining segment, so `door:*` matches `door:opened` and `door:lock:opened` alike.\nexport function compileEventPattern(subscription : string) : RegExp\n{\n const escaped = subscription\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\\\\\*/g, '.*');\n\n return new RegExp(`^${ escaped }$`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Event Bus\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { drainPassLimit } from './constants.ts';\nimport {\n type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface PatternSubscription\n{\n pattern : RegExp;\n subscriber : Subscriber;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class EventBus\n{\n #queue : RawGameEvent[] = [];\n #patterns : PatternSubscription[] = [];\n\n readonly #subscribers = new Map<string, Subscriber>();\n readonly #exact = new Map<string, Subscriber[]>();\n\n register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n if(this.#subscribers.has(subscriber.id))\n {\n throw new Error(`A subscriber is already registered under the ID \"${ subscriber.id }\".`);\n }\n\n this.#subscribers.set(subscriber.id, subscriber);\n\n const exactNames : string[] = [];\n const patternEntries : PatternSubscription[] = [];\n\n for(const subscription of subscriptions)\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n const entry = { pattern: compileEventPattern(name), subscriber };\n patternEntries.push(entry);\n this.#patterns.push(entry);\n }\n else if(!exactNames.includes(name))\n {\n exactNames.push(name);\n this.#addExact(name, subscriber);\n }\n }\n\n return this.#teardown(subscriber, exactNames, patternEntries);\n }\n\n emit(event : GameEvent) : void\n {\n this.#queue.push(event as RawGameEvent);\n }\n\n drain() : void\n {\n const errors : unknown[] = [];\n let passes = 0;\n\n while(this.#queue.length > 0)\n {\n passes += 1;\n\n if(passes > drainPassLimit)\n {\n this.#queue = [];\n errors.push(new Error(`The drain exceeded ${ drainPassLimit } passes.`));\n break;\n }\n\n const pass = this.#queue;\n this.#queue = [];\n\n for(const event of pass)\n {\n this.#deliver(event, errors);\n }\n }\n\n if(errors.length > 0)\n {\n throw new AggregateError(errors, 'The drain raised.');\n }\n }\n\n #addExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name) ?? [];\n subscribers.push(subscriber);\n this.#exact.set(name, subscribers);\n }\n\n #removeExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name);\n\n if(subscribers === undefined)\n {\n return;\n }\n\n const index = subscribers.indexOf(subscriber);\n\n if(index >= 0)\n {\n subscribers.splice(index, 1);\n }\n\n if(subscribers.length === 0)\n {\n this.#exact.delete(name);\n }\n }\n\n // A teardown is idempotent, and it evicts the subscriber under its ID only while that is still the registration it\n // was handed out for; a later registration that took the same ID survives a stale teardown.\n #teardown(\n subscriber : Subscriber,\n exactNames : readonly string[],\n patternEntries : readonly PatternSubscription[]\n ) : Unsubscribe\n {\n let active = true;\n\n return () : void =>\n {\n if(!active)\n {\n return;\n }\n\n active = false;\n\n if(this.#subscribers.get(subscriber.id) === subscriber)\n {\n this.#subscribers.delete(subscriber.id);\n }\n\n for(const name of exactNames)\n {\n this.#removeExact(name, subscriber);\n }\n\n const remaining : PatternSubscription[] = [];\n\n for(const entry of this.#patterns)\n {\n if(!patternEntries.includes(entry))\n {\n remaining.push(entry);\n }\n }\n\n this.#patterns = remaining;\n };\n }\n\n #deliver(event : RawGameEvent, errors : unknown[]) : void\n {\n if(event.targetID !== undefined)\n {\n const addressed = this.#subscribers.get(event.targetID);\n\n if(addressed !== undefined)\n {\n EventBus.#handOff(addressed, event, errors);\n }\n\n return;\n }\n\n const delivered = new Set<Subscriber>();\n\n for(const subscriber of [ ...this.#exact.get(event.type) ?? [] ])\n {\n EventBus.#handOffOnce(subscriber, event, delivered, errors);\n }\n\n for(const entry of [ ...this.#patterns ])\n {\n if(entry.pattern.test(event.type))\n {\n EventBus.#handOffOnce(entry.subscriber, event, delivered, errors);\n }\n }\n }\n\n static #handOffOnce(\n subscriber : Subscriber,\n event : RawGameEvent,\n delivered : Set<Subscriber>,\n errors : unknown[]\n ) : void\n {\n if(delivered.has(subscriber))\n {\n return;\n }\n\n delivered.add(subscriber);\n EventBus.#handOff(subscriber, event, errors);\n }\n\n static #handOff(subscriber : Subscriber, event : RawGameEvent, errors : unknown[]) : void\n {\n try\n {\n subscriber.receive(event as GameEvent);\n }\n catch(error)\n {\n errors.push(error);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { operationCallDepthLimit } from './constants.ts';\nimport {\n type EventMap, type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\nimport type { Behavior, BehaviorClass } from './behavior.ts';\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n// State\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type StateValue = null | boolean | number | string | StateValue[] | { [key : string] : StateValue };\n\nexport type StateBag = Record<string, StateValue>;\n\nexport type DeepReadonly<Value> = Value extends (infer Item)[]\n ? readonly DeepReadonly<Item>[]\n : Value extends object\n ? { readonly [Key in keyof Value] : DeepReadonly<Value[Key]> }\n : Value;\n\nexport interface GameEntityState\n{\n tags : string[];\n}\n\n// Records are copied with no prototype, so a state field named `__proto__` stays an ordinary field of the copy.\nfunction copyStateValue(value : StateValue) : StateValue\n{\n if(Array.isArray(value))\n {\n const items : StateValue[] = [];\n\n for(const item of value)\n {\n items.push(copyStateValue(item));\n }\n\n return items;\n }\n\n if(value !== null && typeof value === 'object')\n {\n const copy = Object.create(null) as StateBag;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = copyStateValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\nexport function copyStateBag(bag : Readonly<StateBag>) : StateBag\n{\n return copyStateValue(bag) as StateBag;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Composition\n//----------------------------------------------------------------------------------------------------------------------\n\ntype UnionToIntersection<Union>\n = (Union extends unknown ? (value : Union) => void : never) extends (value : infer Intersection) => void\n ? Intersection\n : never;\n\ntype StateOf<Class> = Class extends new () => { state : infer State } ? State : never;\n\ntype OperationNames<Class> = Class extends { ops : readonly (infer Name)[] } ? Name & PropertyKey : never;\n\ntype OpsFrom<Class> = Class extends BehaviorClass\n ? Pick<InstanceType<Class>, OperationNames<Class> & keyof InstanceType<Class>>\n : never;\n\ntype RequiresOf<Class> = Class extends new () => { ops : infer Requires } ? Requires : never;\n\nexport type StateFor<Behaviors extends readonly BehaviorClass[]>\n = GameEntityState & UnionToIntersection<StateOf<Behaviors[number]>> & object;\n\nexport type OpsFor<Behaviors extends readonly BehaviorClass[]>\n = UnionToIntersection<OpsFrom<Behaviors[number]>> & object;\n\nexport type OpsOf<Class extends BehaviorClass> = OpsFrom<Class>;\n\nexport type StateSliceOf<Class extends BehaviorClass> = StateOf<Class>;\n\ninterface Unsatisfied<Missing> { 'ERROR: a behavior requires operations the entity does not provide' : Missing }\ninterface DuplicateOperation<Name> { 'ERROR: two behaviors expose the same operation name' : Name }\ninterface IncompatibleState<Name> { 'ERROR: two behaviors declare one state field with incompatible types' : Name }\n\ntype ValidateRequires<Behaviors extends readonly BehaviorClass[]> = {\n [Index in keyof Behaviors] : OpsFor<Behaviors> extends RequiresOf<Behaviors[Index]>\n ? Behaviors[Index]\n : Unsatisfied<Exclude<keyof RequiresOf<Behaviors[Index]>, keyof OpsFor<Behaviors>>>\n};\n\ntype DuplicateNames<Behaviors extends readonly unknown[], Seen extends PropertyKey = never>\n = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? [Extract<OperationNames<Head>, Seen>] extends [never]\n ? DuplicateNames<Tail, Seen | OperationNames<Head>>\n : Extract<OperationNames<Head>, Seen> | DuplicateNames<Tail, Seen | OperationNames<Head>>\n : never;\n\ntype IncompatibleKeys<First, Second> = {\n [Key in keyof First & keyof Second] : [First[Key]] extends [Second[Key]]\n ? [Second[Key]] extends [First[Key]]\n ? never\n : Key\n : Key;\n}[keyof First & keyof Second];\n\ntype IncompatibleStateKeys<\n Behaviors extends readonly unknown[],\n SeenState extends object = object,\n> = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? IncompatibleKeys<SeenState, StateOf<Head>> | IncompatibleStateKeys<Tail, SeenState & StateOf<Head>>\n : never;\n\nexport type ValidTuple<Behaviors extends readonly BehaviorClass[]>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors\n : ValidateRequires<Behaviors>\n : readonly IncompatibleState<IncompatibleStateKeys<Behaviors>>[]\n : readonly DuplicateOperation<DuplicateNames<Behaviors>>[];\n\nexport interface GameEntityType\n{\n readonly behaviors : readonly BehaviorClass[];\n readonly prototype : GameEntity;\n}\n\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : StateFor<Type['behaviors']>;\n readonly ops : OpsFor<Type['behaviors']>;\n};\n\nexport type NarrowedTo<Class extends BehaviorClass> = GameEntity & {\n readonly state : DeepReadonly<StateSliceOf<Class>>;\n readonly ops : OpsOf<Class>;\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype BehaviorInstance = Behavior<object, object>;\n\ntype OperationTable = Record<PropertyKey, unknown>;\n\ntype EntityConstructor = new (\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n) => GameEntity;\n\ninterface BehaviorListener\n{\n instance : BehaviorInstance;\n exact : string[];\n patterns : RegExp[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The entity\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class GameEntity implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : GameSimulationContext;\n readonly state : object;\n readonly ops : object;\n readonly eventSubscriptions : readonly EventSubscription[];\n\n static readonly behaviors : readonly BehaviorClass[] = [];\n\n readonly #instances : BehaviorInstance[] = [];\n readonly #listeners : BehaviorListener[] = [];\n readonly #unsubscribe : Unsubscribe;\n\n #operationDepth = 0;\n #creationEvents : RawGameEvent[] | undefined;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors']> },\n id : string,\n context : GameSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state ?? {}, true, name);\n }\n\n static restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors']> },\n id : string,\n context : GameSimulationContext,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state, false, name);\n }\n\n static #construct<Type extends GameEntityType>(\n type : Type,\n id : string,\n context : GameSimulationContext,\n state : object,\n runCreateHooks : boolean,\n name ?: string\n ) : ComposedOf<Type>\n {\n const constructable = type as unknown as EntityConstructor;\n\n return new constructable(id, context, state, runCreateHooks, name) as ComposedOf<Type>;\n }\n\n protected constructor(\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n this.id = id;\n this.name = name ?? (new.target as typeof GameEntity).name;\n this.context = context;\n\n const behaviorClasses = (new.target as typeof GameEntity).behaviors;\n\n for(const behaviorClass of behaviorClasses)\n {\n this.#instances.push(new behaviorClass());\n }\n\n this.state = GameEntity.#composeState(this.#instances, providedState, id);\n\n const ops = Object.create(null) as OperationTable;\n this.ops = ops;\n\n const subscriptions : EventSubscription[] = [];\n\n for(const [ index, behaviorClass ] of behaviorClasses.entries())\n {\n const instance = this.#instances[index];\n\n if(instance !== undefined)\n {\n Object.assign(instance, { state: this.state, ops, entity: this });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n this.eventSubscriptions = subscriptions;\n this.#unsubscribe = context.bus.register(this, subscriptions);\n\n if(runCreateHooks)\n {\n this.#runCreateHooks();\n }\n }\n\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is NarrowedTo<Class>\n {\n for(const instance of this.#instances)\n {\n if(instance instanceof behaviorClass)\n {\n return true;\n }\n }\n\n return false;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.context.getEntity(id);\n }\n\n emit<Type extends keyof EventMap & string>(type : Type, payload : EventMap[Type], targetID ?: string) : void\n {\n const event : RawGameEvent = { type, senderID: this.id, targetID, payload };\n\n if(this.#creationEvents !== undefined)\n {\n this.#creationEvents.push(event);\n return;\n }\n\n this.context.bus.emit(event as GameEvent);\n }\n\n update(dt : number) : void\n {\n for(const instance of this.#instances)\n {\n instance.onUpdate?.(dt);\n }\n }\n\n receive(event : GameEvent) : boolean\n {\n const raw = event as RawGameEvent;\n\n for(const listener of this.#listeners)\n {\n if(GameEntity.#matches(listener, raw.type) && listener.instance.onEvent?.(event) === true)\n {\n return true;\n }\n }\n\n return false;\n }\n\n destroy() : void\n {\n this.#unsubscribe();\n }\n\n // A copy, so an override reads the attachment order without reaching the list the entity dispatches from.\n protected attachedBehaviors() : readonly BehaviorInstance[]\n {\n return [ ...this.#instances ];\n }\n\n #bindOperations(instance : BehaviorInstance, behaviorClass : BehaviorClass, ops : OperationTable) : void\n {\n for(const name of behaviorClass.ops ?? [])\n {\n if(Object.hasOwn(ops, name))\n {\n throw new Error(`Two behaviors on entity \"${ this.id }\" expose the operation \"${ String(name) }\".`);\n }\n\n const method = (instance as unknown as OperationTable)[name];\n\n if(typeof method !== 'function')\n {\n throw new Error(\n `Behavior \"${ behaviorClass.name }\" declares the operation \"${ String(name) }\" `\n + 'with no method behind it.'\n );\n }\n\n ops[name] = this.#guardedOperation(name, method as (...args : unknown[]) => unknown, instance);\n }\n }\n\n #bindEvents(\n instance : BehaviorInstance,\n behaviorClass : BehaviorClass,\n subscriptions : EventSubscription[]\n ) : void\n {\n const exact : string[] = [];\n const patterns : RegExp[] = [];\n\n for(const subscription of behaviorClass.events ?? [])\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n patterns.push(compileEventPattern(name));\n }\n else\n {\n exact.push(name);\n }\n\n if(!subscriptions.includes(subscription))\n {\n subscriptions.push(subscription);\n }\n }\n\n if(exact.length > 0 || patterns.length > 0)\n {\n this.#listeners.push({ instance, exact, patterns });\n }\n }\n\n #guardedOperation(\n name : PropertyKey,\n method : (...args : unknown[]) => unknown,\n instance : BehaviorInstance\n ) : (...args : unknown[]) => unknown\n {\n return (...args : unknown[]) : unknown =>\n {\n this.#operationDepth += 1;\n\n try\n {\n if(this.#operationDepth > operationCallDepthLimit)\n {\n throw new RangeError(\n `Operation \"${ String(name) }\" on entity \"${ this.id }\" ran deeper than the operation `\n + `call depth limit of ${ operationCallDepthLimit }.`\n );\n }\n\n return method.apply(instance, args);\n }\n finally\n {\n this.#operationDepth -= 1;\n }\n };\n }\n\n // The hooks emit into a held list rather than onto the queue, so a hook that throws leaves the bus as it found it.\n #runCreateHooks() : void\n {\n this.#creationEvents = [];\n\n try\n {\n for(const instance of this.#instances)\n {\n instance.onCreate?.();\n }\n }\n catch(error)\n {\n this.#creationEvents = undefined;\n this.#unsubscribe();\n throw error;\n }\n\n const held = this.#creationEvents;\n this.#creationEvents = undefined;\n\n for(const event of held)\n {\n this.context.bus.emit(event as GameEvent);\n }\n }\n\n static #composeState(instances : readonly BehaviorInstance[], providedState : object, id : string) : StateBag\n {\n const state = Object.create(null) as StateBag;\n state['tags'] = [];\n\n for(const instance of instances)\n {\n Object.assign(state, GameEntity.#copyValidState(instance.defaults, id));\n }\n\n Object.assign(state, GameEntity.#copyValidState(providedState, id));\n GameEntity.#checkTags(state, id);\n\n return state;\n }\n\n static #copyValidState(value : object, id : string) : StateBag\n {\n if(!GameEntity.#isStateBag(value, new Set<object>()))\n {\n throw new TypeError(`Entity \"${ id }\" state holds a value that is not finite JSON-compatible data.`);\n }\n\n return copyStateBag(value);\n }\n\n static #checkTags(state : StateBag, id : string) : void\n {\n const tags = state['tags'];\n\n if(!Array.isArray(tags))\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n const seen : string[] = [];\n\n for(const tag of tags)\n {\n if(typeof tag !== 'string')\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n if(seen.includes(tag))\n {\n throw new Error(`Entity \"${ id }\" carries the tag \"${ tag }\" more than once.`);\n }\n\n seen.push(tag);\n }\n }\n\n static #isStateValue(value : unknown, seen : Set<object>) : value is StateValue\n {\n if(value === null || typeof value === 'string' || typeof value === 'boolean')\n {\n return true;\n }\n\n if(typeof value === 'number')\n {\n return Number.isFinite(value);\n }\n\n if(Array.isArray(value))\n {\n return GameEntity.#everyItemIsState(value, seen);\n }\n\n if(typeof value === 'object')\n {\n return GameEntity.#isStateBag(value, seen);\n }\n\n return false;\n }\n\n static #everyItemIsState(items : readonly unknown[], seen : Set<object>) : boolean\n {\n if(seen.has(items))\n {\n return false;\n }\n\n seen.add(items);\n\n for(const item of items)\n {\n if(!GameEntity.#isStateValue(item, seen))\n {\n seen.delete(items);\n return false;\n }\n }\n\n seen.delete(items);\n return true;\n }\n\n static #isStateBag(value : object, seen : Set<object>) : value is StateBag\n {\n const prototype = Object.getPrototypeOf(value) as unknown;\n\n if(prototype !== Object.prototype && prototype !== null)\n {\n return false;\n }\n\n if(seen.has(value))\n {\n return false;\n }\n\n seen.add(value);\n\n for(const item of Object.values(value))\n {\n if(!GameEntity.#isStateValue(item, seen))\n {\n seen.delete(value);\n return false;\n }\n }\n\n seen.delete(value);\n return true;\n }\n\n static #matches(listener : BehaviorListener, type : string) : boolean\n {\n if(listener.exact.includes(type))\n {\n return true;\n }\n\n for(const pattern of listener.patterns)\n {\n if(pattern.test(type))\n {\n return true;\n }\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Simulation Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { EventBus } from './eventBus.ts';\nimport {\n type GameEntity, type GameEntityState, type GameEntityType, type StateBag, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n tick : number;\n idCounter : number;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\nexport interface GameSimulationOptions\n{\n fixedStepDuration : number;\n entityClasses : Readonly<Record<string, GameEntityType>>;\n allocator ?: IDAllocator;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ConstructableEntityClass extends GameEntityType\n{\n create(id : string, context : GameSimulationContext, state ?: StateBag, name ?: string) : GameEntity;\n restore(id : string, context : GameSimulationContext, state : StateBag, name ?: string) : GameEntity;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class GameSimulationManager implements GameSimulationContext\n{\n readonly #bus = new EventBus();\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Readonly<Record<string, GameEntityType>>;\n readonly #allocator : IDAllocator | undefined;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n\n constructor(options : GameSimulationOptions)\n {\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n\n if(!Number.isFinite(this.#fixedStepDuration) || this.#fixedStepDuration <= 0)\n {\n throw new RangeError('A fixed step duration must be a finite number greater than zero.');\n }\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n spawn(name : string, id ?: string, state ?: StateBag) : GameEntity\n {\n const entityClass = this.#entityClass(name);\n\n let entityID = id;\n\n if(entityID === undefined)\n {\n entityID = this.#mintID();\n }\n\n if(this.getEntity(entityID) !== undefined)\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n const entity = entityClass.create(entityID, this, state ?? {}, name);\n this.#entries.push(entity);\n\n return entity;\n }\n\n despawn(id : string) : void\n {\n const index = this.#entries.findIndex((entity) => entity.id === id);\n\n if(index < 0)\n {\n return;\n }\n\n const entity = this.#entries[index];\n\n if(entity !== undefined)\n {\n entity.destroy();\n this.#entries.splice(index, 1);\n }\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#entries.find((entity) => entity.id === id);\n }\n\n getEntitiesByTag(tag : string) : readonly GameEntity[]\n {\n const found : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if(GameSimulationManager.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n runStepsForElapsedTime(elapsed : number) : void\n {\n if(!Number.isFinite(elapsed) || elapsed < 0)\n {\n throw new RangeError('Elapsed time must be a finite number of zero or more.');\n }\n\n this.#accumulator += elapsed;\n\n let steps = 0;\n\n while(this.#owesAFixedStep() && steps < dilationLimit)\n {\n this.#runFixedStep();\n steps += 1;\n }\n\n // Chronic overrun degrades into simulation time running slower than wall clock: the whole intervals still owed\n // are dropped and only the fraction is carried.\n if(steps === dilationLimit && this.#owesAFixedStep())\n {\n this.#accumulator %= this.#fixedStepDuration;\n }\n }\n\n snapshot() : GameSimulationSnapshot\n {\n const entities : GameEntitySnapshot[] = [];\n\n for(const entity of this.#entries)\n {\n entities.push({\n id: entity.id,\n name: entity.name,\n state: copyStateBag(entity.state as StateBag),\n });\n }\n\n return { tick: this.#tick, idCounter: this.#idCounter, entities };\n }\n\n fromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulationManager\n {\n const restored = this.buildReplacementSimulation({\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n });\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(options : GameSimulationOptions) : GameSimulationManager\n {\n return new GameSimulationManager(options);\n }\n\n #load(snapshot : GameSimulationSnapshot) : void\n {\n const entries : GameEntity[] = [];\n\n try\n {\n for(const row of snapshot.entities)\n {\n const entityClass = this.#entityClass(row.name);\n const entity = entityClass.restore(row.id, this, row.state, row.name);\n entries.push(entity);\n }\n }\n catch(error)\n {\n for(const entity of entries)\n {\n entity.destroy();\n }\n\n throw error;\n }\n\n this.#entries = entries;\n this.#tick = snapshot.tick;\n this.#idCounter = snapshot.idCounter;\n }\n\n // The accumulator holds a sum of binary floating point values, which lands a few units in the last place short of\n // the interval the caller meant; without the epsilon three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n return this.#accumulator + Number.EPSILON >= this.#fixedStepDuration;\n }\n\n #runFixedStep() : void\n {\n for(const entity of [ ...this.#entries ])\n {\n entity.update(this.#fixedStepDuration);\n }\n\n // The step ran, so the tick and the accumulator settle whether or not the drain raised.\n try\n {\n this.#bus.drain();\n }\n finally\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= Number.EPSILON)\n {\n this.#accumulator = 0;\n }\n\n this.#tick += 1;\n }\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n return `entity-${ this.#idCounter }`;\n }\n\n #entityClass(name : string) : ConstructableEntityClass\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity class table holds no class named \"${ name }\".`);\n }\n\n return entityClass as ConstructableEntityClass;\n }\n\n // Every entity carries the tags field whatever its behaviors declare, so the query reads it straight.\n static #entityState(entity : GameEntity) : GameEntityState\n {\n return entity.state as GameEntityState;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":";AASA,IAAsB,IAAtB,MACA,CAUA,GCPa,IAAb,cAAmC,EACnC;CACI,WAAiC,EAAE,aAAa,GAAG;AACvD,GCZa,IAA0B,IAE1B,IAAiB,IAEjB,IAAgB;;;AC4C7B,SAAgB,EAAoB,GACpC;CACI,IAAM,IAAU,EACX,QAAQ,uBAAuB,MAAM,CAAC,CACtC,QAAQ,SAAS,IAAI;CAE1B,OAAW,OAAO,IAAK,EAAS,EAAE;AACtC;;;ACvCA,IAAa,IAAb,MAAa,EACb;CACI,KAA0B,CAAC;CAC3B,KAAoC,CAAC;CAErC,qBAAwB,IAAI,IAAwB;CACpD,qBAAkB,IAAI,IAA0B;CAEhD,SAAS,GAAyB,GAClC;EACI,IAAG,KAAK,GAAa,IAAI,EAAW,EAAE,GAElC,MAAU,MAAM,oDAAqD,EAAW,GAAI,GAAG;EAG3F,KAAK,GAAa,IAAI,EAAW,IAAI,CAAU;EAE/C,IAAM,IAAwB,CAAC,GACzB,IAAyC,CAAC;EAEhD,KAAI,IAAM,KAAgB,GAC1B;GACI,IAAM,IAAgB;GAEtB,IAAG,EAAK,SAAS,IAAI,GACrB;IACI,IAAM,IAAQ;KAAE,SAAS,EAAoB,CAAI;KAAG;IAAW;IAE/D,AADA,EAAe,KAAK,CAAK,GACzB,KAAK,GAAU,KAAK,CAAK;GAC7B,OACK,AAAI,EAAW,SAAS,CAAI,MAE7B,EAAW,KAAK,CAAI,GACpB,KAAK,GAAU,GAAM,CAAU;EAEvC;EAEA,OAAO,KAAK,GAAU,GAAY,GAAY,CAAc;CAChE;CAEA,KAAK,GACL;EACI,KAAK,GAAO,KAAK,CAAqB;CAC1C;CAEA,QACA;EACI,IAAM,IAAqB,CAAC,GACxB,IAAS;EAEb,OAAM,KAAK,GAAO,SAAS,IAC3B;GAGI,IAFA,KAAU,GAEP,IAAA,IACH;IAEI,AADA,KAAK,KAAS,CAAC,GACf,EAAO,KAAK,gBAAI,MAAM,+BAAgD,CAAC;IACvE;GACJ;GAEA,IAAM,IAAO,KAAK;GAClB,KAAK,KAAS,CAAC;GAEf,KAAI,IAAM,KAAS,GAEf,KAAK,GAAS,GAAO,CAAM;EAEnC;EAEA,IAAG,EAAO,SAAS,GAEf,MAAU,eAAe,GAAQ,mBAAmB;CAE5D;CAEA,GAAU,GAAe,GACzB;EACI,IAAM,IAAc,KAAK,GAAO,IAAI,CAAI,KAAK,CAAC;EAE9C,AADA,EAAY,KAAK,CAAU,GAC3B,KAAK,GAAO,IAAI,GAAM,CAAW;CACrC;CAEA,GAAa,GAAe,GAC5B;EACI,IAAM,IAAc,KAAK,GAAO,IAAI,CAAI;EAExC,IAAG,MAAgB,KAAA,GAEf;EAGJ,IAAM,IAAQ,EAAY,QAAQ,CAAU;EAO5C,AALG,KAAS,KAER,EAAY,OAAO,GAAO,CAAC,GAG5B,EAAY,WAAW,KAEtB,KAAK,GAAO,OAAO,CAAI;CAE/B;CAIA,GACI,GACA,GACA,GAEJ;EACI,IAAI,IAAS;EAEb,aACA;GACI,IAAG,CAAC,GAEA;GAKJ,AAFA,IAAS,IAEN,KAAK,GAAa,IAAI,EAAW,EAAE,MAAM,KAExC,KAAK,GAAa,OAAO,EAAW,EAAE;GAG1C,KAAI,IAAM,KAAQ,GAEd,KAAK,GAAa,GAAM,CAAU;GAGtC,IAAM,IAAoC,CAAC;GAE3C,KAAI,IAAM,KAAS,KAAK,IAEpB,AAAI,EAAe,SAAS,CAAK,KAE7B,EAAU,KAAK,CAAK;GAI5B,KAAK,KAAY;EACrB;CACJ;CAEA,GAAS,GAAsB,GAC/B;EACI,IAAG,EAAM,aAAa,KAAA,GACtB;GACI,IAAM,IAAY,KAAK,GAAa,IAAI,EAAM,QAAQ;GAEtD,AAAG,MAAc,KAAA,KAEb,EAAS,GAAS,GAAW,GAAO,CAAM;GAG9C;EACJ;EAEA,IAAM,oBAAY,IAAI,IAAgB;EAEtC,KAAI,IAAM,KAAc,CAAE,GAAG,KAAK,GAAO,IAAI,EAAM,IAAI,KAAK,CAAC,CAAE,GAE3D,EAAS,GAAa,GAAY,GAAO,GAAW,CAAM;EAG9D,KAAI,IAAM,KAAS,CAAE,GAAG,KAAK,EAAU,GAEnC,AAAG,EAAM,QAAQ,KAAK,EAAM,IAAI,KAE5B,EAAS,GAAa,EAAM,YAAY,GAAO,GAAW,CAAM;CAG5E;CAEA,OAAO,GACH,GACA,GACA,GACA,GAEJ;EACO,EAAU,IAAI,CAAU,MAK3B,EAAU,IAAI,CAAU,GACxB,EAAS,GAAS,GAAY,GAAO,CAAM;CAC/C;CAEA,OAAO,GAAS,GAAyB,GAAsB,GAC/D;EACI,IACA;GACI,EAAW,QAAQ,CAAkB;EACzC,SACM,GACN;GACI,EAAO,KAAK,CAAK;EACrB;CACJ;AACJ;;;AChMA,SAAS,EAAe,GACxB;CACI,IAAG,MAAM,QAAQ,CAAK,GACtB;EACI,IAAM,IAAuB,CAAC;EAE9B,KAAI,IAAM,KAAQ,GAEd,EAAM,KAAK,EAAe,CAAI,CAAC;EAGnC,OAAO;CACX;CAEA,IAAqB,OAAO,KAAU,YAAnC,GACH;EACI,IAAM,IAAO,OAAO,OAAO,IAAI;EAE/B,KAAI,IAAM,CAAE,GAAK,MAAW,OAAO,QAAQ,CAAK,GAE5C,EAAK,KAAO,EAAe,CAAK;EAGpC,OAAO;CACX;CAEA,OAAO;AACX;AAEA,SAAgB,EAAa,GAC7B;CACI,OAAO,EAAe,CAAG;AAC7B;AAiHA,IAAsB,IAAtB,MAAsB,EACtB;CACI;CACA;CACA;CACA;CACA;CACA;CAEA,OAAgB,YAAuC,CAAC;CAExD,KAA2C,CAAC;CAC5C,KAA2C,CAAC;CAC5C;CAEA,KAAkB;CAClB;CAEA,OAAO,OAEH,GACA,GACA,GACA,GAEJ;EACI,OAAO,EAAW,GAAW,MAAM,GAAI,GAAS,KAAS,CAAC,GAAG,IAAM,CAAI;CAC3E;CAEA,OAAO,QAEH,GACA,GACA,GACA,GAEJ;EACI,OAAO,EAAW,GAAW,MAAM,GAAI,GAAS,GAAO,IAAO,CAAI;CACtE;CAEA,OAAO,GACH,GACA,GACA,GACA,GACA,GACA,GAEJ;EAGI,OAAO,IAAI,EAAc,GAAI,GAAS,GAAO,GAAgB,CAAI;CACrE;CAEA,YACI,GACA,GACA,GACA,GACA,GAEJ;EAGI,AAFA,KAAK,KAAK,GACV,KAAK,OAAO,KAAS,WAAiC,MACtD,KAAK,UAAU;EAEf,IAAM,IAAmB,WAAiC;EAE1D,KAAI,IAAM,KAAiB,GAEvB,KAAK,GAAW,KAAK,IAAI,EAAc,CAAC;EAG5C,KAAK,QAAQ,EAAW,GAAc,KAAK,IAAY,GAAe,CAAE;EAExE,IAAM,IAAM,OAAO,OAAO,IAAI;EAC9B,KAAK,MAAM;EAEX,IAAM,IAAsC,CAAC;EAE7C,KAAI,IAAM,CAAE,GAAO,MAAmB,EAAgB,QAAQ,GAC9D;GACI,IAAM,IAAW,KAAK,GAAW;GAEjC,AAAG,MAAa,KAAA,MAEZ,OAAO,OAAO,GAAU;IAAE,OAAO,KAAK;IAAO;IAAK,QAAQ;GAAK,CAAC,GAChE,KAAK,GAAgB,GAAU,GAAe,CAAG,GACjD,KAAK,GAAY,GAAU,GAAe,CAAa;EAE/D;EAKA,AAHA,KAAK,qBAAqB,GAC1B,KAAK,KAAe,EAAQ,IAAI,SAAS,MAAM,CAAa,GAEzD,KAEC,KAAK,GAAgB;CAE7B;CAEA,YAAyC,GACzC;EACI,KAAI,IAAM,KAAY,KAAK,IAEvB,IAAG,aAAoB,GAEnB,OAAO;EAIf,OAAO;CACX;CAEA,UAAU,GACV;EACI,OAAO,KAAK,QAAQ,UAAU,CAAE;CACpC;CAEA,KAA2C,GAAa,GAA0B,GAClF;EACI,IAAM,IAAuB;GAAE;GAAM,UAAU,KAAK;GAAI;GAAU;EAAQ;EAE1E,IAAG,KAAK,OAAoB,KAAA,GAC5B;GACI,KAAK,GAAgB,KAAK,CAAK;GAC/B;EACJ;EAEA,KAAK,QAAQ,IAAI,KAAK,CAAkB;CAC5C;CAEA,OAAO,GACP;EACI,KAAI,IAAM,KAAY,KAAK,IAEvB,EAAS,WAAW,CAAE;CAE9B;CAEA,QAAQ,GACR;EACI,IAAM,IAAM;EAEZ,KAAI,IAAM,KAAY,KAAK,IAEvB,IAAG,EAAW,GAAS,GAAU,EAAI,IAAI,KAAK,EAAS,SAAS,UAAU,CAAK,MAAM,IAEjF,OAAO;EAIf,OAAO;CACX;CAEA,UACA;EACI,KAAK,GAAa;CACtB;CAGA,oBACA;EACI,OAAO,CAAE,GAAG,KAAK,EAAW;CAChC;CAEA,GAAgB,GAA6B,GAA+B,GAC5E;EACI,KAAI,IAAM,KAAQ,EAAc,OAAO,CAAC,GACxC;GACI,IAAG,OAAO,OAAO,GAAK,CAAI,GAEtB,MAAU,MAAM,4BAA6B,KAAK,GAAI,0BAA2B,OAAO,CAAI,EAAG,GAAG;GAGtG,IAAM,IAAU,EAAuC;GAEvD,IAAG,OAAO,KAAW,YAEjB,MAAU,MACN,aAAc,EAAc,KAAM,4BAA6B,OAAO,CAAI,EAAG,4BAEjF;GAGJ,EAAI,KAAQ,KAAK,GAAkB,GAAM,GAA4C,CAAQ;EACjG;CACJ;CAEA,GACI,GACA,GACA,GAEJ;EACI,IAAM,IAAmB,CAAC,GACpB,IAAsB,CAAC;EAE7B,KAAI,IAAM,KAAgB,EAAc,UAAU,CAAC,GACnD;GACI,IAAM,IAAgB;GAWtB,AATG,EAAK,SAAS,IAAI,IAEjB,EAAS,KAAK,EAAoB,CAAI,CAAC,IAIvC,EAAM,KAAK,CAAI,GAGf,EAAc,SAAS,CAAY,KAEnC,EAAc,KAAK,CAAY;EAEvC;EAEA,CAAG,EAAM,SAAS,KAAK,EAAS,SAAS,MAErC,KAAK,GAAW,KAAK;GAAE;GAAU;GAAO;EAAS,CAAC;CAE1D;CAEA,GACI,GACA,GACA,GAEJ;EACI,QAAQ,GAAG,MACX;GACI,KAAK,MAAmB;GAExB,IACA;IACI,IAAG,KAAK,KAAA,IAEJ,MAAU,WACN,cAAe,OAAO,CAAI,EAAG,eAAgB,KAAK,GAAI,wDAE1D;IAGJ,OAAO,EAAO,MAAM,GAAU,CAAI;GACtC,UAEA;IACI,OAAK;GACT;EACJ;CACJ;CAGA,KACA;EACI,KAAK,KAAkB,CAAC;EAExB,IACA;GACI,KAAI,IAAM,KAAY,KAAK,IAEvB,EAAS,WAAW;EAE5B,SACM,GACN;GAGI,MAFA,KAAK,KAAkB,KAAA,GACvB,KAAK,GAAa,GACZ;EACV;EAEA,IAAM,IAAO,KAAK;EAClB,KAAK,KAAkB,KAAA;EAEvB,KAAI,IAAM,KAAS,GAEf,KAAK,QAAQ,IAAI,KAAK,CAAkB;CAEhD;CAEA,OAAO,GAAc,GAAyC,GAAwB,GACtF;EACI,IAAM,IAAQ,OAAO,OAAO,IAAI;EAChC,EAAM,OAAU,CAAC;EAEjB,KAAI,IAAM,KAAY,GAElB,OAAO,OAAO,GAAO,EAAW,GAAgB,EAAS,UAAU,CAAE,CAAC;EAM1E,OAHA,OAAO,OAAO,GAAO,EAAW,GAAgB,GAAe,CAAE,CAAC,GAClE,EAAW,GAAW,GAAO,CAAE,GAExB;CACX;CAEA,OAAO,GAAgB,GAAgB,GACvC;EACI,IAAG,CAAC,EAAW,GAAY,mBAAO,IAAI,IAAY,CAAC,GAE/C,MAAU,UAAU,WAAY,EAAI,+DAA+D;EAGvG,OAAO,EAAa,CAAK;CAC7B;CAEA,OAAO,GAAW,GAAkB,GACpC;EACI,IAAM,IAAO,EAAM;EAEnB,IAAG,CAAC,MAAM,QAAQ,CAAI,GAElB,MAAU,UAAU,WAAY,EAAI,kCAAkC;EAG1E,IAAM,IAAkB,CAAC;EAEzB,KAAI,IAAM,KAAO,GACjB;GACI,IAAG,OAAO,KAAQ,UAEd,MAAU,UAAU,WAAY,EAAI,kCAAkC;GAG1E,IAAG,EAAK,SAAS,CAAG,GAEhB,MAAU,MAAM,WAAY,EAAI,qBAAsB,EAAK,kBAAkB;GAGjF,EAAK,KAAK,CAAG;EACjB;CACJ;CAEA,OAAO,GAAc,GAAiB,GACtC;EAqBI,OApBG,MAAU,QAAQ,OAAO,KAAU,YAAY,OAAO,KAAU,YAExD,KAGR,OAAO,KAAU,WAET,OAAO,SAAS,CAAK,IAG7B,MAAM,QAAQ,CAAK,IAEX,EAAW,GAAkB,GAAO,CAAI,IAGhD,OAAO,KAAU,YAET,EAAW,GAAY,GAAO,CAAI;CAIjD;CAEA,OAAO,GAAkB,GAA4B,GACrD;EACI,IAAG,EAAK,IAAI,CAAK,GAEb,OAAO;EAGX,EAAK,IAAI,CAAK;EAEd,KAAI,IAAM,KAAQ,GAEd,IAAG,CAAC,EAAW,GAAc,GAAM,CAAI,GAGnC,OADA,EAAK,OAAO,CAAK,GACV;EAKf,OADA,EAAK,OAAO,CAAK,GACV;CACX;CAEA,OAAO,GAAY,GAAgB,GACnC;EACI,IAAM,IAAY,OAAO,eAAe,CAAK;EAO7C,IALG,MAAc,OAAO,aAAa,MAAc,QAKhD,EAAK,IAAI,CAAK,GAEb,OAAO;EAGX,EAAK,IAAI,CAAK;EAEd,KAAI,IAAM,KAAQ,OAAO,OAAO,CAAK,GAEjC,IAAG,CAAC,EAAW,GAAc,GAAM,CAAI,GAGnC,OADA,EAAK,OAAO,CAAK,GACV;EAKf,OADA,EAAK,OAAO,CAAK,GACV;CACX;CAEA,OAAO,GAAS,GAA6B,GAC7C;EACI,IAAG,EAAS,MAAM,SAAS,CAAI,GAE3B,OAAO;EAGX,KAAI,IAAM,KAAW,EAAS,UAE1B,IAAG,EAAQ,KAAK,CAAI,GAEhB,OAAO;EAIf,OAAO;CACX;AACJ,GC7iBa,IAAb,MAAa,EACb;CACI,KAAgB,IAAI,EAAS;CAC7B;CACA;CACA;CAEA,KAAQ;CACR,KAAa;CACb,KAAe;CACf,KAA0B,CAAC;CAE3B,YAAY,GACZ;EAKI,IAJA,KAAK,KAAqB,EAAQ,mBAClC,KAAK,KAAiB,EAAQ,eAC9B,KAAK,KAAa,EAAQ,WAEvB,CAAC,OAAO,SAAS,KAAK,EAAkB,KAAK,KAAK,MAAsB,GAEvE,MAAU,WAAW,kEAAkE;CAE/F;CAEA,IAAI,MACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,OACJ;EACI,OAAO,KAAK;CAChB;CAEA,IAAI,WACJ;EACI,OAAO,CAAE,GAAG,KAAK,EAAS;CAC9B;CAEA,MAAM,GAAe,GAAc,GACnC;EACI,IAAM,IAAc,KAAK,GAAa,CAAI,GAEtC,IAAW;EAOf,IALG,MAAa,KAAA,MAEZ,IAAW,KAAK,GAAQ,IAGzB,KAAK,UAAU,CAAQ,MAAM,KAAA,GAE5B,MAAU,MAAM,wDAAyD,EAAU,GAAG;EAG1F,IAAM,IAAS,EAAY,OAAO,GAAU,MAAM,KAAS,CAAC,GAAG,CAAI;EAGnE,OAFA,KAAK,GAAS,KAAK,CAAM,GAElB;CACX;CAEA,QAAQ,GACR;EACI,IAAM,IAAQ,KAAK,GAAS,WAAW,MAAW,EAAO,OAAO,CAAE;EAElE,IAAG,IAAQ,GAEP;EAGJ,IAAM,IAAS,KAAK,GAAS;EAE7B,AAAG,MAAW,KAAA,MAEV,EAAO,QAAQ,GACf,KAAK,GAAS,OAAO,GAAO,CAAC;CAErC;CAEA,UAAU,GACV;EACI,OAAO,KAAK,GAAS,MAAM,MAAW,EAAO,OAAO,CAAE;CAC1D;CAEA,iBAAiB,GACjB;EACI,IAAM,IAAuB,CAAC;EAE9B,KAAI,IAAM,KAAU,KAAK,IAErB,AAAG,EAAsB,GAAa,CAAM,CAAC,CAAC,KAAK,SAAS,CAAG,KAE3D,EAAM,KAAK,CAAM;EAIzB,OAAO;CACX;CAEA,uBAAuB,GACvB;EACI,IAAG,CAAC,OAAO,SAAS,CAAO,KAAK,IAAU,GAEtC,MAAU,WAAW,uDAAuD;EAGhF,KAAK,MAAgB;EAErB,IAAI,IAAQ;EAEZ,OAAM,KAAK,GAAgB,KAAK,IAAA,IAG5B,AADA,KAAK,GAAc,GACnB,KAAS;EAKb,AAAG,MAAA,KAA2B,KAAK,GAAgB,MAE/C,KAAK,MAAgB,KAAK;CAElC;CAEA,WACA;EACI,IAAM,IAAkC,CAAC;EAEzC,KAAI,IAAM,KAAU,KAAK,IAErB,EAAS,KAAK;GACV,IAAI,EAAO;GACX,MAAM,EAAO;GACb,OAAO,EAAa,EAAO,KAAiB;EAChD,CAAC;EAGL,OAAO;GAAE,MAAM,KAAK;GAAO,WAAW,KAAK;GAAY;EAAS;CACpE;CAEA,aAAa,GACb;EACI,IAAM,IAAW,KAAK,2BAA2B;GAC7C,mBAAmB,KAAK;GACxB,eAAe,KAAK;GACpB,WAAW,KAAK;EACpB,CAAC;EAID,OAFA,EAAS,GAAM,CAAQ,GAEhB;CACX;CAEA,2BAAqC,GACrC;EACI,OAAO,IAAI,EAAsB,CAAO;CAC5C;CAEA,GAAM,GACN;EACI,IAAM,IAAyB,CAAC;EAEhC,IACA;GACI,KAAI,IAAM,KAAO,EAAS,UAC1B;IAEI,IAAM,IADc,KAAK,GAAa,EAAI,IAC3B,CAAA,CAAY,QAAQ,EAAI,IAAI,MAAM,EAAI,OAAO,EAAI,IAAI;IACpE,EAAQ,KAAK,CAAM;GACvB;EACJ,SACM,GACN;GACI,KAAI,IAAM,KAAU,GAEhB,EAAO,QAAQ;GAGnB,MAAM;EACV;EAIA,AAFA,KAAK,KAAW,GAChB,KAAK,KAAQ,EAAS,MACtB,KAAK,KAAa,EAAS;CAC/B;CAIA,KACA;EACI,OAAO,KAAK,iBAAiC,KAAK;CACtD;CAEA,KACA;EACI,KAAI,IAAM,KAAU,CAAE,GAAG,KAAK,EAAS,GAEnC,EAAO,OAAO,KAAK,EAAkB;EAIzC,IACA;GACI,KAAK,GAAK,MAAM;EACpB,UAEA;GAQI,AAPA,KAAK,MAAgB,KAAK,IAEvB,KAAK,IAAI,KAAK,EAAY,kBAEzB,KAAK,KAAe,IAGxB,KAAK,MAAS;EAClB;CACJ;CAEA,KACA;EAQI,OAPG,KAAK,OAAe,KAAA,KAKvB,KAAK,MAAc,GAEZ,UAAW,KAAK,QALZ,KAAK,GAAW;CAM/B;CAEA,GAAa,GACb;EACI,IAAM,IAAc,KAAK,GAAe;EAExC,IAAG,MAAgB,KAAA,GAEf,MAAU,MAAM,gDAAiD,EAAM,GAAG;EAG9E,OAAO;CACX;CAGA,OAAO,GAAa,GACpB;EACI,OAAO,EAAO;CAClB;AACJ"}
|
|
1
|
+
{"version":3,"file":"sage-core.js","names":[],"sources":["../src/behavior.ts","../src/namedBehavior.ts","../src/stateMachineBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<State extends object, Requires extends object = object>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : GameEntity;\n\n abstract readonly defaults : State;\n\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n}\n\nexport interface BehaviorClass\n{\n new () : Behavior<object, object>;\n readonly ops ?: readonly PropertyKey[];\n readonly events ?: readonly EventSubscription[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// NamedBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface NamedState\n{\n displayName : string;\n}\n\nexport class NamedBehavior extends Behavior<NamedState>\n{\n readonly defaults : NamedState = { displayName: '' };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// StateMachineBehavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { Behavior } from './behavior.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface StateDefinition<Machine, States extends string>\n{\n enter ?(this : Machine, from : States) : void;\n exit ?(this : Machine, to : States) : void;\n update ?(this : Machine, dt : number) : void;\n}\n\n// A subclass assigns `states` from this call, passing its own `this` as `machine`, rather than writing the\n// Record<...> type out by hand: a class field with no type annotation infers its type from a function's return type\n// without pushing the function's parameter type back down into the argument, so a bare object literal would leave\n// every enter/exit/update untyped. Calling through this instead gives the object literal a contextual type, which is\n// what makes `this.state` and each hook's own parameter resolve inside the literal at all.\nexport function defineStates<Machine, States extends string>(\n _machine : Machine,\n states : Record<States, StateDefinition<Machine, States>>\n) : Record<States, StateDefinition<Machine, States>>\n{\n return states;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class StateMachineBehavior<\n OwnState extends { currentState : string },\n Requires extends object = object,\n> extends Behavior<OwnState, Requires>\n{\n static readonly ops = [ 'transitionTo' ] as const;\n\n abstract readonly states : Record<OwnState['currentState'], StateDefinition<this, OwnState['currentState']>>;\n\n onUpdate(dt : number) : void\n {\n // `this.state.currentState` widens to `string` as an index expression on its own -- naming its type here\n // is what keeps it narrowed to `OwnState['currentState']`, which `states` is actually keyed by.\n const current : OwnState['currentState'] = this.state.currentState;\n\n this.states[current].update?.call(this, dt);\n }\n\n transitionTo(to : OwnState['currentState']) : void\n {\n if(!(to in this.states))\n {\n throw new Error(`StateMachineBehavior: no state named \"${ to }\" is declared.`);\n }\n\n if(to === this.state.currentState)\n {\n return;\n }\n\n const from : OwnState['currentState'] = this.state.currentState;\n\n this.states[from].exit?.call(this, to);\n this.state.currentState = to;\n this.states[to].enter?.call(this, from);\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Tuning Constants\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const operationCallDepthLimit = 32;\n\nexport const drainPassLimit = 64;\n\nexport const dilationLimit = 5;\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Events\n//\n// `EventMap` is declared in the package root because a `declare module` augmentation only merges into the module that\n// declares the interface, and the root is the only module a consumer can address.\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { EventMap } from './index.ts';\n\nexport type { EventMap };\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type GameEvent = {\n [Type in keyof EventMap & string] : {\n type : Type;\n senderID : string;\n targetID ?: string;\n payload : EventMap[Type];\n }\n}[keyof EventMap & string];\n\ntype EventPrefix<Type extends string> = Type extends `${ infer Prefix }:${ infer Rest }`\n ? Prefix | `${ Prefix }:${ EventPrefix<Rest> }`\n : never;\n\nexport type EventSubscription = (keyof EventMap & string) | `${ EventPrefix<keyof EventMap & string> }:*`;\n\nexport type Unsubscribe = () => void;\n\nexport interface Subscriber\n{\n readonly id : string;\n receive(event : GameEvent) : boolean;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The empty map boundary\n//----------------------------------------------------------------------------------------------------------------------\n\n// Until a game augments `EventMap`, it holds nothing, `GameEvent` is `never` everywhere inside this package, and no\n// event value can be built against it. `RawGameEvent` is the shape an event has whatever the map holds, and it is the\n// only place core steps outside the typed vocabulary: it stays internal, and every public signature keeps `GameEvent`.\nexport interface RawGameEvent\n{\n type : string;\n senderID : string;\n targetID ?: string;\n payload : unknown;\n}\n\n// A pattern spans every remaining segment, so `door:*` matches `door:opened` and `door:lock:opened` alike.\nexport function compileEventPattern(subscription : string) : RegExp\n{\n const escaped = subscription\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\\\\\*/g, '.*');\n\n return new RegExp(`^${ escaped }$`);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Event Bus\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { drainPassLimit } from './constants.ts';\nimport {\n type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface PatternSubscription\n{\n pattern : RegExp;\n subscriber : Subscriber;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class EventBus\n{\n #queue : RawGameEvent[] = [];\n #patterns : PatternSubscription[] = [];\n\n readonly #subscribers = new Map<string, Subscriber>();\n readonly #exact = new Map<string, Subscriber[]>();\n\n register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n if(this.#subscribers.has(subscriber.id))\n {\n throw new Error(`A subscriber is already registered under the ID \"${ subscriber.id }\".`);\n }\n\n this.#subscribers.set(subscriber.id, subscriber);\n\n const exactNames : string[] = [];\n const patternEntries : PatternSubscription[] = [];\n\n for(const subscription of subscriptions)\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n const entry = { pattern: compileEventPattern(name), subscriber };\n patternEntries.push(entry);\n this.#patterns.push(entry);\n }\n else if(!exactNames.includes(name))\n {\n exactNames.push(name);\n this.#addExact(name, subscriber);\n }\n }\n\n return this.#teardown(subscriber, exactNames, patternEntries);\n }\n\n emit(event : GameEvent) : void\n {\n this.#queue.push(event as RawGameEvent);\n }\n\n drain() : void\n {\n const errors : unknown[] = [];\n let passes = 0;\n\n while(this.#queue.length > 0)\n {\n passes += 1;\n\n if(passes > drainPassLimit)\n {\n this.#queue = [];\n errors.push(new Error(`The drain exceeded ${ drainPassLimit } passes.`));\n break;\n }\n\n const pass = this.#queue;\n this.#queue = [];\n\n for(const event of pass)\n {\n this.#deliver(event, errors);\n }\n }\n\n if(errors.length > 0)\n {\n throw new AggregateError(errors, 'The drain raised.');\n }\n }\n\n #addExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name) ?? [];\n subscribers.push(subscriber);\n this.#exact.set(name, subscribers);\n }\n\n #removeExact(name : string, subscriber : Subscriber) : void\n {\n const subscribers = this.#exact.get(name);\n\n if(subscribers === undefined)\n {\n return;\n }\n\n const index = subscribers.indexOf(subscriber);\n\n if(index >= 0)\n {\n subscribers.splice(index, 1);\n }\n\n if(subscribers.length === 0)\n {\n this.#exact.delete(name);\n }\n }\n\n // A teardown is idempotent, and it evicts the subscriber under its ID only while that is still the registration it\n // was handed out for; a later registration that took the same ID survives a stale teardown.\n #teardown(\n subscriber : Subscriber,\n exactNames : readonly string[],\n patternEntries : readonly PatternSubscription[]\n ) : Unsubscribe\n {\n let active = true;\n\n return () : void =>\n {\n if(!active)\n {\n return;\n }\n\n active = false;\n\n if(this.#subscribers.get(subscriber.id) === subscriber)\n {\n this.#subscribers.delete(subscriber.id);\n }\n\n for(const name of exactNames)\n {\n this.#removeExact(name, subscriber);\n }\n\n const remaining : PatternSubscription[] = [];\n\n for(const entry of this.#patterns)\n {\n if(!patternEntries.includes(entry))\n {\n remaining.push(entry);\n }\n }\n\n this.#patterns = remaining;\n };\n }\n\n #deliver(event : RawGameEvent, errors : unknown[]) : void\n {\n if(event.targetID !== undefined)\n {\n const addressed = this.#subscribers.get(event.targetID);\n\n if(addressed !== undefined)\n {\n EventBus.#handOff(addressed, event, errors);\n }\n\n return;\n }\n\n const delivered = new Set<Subscriber>();\n\n for(const subscriber of [ ...this.#exact.get(event.type) ?? [] ])\n {\n EventBus.#handOffOnce(subscriber, event, delivered, errors);\n }\n\n for(const entry of [ ...this.#patterns ])\n {\n if(entry.pattern.test(event.type))\n {\n EventBus.#handOffOnce(entry.subscriber, event, delivered, errors);\n }\n }\n }\n\n static #handOffOnce(\n subscriber : Subscriber,\n event : RawGameEvent,\n delivered : Set<Subscriber>,\n errors : unknown[]\n ) : void\n {\n if(delivered.has(subscriber))\n {\n return;\n }\n\n delivered.add(subscriber);\n EventBus.#handOff(subscriber, event, errors);\n }\n\n static #handOff(subscriber : Subscriber, event : RawGameEvent, errors : unknown[]) : void\n {\n try\n {\n subscriber.receive(event as GameEvent);\n }\n catch(error)\n {\n errors.push(error);\n }\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameEntity\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { operationCallDepthLimit } from './constants.ts';\nimport {\n type EventMap, type EventSubscription, type GameEvent, type RawGameEvent, type Subscriber, type Unsubscribe,\n compileEventPattern,\n} from './events.ts';\n\nimport type { Behavior, BehaviorClass } from './behavior.ts';\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n// State\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type StateValue = null | boolean | number | string | StateValue[] | { [key : string] : StateValue };\n\nexport type StateBag = Record<string, StateValue>;\n\nexport type DeepReadonly<Value> = Value extends (infer Item)[]\n ? readonly DeepReadonly<Item>[]\n : Value extends object\n ? { readonly [Key in keyof Value] : DeepReadonly<Value[Key]> }\n : Value;\n\nexport interface GameEntityState\n{\n tags : string[];\n}\n\nfunction rejectStateValue(id : string, path : string) : never\n{\n throw new TypeError(\n `Entity \"${ id }\" state holds a value that is not finite JSON-compatible data at \"${ path }\".`\n );\n}\n\n// Copies and checks in one walk, so every path that hands state around -- creation, restoration, snapshot --\n// rejects the same values (design/entities.md#state). Records are copied with no prototype, so a state field\n// named `__proto__` stays an ordinary field of the copy.\nfunction copyStateValue(value : unknown, id : string, path : string, seen : Set<object>) : StateValue\n{\n if(value === null || typeof value === 'string' || typeof value === 'boolean')\n {\n return value;\n }\n\n if(typeof value === 'number')\n {\n if(!Number.isFinite(value))\n {\n rejectStateValue(id, path);\n }\n\n return value;\n }\n\n if(Array.isArray(value))\n {\n if(seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const items : StateValue[] = [];\n\n for(const [ index, item ] of value.entries())\n {\n items.push(copyStateValue(item, id, `${ path }[${ index }]`, seen));\n }\n\n seen.delete(value);\n\n return items;\n }\n\n if(typeof value === 'object')\n {\n const prototype = Object.getPrototypeOf(value) as unknown;\n\n if((prototype !== Object.prototype && prototype !== null) || seen.has(value))\n {\n rejectStateValue(id, path);\n }\n\n seen.add(value);\n\n const copy = Object.create(null) as StateBag;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = copyStateValue(field, id, path === '' ? key : `${ path }.${ key }`, seen);\n }\n\n seen.delete(value);\n\n return copy;\n }\n\n return rejectStateValue(id, path);\n}\n\nexport function copyStateBag(bag : Readonly<StateBag>, id : string) : StateBag\n{\n return copyStateValue(bag, id, '', new Set<object>()) as StateBag;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// Composition\n//----------------------------------------------------------------------------------------------------------------------\n\ntype UnionToIntersection<Union>\n = (Union extends unknown ? (value : Union) => void : never) extends (value : infer Intersection) => void\n ? Intersection\n : never;\n\ntype StateOf<Class> = Class extends new () => { state : infer State } ? State : never;\n\ntype OperationNames<Class> = Class extends { ops : readonly (infer Name)[] } ? Name & PropertyKey : never;\n\ntype OpsFrom<Class> = Class extends BehaviorClass\n ? Pick<InstanceType<Class>, OperationNames<Class> & keyof InstanceType<Class>>\n : never;\n\ntype RequiresOf<Class> = Class extends new () => { ops : infer Requires } ? Requires : never;\n\nexport type StateFor<Behaviors extends readonly BehaviorClass[]>\n = GameEntityState & UnionToIntersection<StateOf<Behaviors[number]>> & object;\n\nexport type OpsFor<Behaviors extends readonly BehaviorClass[]>\n = UnionToIntersection<OpsFrom<Behaviors[number]>> & object;\n\nexport type OpsOf<Class extends BehaviorClass> = OpsFrom<Class>;\n\nexport type StateSliceOf<Class extends BehaviorClass> = StateOf<Class>;\n\ninterface Unsatisfied<Missing> { 'ERROR: a behavior requires operations the entity does not provide' : Missing }\ninterface DuplicateOperation<Name> { 'ERROR: two behaviors expose the same operation name' : Name }\ninterface IncompatibleState<Name> { 'ERROR: two behaviors declare one state field with incompatible types' : Name }\n\ntype ValidateRequires<Behaviors extends readonly BehaviorClass[]> = {\n [Index in keyof Behaviors] : OpsFor<Behaviors> extends RequiresOf<Behaviors[Index]>\n ? Behaviors[Index]\n : Unsatisfied<Exclude<keyof RequiresOf<Behaviors[Index]>, keyof OpsFor<Behaviors>>>\n};\n\ntype DuplicateNames<Behaviors extends readonly unknown[], Seen extends PropertyKey = never>\n = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? [Extract<OperationNames<Head>, Seen>] extends [never]\n ? DuplicateNames<Tail, Seen | OperationNames<Head>>\n : Extract<OperationNames<Head>, Seen> | DuplicateNames<Tail, Seen | OperationNames<Head>>\n : never;\n\ntype IncompatibleKeys<First, Second> = {\n [Key in keyof First & keyof Second] : [First[Key]] extends [Second[Key]]\n ? [Second[Key]] extends [First[Key]]\n ? never\n : Key\n : Key;\n}[keyof First & keyof Second];\n\ntype IncompatibleStateKeys<\n Behaviors extends readonly unknown[],\n SeenState extends object = object,\n> = Behaviors extends readonly [ infer Head, ...infer Tail ]\n ? IncompatibleKeys<SeenState, StateOf<Head>> | IncompatibleStateKeys<Tail, SeenState & StateOf<Head>>\n : never;\n\nexport type ValidTuple<Behaviors extends readonly BehaviorClass[]>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors\n : ValidateRequires<Behaviors>\n : readonly IncompatibleState<IncompatibleStateKeys<Behaviors>>[]\n : readonly DuplicateOperation<DuplicateNames<Behaviors>>[];\n\nexport interface GameEntityType\n{\n readonly behaviors : readonly BehaviorClass[];\n readonly prototype : GameEntity;\n}\n\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : StateFor<Type['behaviors']>;\n readonly ops : OpsFor<Type['behaviors']>;\n};\n\nexport type NarrowedTo<Class extends BehaviorClass> = GameEntity & {\n readonly state : DeepReadonly<StateSliceOf<Class>>;\n readonly ops : OpsOf<Class>;\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype BehaviorInstance = Behavior<object, object>;\n\ntype OperationTable = Record<PropertyKey, unknown>;\n\ntype EntityConstructor = new (\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n) => GameEntity;\n\ninterface BehaviorListener\n{\n instance : BehaviorInstance;\n exact : string[];\n patterns : RegExp[];\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n// The entity\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class GameEntity implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : GameSimulationContext;\n readonly state : object;\n readonly ops : object;\n readonly eventSubscriptions : readonly EventSubscription[];\n\n static readonly behaviors : readonly BehaviorClass[] = [];\n\n readonly #instances : BehaviorInstance[] = [];\n readonly #listeners : BehaviorListener[] = [];\n readonly #unsubscribe : Unsubscribe;\n\n #operationDepth = 0;\n #creationEvents : RawGameEvent[] | undefined;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors']> },\n id : string,\n context : GameSimulationContext,\n state ?: Partial<StateFor<Type['behaviors']>>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state ?? {}, true, name);\n }\n\n static restore<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors']> },\n id : string,\n context : GameSimulationContext,\n state : StateFor<Type['behaviors']>,\n name ?: string\n ) : ComposedOf<Type>\n {\n return GameEntity.#construct(this, id, context, state, false, name);\n }\n\n static #construct<Type extends GameEntityType>(\n type : Type,\n id : string,\n context : GameSimulationContext,\n state : object,\n runCreateHooks : boolean,\n name ?: string\n ) : ComposedOf<Type>\n {\n const constructable = type as unknown as EntityConstructor;\n\n return new constructable(id, context, state, runCreateHooks, name) as ComposedOf<Type>;\n }\n\n protected constructor(\n id : string,\n context : GameSimulationContext,\n providedState : object,\n runCreateHooks : boolean,\n name ?: string\n )\n {\n this.id = id;\n this.name = name ?? (new.target as typeof GameEntity).name;\n this.context = context;\n\n const behaviorClasses = (new.target as typeof GameEntity).behaviors;\n\n for(const behaviorClass of behaviorClasses)\n {\n this.#instances.push(new behaviorClass());\n }\n\n this.state = GameEntity.#composeState(this.#instances, providedState, id);\n\n const ops = Object.create(null) as OperationTable;\n this.ops = ops;\n\n const subscriptions : EventSubscription[] = [];\n\n for(const [ index, behaviorClass ] of behaviorClasses.entries())\n {\n const instance = this.#instances[index];\n\n if(instance !== undefined)\n {\n Object.assign(instance, { state: this.state, ops, entity: this });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n this.eventSubscriptions = subscriptions;\n this.#unsubscribe = context.bus.register(this, subscriptions);\n\n if(runCreateHooks)\n {\n this.#runCreateHooks();\n }\n }\n\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is NarrowedTo<Class>\n {\n for(const instance of this.#instances)\n {\n if(instance instanceof behaviorClass)\n {\n return true;\n }\n }\n\n return false;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.context.getEntity(id);\n }\n\n emit<Type extends keyof EventMap & string>(type : Type, payload : EventMap[Type], targetID ?: string) : void\n {\n const event : RawGameEvent = { type, senderID: this.id, targetID, payload };\n\n if(this.#creationEvents !== undefined)\n {\n this.#creationEvents.push(event);\n return;\n }\n\n this.context.bus.emit(event as GameEvent);\n }\n\n update(dt : number) : void\n {\n for(const instance of this.#instances)\n {\n instance.onUpdate?.(dt);\n }\n }\n\n receive(event : GameEvent) : boolean\n {\n const raw = event as RawGameEvent;\n\n for(const listener of this.#listeners)\n {\n if(GameEntity.#matches(listener, raw.type) && listener.instance.onEvent?.(event) === true)\n {\n return true;\n }\n }\n\n return false;\n }\n\n destroy() : void\n {\n this.#unsubscribe();\n }\n\n // A copy, so an override reads the attachment order without reaching the list the entity dispatches from.\n protected attachedBehaviors() : readonly BehaviorInstance[]\n {\n return [ ...this.#instances ];\n }\n\n #bindOperations(instance : BehaviorInstance, behaviorClass : BehaviorClass, ops : OperationTable) : void\n {\n for(const name of behaviorClass.ops ?? [])\n {\n if(Object.hasOwn(ops, name))\n {\n throw new Error(`Two behaviors on entity \"${ this.id }\" expose the operation \"${ String(name) }\".`);\n }\n\n const method = (instance as unknown as OperationTable)[name];\n\n if(typeof method !== 'function')\n {\n throw new Error(\n `Behavior \"${ behaviorClass.name }\" declares the operation \"${ String(name) }\" `\n + 'with no method behind it.'\n );\n }\n\n ops[name] = this.#guardedOperation(name, method as (...args : unknown[]) => unknown, instance);\n }\n }\n\n #bindEvents(\n instance : BehaviorInstance,\n behaviorClass : BehaviorClass,\n subscriptions : EventSubscription[]\n ) : void\n {\n const exact : string[] = [];\n const patterns : RegExp[] = [];\n\n for(const subscription of behaviorClass.events ?? [])\n {\n const name : string = subscription;\n\n if(name.endsWith(':*'))\n {\n patterns.push(compileEventPattern(name));\n }\n else\n {\n exact.push(name);\n }\n\n if(!subscriptions.includes(subscription))\n {\n subscriptions.push(subscription);\n }\n }\n\n if(exact.length > 0 || patterns.length > 0)\n {\n this.#listeners.push({ instance, exact, patterns });\n }\n }\n\n #guardedOperation(\n name : PropertyKey,\n method : (...args : unknown[]) => unknown,\n instance : BehaviorInstance\n ) : (...args : unknown[]) => unknown\n {\n return (...args : unknown[]) : unknown =>\n {\n this.#operationDepth += 1;\n\n try\n {\n if(this.#operationDepth > operationCallDepthLimit)\n {\n throw new RangeError(\n `Operation \"${ String(name) }\" on entity \"${ this.id }\" ran deeper than the operation `\n + `call depth limit of ${ operationCallDepthLimit }.`\n );\n }\n\n return method.apply(instance, args);\n }\n finally\n {\n this.#operationDepth -= 1;\n }\n };\n }\n\n // The hooks emit into a held list rather than onto the queue, so a hook that throws leaves the bus as it found it.\n #runCreateHooks() : void\n {\n this.#creationEvents = [];\n\n try\n {\n for(const instance of this.#instances)\n {\n instance.onCreate?.();\n }\n }\n catch(error)\n {\n this.#creationEvents = undefined;\n this.#unsubscribe();\n throw error;\n }\n\n const held = this.#creationEvents;\n this.#creationEvents = undefined;\n\n for(const event of held)\n {\n this.context.bus.emit(event as GameEvent);\n }\n }\n\n static #composeState(instances : readonly BehaviorInstance[], providedState : object, id : string) : StateBag\n {\n const state = Object.create(null) as StateBag;\n state['tags'] = [];\n\n for(const instance of instances)\n {\n Object.assign(state, copyStateBag(instance.defaults as StateBag, id));\n }\n\n Object.assign(state, copyStateBag(providedState as StateBag, id));\n GameEntity.#checkTags(state, id);\n\n return state;\n }\n\n static #checkTags(state : StateBag, id : string) : void\n {\n const tags = state['tags'];\n\n if(!Array.isArray(tags))\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n const seen : string[] = [];\n\n for(const tag of tags)\n {\n if(typeof tag !== 'string')\n {\n throw new TypeError(`Entity \"${ id }\" tags must be a list of strings.`);\n }\n\n if(seen.includes(tag))\n {\n throw new Error(`Entity \"${ id }\" carries the tag \"${ tag }\" more than once.`);\n }\n\n seen.push(tag);\n }\n }\n\n static #matches(listener : BehaviorListener, type : string) : boolean\n {\n if(listener.exact.includes(type))\n {\n return true;\n }\n\n for(const pattern of listener.patterns)\n {\n if(pattern.test(type))\n {\n return true;\n }\n }\n\n return false;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Simulation Manager\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { EventBus } from './eventBus.ts';\nimport {\n type GameEntity, type GameEntityState, type GameEntityType, type StateBag, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { GameSimulationContext } from './simulationContext.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n tick : number;\n idCounter : number;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\nexport interface GameSimulationOptions\n{\n fixedStepDuration : number;\n entityClasses : Readonly<Record<string, GameEntityType>>;\n allocator ?: IDAllocator;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface ConstructableEntityClass extends GameEntityType\n{\n create(id : string, context : GameSimulationContext, state ?: StateBag, name ?: string) : GameEntity;\n restore(id : string, context : GameSimulationContext, state : StateBag, name ?: string) : GameEntity;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class GameSimulationManager implements GameSimulationContext\n{\n readonly #bus = new EventBus();\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Readonly<Record<string, GameEntityType>>;\n readonly #allocator : IDAllocator | undefined;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n\n constructor(options : GameSimulationOptions)\n {\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n\n if(!Number.isFinite(this.#fixedStepDuration) || this.#fixedStepDuration <= 0)\n {\n throw new RangeError('A fixed step duration must be a finite number greater than zero.');\n }\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n spawn(name : string, id ?: string, state ?: StateBag) : GameEntity\n {\n const entityClass = this.#entityClass(name);\n\n let entityID = id;\n\n if(entityID === undefined)\n {\n entityID = this.#mintID();\n }\n\n if(this.getEntity(entityID) !== undefined)\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n const entity = entityClass.create(entityID, this, state ?? {}, name);\n this.#entries.push(entity);\n\n return entity;\n }\n\n despawn(id : string) : void\n {\n const index = this.#entries.findIndex((entity) => entity.id === id);\n\n if(index < 0)\n {\n return;\n }\n\n const entity = this.#entries[index];\n\n if(entity !== undefined)\n {\n entity.destroy();\n this.#entries.splice(index, 1);\n }\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#entries.find((entity) => entity.id === id);\n }\n\n getEntitiesByTag(tag : string) : readonly GameEntity[]\n {\n const found : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if(GameSimulationManager.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n runStepsForElapsedTime(elapsed : number) : void\n {\n if(!Number.isFinite(elapsed) || elapsed < 0)\n {\n throw new RangeError('Elapsed time must be a finite number of zero or more.');\n }\n\n this.#accumulator += elapsed;\n\n let steps = 0;\n\n while(this.#owesAFixedStep() && steps < dilationLimit)\n {\n this.#runFixedStep();\n steps += 1;\n }\n\n // Chronic overrun degrades into simulation time running slower than wall clock: the whole intervals still owed\n // are dropped and only the fraction is carried.\n if(steps === dilationLimit && this.#owesAFixedStep())\n {\n this.#accumulator %= this.#fixedStepDuration;\n }\n }\n\n snapshot() : GameSimulationSnapshot\n {\n const entities : GameEntitySnapshot[] = [];\n\n for(const entity of this.#entries)\n {\n entities.push({\n id: entity.id,\n name: entity.name,\n state: copyStateBag(entity.state as StateBag, entity.id),\n });\n }\n\n return { tick: this.#tick, idCounter: this.#idCounter, entities };\n }\n\n fromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulationManager\n {\n const restored = this.buildReplacementSimulation({\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n });\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(options : GameSimulationOptions) : GameSimulationManager\n {\n return new GameSimulationManager(options);\n }\n\n #load(snapshot : GameSimulationSnapshot) : void\n {\n const entries : GameEntity[] = [];\n\n try\n {\n for(const row of snapshot.entities)\n {\n const entityClass = this.#entityClass(row.name);\n const entity = entityClass.restore(row.id, this, row.state, row.name);\n entries.push(entity);\n }\n }\n catch(error)\n {\n for(const entity of entries)\n {\n entity.destroy();\n }\n\n throw error;\n }\n\n this.#entries = entries;\n this.#tick = snapshot.tick;\n this.#idCounter = snapshot.idCounter;\n }\n\n // The accumulator holds a sum of binary floating point values, which lands a few units in the last place short of\n // the interval the caller meant; without the epsilon three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n return this.#accumulator + Number.EPSILON >= this.#fixedStepDuration;\n }\n\n #runFixedStep() : void\n {\n for(const entity of [ ...this.#entries ])\n {\n entity.update(this.#fixedStepDuration);\n }\n\n // The step ran, so the tick and the accumulator settle whether or not the drain raised.\n try\n {\n this.#bus.drain();\n }\n finally\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= Number.EPSILON)\n {\n this.#accumulator = 0;\n }\n\n this.#tick += 1;\n }\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n return `entity-${ this.#idCounter }`;\n }\n\n #entityClass(name : string) : ConstructableEntityClass\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity class table holds no class named \"${ name }\".`);\n }\n\n return entityClass as ConstructableEntityClass;\n }\n\n // Every entity carries the tags field whatever its behaviors declare, so the query reads it straight.\n static #entityState(entity : GameEntity) : GameEntityState\n {\n return entity.state as GameEntityState;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Behavior } from "./behavior.d.ts";
|
|
2
|
+
export interface StateDefinition<Machine, States extends string> {
|
|
3
|
+
enter?(this: Machine, from: States): void;
|
|
4
|
+
exit?(this: Machine, to: States): void;
|
|
5
|
+
update?(this: Machine, dt: number): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function defineStates<Machine, States extends string>(_machine: Machine, states: Record<States, StateDefinition<Machine, States>>): Record<States, StateDefinition<Machine, States>>;
|
|
8
|
+
export declare abstract class StateMachineBehavior<OwnState extends {
|
|
9
|
+
currentState: string;
|
|
10
|
+
}, Requires extends object = object> extends Behavior<OwnState, Requires> {
|
|
11
|
+
static readonly ops: readonly ["transitionTo"];
|
|
12
|
+
abstract readonly states: Record<OwnState['currentState'], StateDefinition<this, OwnState['currentState']>>;
|
|
13
|
+
onUpdate(dt: number): void;
|
|
14
|
+
transitionTo(to: OwnState['currentState']): void;
|
|
15
|
+
}
|