@skewedaspect/sage-core 0.11.0 → 0.13.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.
@@ -5,6 +5,11 @@ export interface RandomState {
5
5
  readonly c: number;
6
6
  readonly d: number;
7
7
  }
8
+ /**
9
+ * Whether a value is a state a generator takes: four words as `a`, `b`, `c`, and `d`, not all zero
10
+ * (design/simulation.md#the-generator).
11
+ */
12
+ export declare function isRandomState(value: unknown): boolean;
8
13
  /**
9
14
  * The seeded generator, running xoshiro128** over four 32-bit words (design/simulation.md#random-numbers).
10
15
  */
package/dist/sage-core.js CHANGED
@@ -367,34 +367,42 @@ var _ = class e {
367
367
  function w(e) {
368
368
  return e >>> 0;
369
369
  }
370
- function T(e, t) {
370
+ function T(e) {
371
+ return typeof e != "number" || !Number.isInteger(e) ? !1 : e >= 0 && e < C;
372
+ }
373
+ function E(e) {
374
+ if (typeof e != "object" || !e) return !1;
375
+ let t = e;
376
+ return !(!T(t.a) || !T(t.b) || !T(t.c) || !T(t.d) || t.a === 0 && t.b === 0 && t.c === 0 && t.d === 0);
377
+ }
378
+ function D(e, t) {
371
379
  return w(Math.imul(e, t));
372
380
  }
373
- function E(e, t) {
381
+ function O(e, t) {
374
382
  return w(e << t | e >>> 32 - t);
375
383
  }
376
- function D(e) {
384
+ function k(e) {
377
385
  let t = w(v);
378
- for (let n = 0; n < e.length; n++) t = w(t ^ e.charCodeAt(n)), t = T(t, y);
386
+ for (let n = 0; n < e.length; n++) t = w(t ^ e.charCodeAt(n)), t = D(t, y);
379
387
  return t;
380
388
  }
381
- function O(e) {
382
- return typeof e == "string" ? D(e) : w(e);
389
+ function A(e) {
390
+ return typeof e == "string" ? k(e) : w(e);
383
391
  }
384
- function k(e) {
392
+ function j(e) {
385
393
  let t = e;
386
- return t = w(t ^ t >>> 16), t = T(t, x), t = w(t ^ t >>> 15), t = T(t, S), t = w(t ^ t >>> 15), t;
394
+ return t = w(t ^ t >>> 16), t = D(t, x), t = w(t ^ t >>> 15), t = D(t, S), t = w(t ^ t >>> 15), t;
387
395
  }
388
- function A(e) {
396
+ function M(e) {
389
397
  let t = w(e + b), n = w(t + b), r = w(n + b), i = w(r + b);
390
398
  return {
391
- a: k(t),
392
- b: k(n),
393
- c: k(r),
394
- d: k(i)
399
+ a: j(t),
400
+ b: j(n),
401
+ c: j(r),
402
+ d: j(i)
395
403
  };
396
404
  }
397
- var j = class e {
405
+ var N = class e {
398
406
  seed;
399
407
  #e;
400
408
  #t;
@@ -402,8 +410,8 @@ var j = class e {
402
410
  #r;
403
411
  constructor(e) {
404
412
  let t = e;
405
- t === void 0 && (t = Date.now()), this.seed = O(t);
406
- let n = A(this.seed);
413
+ t === void 0 && (t = Date.now()), this.seed = A(t);
414
+ let n = M(this.seed);
407
415
  this.#e = n.a, this.#t = n.b, this.#n = n.c, this.#r = n.d;
408
416
  }
409
417
  get state() {
@@ -415,11 +423,12 @@ var j = class e {
415
423
  };
416
424
  }
417
425
  set state(e) {
418
- this.#e = w(e.a), this.#t = w(e.b), this.#n = w(e.c), this.#r = w(e.d);
426
+ if (!E(e)) throw RangeError("A GameRandom state is four whole numbers from 0 to 2^32 - 1 as a, b, c, and d, not all zero.");
427
+ this.#e = e.a, this.#t = e.b, this.#n = e.c, this.#r = e.d;
419
428
  }
420
429
  nextInt() {
421
- let e = T(E(T(this.#t, 5), 7), 9), t = w(this.#t << 9);
422
- return this.#n = w(this.#n ^ this.#e), this.#r = w(this.#r ^ this.#t), this.#t = w(this.#t ^ this.#n), this.#e = w(this.#e ^ this.#r), this.#n = w(this.#n ^ t), this.#r = E(this.#r, 11), e;
430
+ let e = D(O(D(this.#t, 5), 7), 9), t = w(this.#t << 9);
431
+ return this.#n = w(this.#n ^ this.#e), this.#r = w(this.#r ^ this.#t), this.#t = w(this.#t ^ this.#n), this.#e = w(this.#e ^ this.#r), this.#n = w(this.#n ^ t), this.#r = O(this.#r, 11), e;
423
432
  }
424
433
  nextFloat() {
425
434
  return this.nextInt() / C;
@@ -456,7 +465,7 @@ var j = class e {
456
465
  fork() {
457
466
  return new e(this.nextInt());
458
467
  }
459
- }, M = class extends p {
468
+ }, P = class extends p {
460
469
  #e;
461
470
  #t;
462
471
  #n = [];
@@ -517,7 +526,7 @@ var j = class e {
517
526
  #a() {
518
527
  if (this.#r !== void 0) return this.#t;
519
528
  }
520
- }, N = "global", P = 128 * 2 ** -52, F = class e {
529
+ }, F = "global", I = 128 * 2 ** -52, L = class e {
521
530
  #e;
522
531
  #t;
523
532
  #n;
@@ -533,7 +542,7 @@ var j = class e {
533
542
  #f = /* @__PURE__ */ new Map();
534
543
  #p = /* @__PURE__ */ new Set();
535
544
  constructor(e) {
536
- if (this.#e = e.id, this.#r = e.fixedStepDuration, this.#i = e.entityClasses, this.#a = e.allocator, this.#o = e.seed ?? e.id, this.#s = new j(this.#o), this.#n = e.bus, this.#t = new M(e.id, e.bus), this.#e === N) throw Error(`The simulation name "${N}" is reserved for the persistent simulation.`);
545
+ if (this.#e = e.id, this.#r = e.fixedStepDuration, this.#i = e.entityClasses, this.#a = e.allocator, this.#o = e.seed ?? e.id, this.#s = new N(this.#o), this.#n = e.bus, this.#t = new P(e.id, e.bus), this.#e === F) throw Error(`The simulation name "${F}" is reserved for the persistent simulation.`);
537
546
  if (!Number.isFinite(this.#r) || this.#r <= 0) throw RangeError("A fixed step duration must be a finite number greater than zero.");
538
547
  }
539
548
  get id() {
@@ -623,7 +632,7 @@ var j = class e {
623
632
  };
624
633
  }
625
634
  buildFromSnapshot(e) {
626
- if (e.randomState === void 0) throw Error(`The snapshot of "${e.id}" carries no randomState, so its generator cannot be restored.`);
635
+ if (!E(e.randomState)) throw Error(`The snapshot of "${e.id}" carries no randomState a generator takes, so it cannot be restored.`);
627
636
  let t = this.buildReplacementSimulation({
628
637
  id: e.id,
629
638
  fixedStepDuration: this.#r,
@@ -653,7 +662,7 @@ var j = class e {
653
662
  this.#c = e.tick, this.#l = e.idCounter;
654
663
  }
655
664
  #g() {
656
- let e = P * this.#r;
665
+ let e = I * this.#r;
657
666
  return this.#u + e >= this.#r;
658
667
  }
659
668
  advanceFixedStep(e) {}
@@ -663,7 +672,7 @@ var j = class e {
663
672
  try {
664
673
  this.#t.drain();
665
674
  } finally {
666
- e.fromAccumulator && (this.#u -= this.#r, Math.abs(this.#u) <= P * this.#r && (this.#u = 0)), this.#c += 1;
675
+ e.fromAccumulator && (this.#u -= this.#r, Math.abs(this.#u) <= I * this.#r && (this.#u = 0)), this.#c += 1;
667
676
  }
668
677
  }
669
678
  #v(e) {
@@ -692,6 +701,6 @@ var j = class e {
692
701
  }
693
702
  };
694
703
  //#endregion
695
- export { a as Behavior, p as EventBus, _ as GameEntity, j as GameRandom, F as GameSimulation, o as NamedBehavior, c as StateMachineBehavior, s as defineStates, d as dilationLimit, u as drainPassLimit, l as operationCallDepthLimit };
704
+ export { a as Behavior, p as EventBus, _ as GameEntity, N as GameRandom, L as GameSimulation, o as NamedBehavior, c as StateMachineBehavior, s as defineStates, d as dilationLimit, u as drainPassLimit, l as operationCallDepthLimit };
696
705
 
697
706
  //# sourceMappingURL=sage-core.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sage-core.js","names":[],"sources":["../src/computedState.ts","../src/behavior.ts","../src/namedBehavior.ts","../src/stateMachineBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/gameRandom.ts","../src/simulationBus.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Computed State\n//\n// A field can carry more than one computation, so what each one replaced is remembered rather than assumed to have\n// been a plain value (design/entities.md#computed-state).\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a computed field is read and written through (design/entities.md#computed-state). */\nexport interface Computation<Value>\n{\n get : () => Value;\n set : (value : Value) => void;\n}\n\ninterface Replaced\n{\n field : string;\n beneath : PropertyDescriptor;\n}\n\nconst replaced = new WeakMap<object, Replaced[]>();\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction replacedOn(state : object) : Replaced[]\n{\n let entries = replaced.get(state);\n\n if(entries === undefined)\n {\n entries = [];\n replaced.set(state, entries);\n }\n\n return entries;\n}\n\nexport function defineComputed<Value>(state : object, field : string, computation : Computation<Value>) : void\n{\n let beneath = Object.getOwnPropertyDescriptor(state, field);\n\n if(beneath === undefined)\n {\n beneath = { value: undefined, writable: true, enumerable: true, configurable: true };\n }\n\n replacedOn(state).push({ field, beneath });\n\n Object.defineProperty(state, field, {\n get: computation.get,\n set: computation.set,\n enumerable: true,\n configurable: true,\n });\n}\n\n// A computation may answer with a live view of what it computes from, so what a removal leaves behind is copied\n// out of the answer rather than kept (design/entities.md#computed-state). Records carry no prototype, the way\n// every other record in a state bag does.\nfunction plainValue(value : unknown) : unknown\n{\n if(Array.isArray(value))\n {\n const items : unknown[] = [];\n\n for(const item of value)\n {\n items.push(plainValue(item));\n }\n\n return items;\n }\n\n if(typeof value === 'object' && value !== null)\n {\n const copy = Object.create(null) as Record<string, unknown>;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = plainValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\n/**\n * Take every computation off a state bag, newest first, so a computation over a computation leaves the one beneath\n * it answering and the field ends as the plain value the last one answered with.\n */\nexport function removeComputed(state : object) : void\n{\n const entries = replaced.get(state);\n\n if(entries === undefined)\n {\n return;\n }\n\n for(let index = entries.length - 1; index >= 0; index--)\n {\n const entry = entries[index];\n const answered = plainValue((state as Record<string, unknown>)[entry.field]);\n\n if('value' in entry.beneath)\n {\n Object.defineProperty(state, entry.field, {\n value: answered,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n else\n {\n Object.defineProperty(state, entry.field, entry.beneath);\n }\n }\n\n entries.length = 0;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed } from './computedState.ts';\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\nimport type { GameSimulationView } from './simulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The simulation an entity of this kind is created with: a `NodeEntity`'s behavior reads a `NodeSimulationContext`\n// through `this.simulation`, and every other behavior reads the plain view\n// (design/behaviors.md#inside-a-behavior).\nexport type SimulationFor<Entity extends GameEntity> = Entity extends GameEntity<infer Context>\n ? Context\n : GameSimulationView;\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<\n State extends object,\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : Entity;\n\n // The simulation the entity was created with. `GameEntity` puts it on beside `entity`, and it is protected\n // because the raw bus behind it emits as anyone (design/entities.md#cross-entity-access).\n protected declare readonly simulation : SimulationFor<Entity>;\n\n abstract readonly defaults : State;\n\n onStateReady?() : void;\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed\n // (design/entities.md#computed-state).\n protected computed<Field extends keyof State & string>(\n field : Field,\n computation : Computation<State[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\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\nimport type { GameEntity } from './gameEntity.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`, so the object literal gets a contextual type\n// and every enter, exit, and update inside it sees `this.state` and its own parameter typed. The two plainer forms\n// both fail: a method returning the table cannot name `this` in its own return type without TS7022, and a field\n// annotated by hand repeats the union of state names that `currentState` already declares.\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 Entity extends GameEntity = GameEntity,\n> extends Behavior<OwnState, Requires, Entity>\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 if(!Object.hasOwn(this.states, current))\n {\n throw new Error(\n `StateMachineBehavior on entity \"${ this.entity.id }\": currentState is \"${ current }\", `\n + 'which states does not declare.'\n );\n }\n\n this.states[current].update?.call(this, dt);\n }\n\n transitionTo(to : OwnState['currentState']) : void\n {\n if(!Object.hasOwn(this.states, to))\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\n// The events a subscription hears: the one type it names, or every type under the prefix a pattern names.\nexport type EventsMatching<Subscription extends EventSubscription> = Subscription extends `${ infer Prefix }:*`\n ? Extract<GameEvent, { type : `${ Prefix }:${ string }` }>\n : Extract<GameEvent, { type : Subscription }>;\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 EventsMatching, type GameEvent, type RawGameEvent, type Subscriber,\n type Unsubscribe, compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EventCallback<Subscription extends EventSubscription = EventSubscription>\n = (event : EventsMatching<Subscription>) => void;\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 #mintedSubscribers = 0;\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 // Anything that is not an entity subscribes this way. The bus mints the subscriber ID, and what the callback\n // answers with is ignored: consuming is an entity's own chain mechanic (design/events.md#registration).\n on<Subscription extends EventSubscription>(\n subscription : Subscription,\n callback : EventCallback<Subscription>\n ) : Unsubscribe\n {\n this.#mintedSubscribers += 1;\n\n const subscriber : Subscriber = {\n id: this.mintSubscriberID(this.#mintedSubscribers),\n receive(event : GameEvent) : boolean\n {\n callback(event as EventsMatching<Subscription>);\n return false;\n },\n };\n\n return this.register(subscriber, [ subscription ]);\n }\n\n /**\n * The ID the n-th subscriber `on` makes registers under (design/events.md#registration).\n */\n protected mintSubscriberID(ordinal : number) : string\n {\n return `subscriber-${ ordinal }`;\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 // What a simulation's bus hands over when the simulation attaches to the engine's, in emission order.\n protected takeQueuedEvents() : GameEvent[]\n {\n const queued = this.#queue;\n this.#queue = [];\n\n return queued as GameEvent[];\n }\n\n // What a simulation's bus hands back to itself on detach: whichever of its own emissions the given bus has not\n // yet drained, in their original relative order, pulled out of that bus's queue rather than everyone's queued\n // events (design/events.md#one-bus-per-engine).\n protected static reclaimQueuedEvents(bus : EventBus, events : readonly RawGameEvent[]) : GameEvent[]\n {\n const reclaimed : RawGameEvent[] = [];\n const remaining : RawGameEvent[] = [];\n\n for(const event of bus.#queue)\n {\n if(events.includes(event))\n {\n reclaimed.push(event);\n }\n else\n {\n remaining.push(event);\n }\n }\n\n bus.#queue = remaining;\n\n return reclaimed as GameEvent[];\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 { type Computation, defineComputed, removeComputed } from './computedState.ts';\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 { GameSimulationView } from './simulation.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 let fieldPath = `${ path }.${ key }`;\n\n if(path === '')\n {\n fieldPath = key;\n }\n\n copy[key] = copyStateValue(field, id, fieldPath, 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\ntype EntityOf<Class> = Class extends new () => { entity : infer Entity } ? Entity : GameEntity;\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 }\ninterface WrongEntity<Class> { 'ERROR: a behavior attaches to an entity of another class' : Class }\n\ntype ValidateEntity<Behaviors extends readonly BehaviorClass[], Entity> = {\n [Index in keyof Behaviors] : Entity extends EntityOf<Behaviors[Index]>\n ? Behaviors[Index]\n : WrongEntity<Behaviors[Index]>\n};\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[], Entity = GameEntity>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors extends ValidateEntity<Behaviors, Entity>\n ? Behaviors\n : ValidateEntity<Behaviors, Entity>\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\n// An entity class as an entity registry holds it: the construction signature is a function-typed property rather than a\n// method, so a subclass narrowing the context it constructs with is checked wherever the class is handed around as\n// a type, the entity registry included (design/entities.md#the-entity-registry).\nexport interface GameEntityClass<Context extends GameSimulationView = GameSimulationView> extends GameEntityType\n{\n readonly create : (id : string, context : Context, state ?: object, name ?: string) => GameEntity;\n readonly restore : (id : string, context : Context, state : GameEntityState, name ?: string) => GameEntity;\n}\n\n// An entity's own state is deep read-only; the one writable view of a slice is the behavior's own `this.state`\n// (design/entities.md#cross-entity-access).\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : DeepReadonly<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 : GameSimulationView,\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\n// The context an entity is created with is its type parameter, so a behavior declaring that entity reads the same\n// kind through `this.simulation` (design/behaviors.md#inside-a-behavior).\nexport abstract class GameEntity<Context extends GameSimulationView = GameSimulationView> implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : Context;\n readonly state : object;\n readonly ops : object;\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 #destroyed = false;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\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'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\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 : GameSimulationView,\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 : Context,\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, simulation: context });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n // A field the entity class computes itself goes on before any behavior's onStateReady, so a behavior\n // computing the same field lands over it rather than under it (design/entities.md#computed-state).\n // This runs inside the base constructor: a subclass's own fields and private methods do not exist yet, so\n // an override reaches this.state and nothing else of its own.\n this.declareComputedState(providedState);\n\n // Every behavior reaches its own state here, before a creation hook or a step could read it, and a restored\n // entity runs these exactly as a created one does (design/behaviors.md#hooks).\n for(const instance of this.#instances)\n {\n instance.onStateReady?.();\n }\n\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 // A behavior that despawns this entity ends the loop here, so a despawn partway through leaves the\n // remaining behaviors' onUpdate unrun (design/entities.md#destruction).\n if(this.#destroyed)\n {\n return;\n }\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.#destroyed = true;\n this.removeComputedState();\n this.#unsubscribe();\n }\n\n // Overridden by an entity class with a field of its own to compute. The state its caller supplied is what\n // tells a field the spawn asked for from one composition filled in (design/entities.md#computed-state).\n protected declareComputedState(suppliedState : object) : void\n {\n void suppliedState;\n }\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed. The\n // declaring class names its own state, since an entity's `state` is typed by its behaviors rather than by\n // the fields the class declares beside them (design/entities.md#computed-state).\n protected computed<OwnState extends object, Field extends keyof OwnState & string>(\n field : Field,\n computation : Computation<OwnState[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n\n // Called before a subclass runs any teardown of its own, so every getter is asked while whatever answers it\n // is still there (design/entities.md#computed-state).\n protected removeComputedState() : void\n {\n removeComputed(this.state);\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 // No prototype, so nothing here inherits a toString: String(state) and an untemplated interpolation of it throw\n // instead of reading \"[object Object]\".\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// GameRandom\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type RandomSeed = number | string;\n\nexport interface RandomState\n{\n readonly a : number;\n readonly b : number;\n readonly c : number;\n readonly d : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// FNV-1a's offset basis and prime, which hash a string seed to one word (design/simulation.md#the-generator).\nconst hashOffsetBasis = 2166136261;\nconst hashPrime = 16777619;\n\n// splitmix32's three constants, which spread one seed word across the four state words.\nconst splitmixStep = 0x9e3779b9;\nconst splitmixFirstPrime = 0x21f0aaad;\nconst splitmixSecondPrime = 0x735a2d97;\n\nconst wordLimit = 2 ** 32;\n\nfunction toWord(value : number) : number\n{\n return value >>> 0;\n}\n\nfunction multiply(left : number, right : number) : number\n{\n return toWord(Math.imul(left, right));\n}\n\nfunction rotateLeft(word : number, bits : number) : number\n{\n return toWord((word << bits) | (word >>> (32 - bits)));\n}\n\nfunction hashString(text : string) : number\n{\n let hash = toWord(hashOffsetBasis);\n\n for(let index = 0; index < text.length; index++)\n {\n hash = toWord(hash ^ text.charCodeAt(index));\n hash = multiply(hash, hashPrime);\n }\n\n return hash;\n}\n\nfunction seedWord(seed : RandomSeed) : number\n{\n if(typeof seed === 'string')\n {\n return hashString(seed);\n }\n\n return toWord(seed);\n}\n\nfunction mix(word : number) : number\n{\n let mixed = word;\n\n mixed = toWord(mixed ^ (mixed >>> 16));\n mixed = multiply(mixed, splitmixFirstPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n mixed = multiply(mixed, splitmixSecondPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n\n return mixed;\n}\n\nfunction spread(word : number) : RandomState\n{\n const first = toWord(word + splitmixStep);\n const second = toWord(first + splitmixStep);\n const third = toWord(second + splitmixStep);\n const fourth = toWord(third + splitmixStep);\n\n return { a: mix(first), b: mix(second), c: mix(third), d: mix(fourth) };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The seeded generator, running xoshiro128** over four 32-bit words (design/simulation.md#random-numbers).\n */\nexport class GameRandom\n{\n readonly seed : number;\n\n #a : number;\n #b : number;\n #c : number;\n #d : number;\n\n constructor(seed ?: RandomSeed)\n {\n let chosen = seed;\n\n if(chosen === undefined)\n {\n // A generator built with no seed is the one sequence SAGE does not repeat, and this is the one clock\n // SAGE's own code reads (design/simulation.md#the-generator).\n chosen = Date.now();\n }\n\n this.seed = seedWord(chosen);\n\n const words = spread(this.seed);\n\n this.#a = words.a;\n this.#b = words.b;\n this.#c = words.c;\n this.#d = words.d;\n }\n\n get state() : RandomState\n {\n return { a: this.#a, b: this.#b, c: this.#c, d: this.#d };\n }\n\n set state(value : RandomState)\n {\n this.#a = toWord(value.a);\n this.#b = toWord(value.b);\n this.#c = toWord(value.c);\n this.#d = toWord(value.d);\n }\n\n nextInt() : number\n {\n const result = multiply(rotateLeft(multiply(this.#b, 5), 7), 9);\n const shifted = toWord(this.#b << 9);\n\n this.#c = toWord(this.#c ^ this.#a);\n this.#d = toWord(this.#d ^ this.#b);\n this.#b = toWord(this.#b ^ this.#c);\n this.#a = toWord(this.#a ^ this.#d);\n this.#c = toWord(this.#c ^ shifted);\n this.#d = rotateLeft(this.#d, 11);\n\n return result;\n }\n\n nextFloat() : number\n {\n return this.nextInt() / wordLimit;\n }\n\n between(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + (this.nextFloat() * (max - min));\n }\n\n wholeBetween(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + Math.floor(this.nextFloat() * (max - min + 1));\n }\n\n chance(probability : number) : boolean\n {\n if(probability < 0 || probability > 1)\n {\n throw new RangeError(`A probability of ${ probability } is outside [0, 1].`);\n }\n\n return this.nextFloat() < probability;\n }\n\n normal(mean = 0, deviation = 1) : number\n {\n if(deviation < 0)\n {\n throw new RangeError(`A deviation of ${ deviation } is negative.`);\n }\n\n // Box-Muller. The first draw is taken as 1 - nextFloat() so the logarithm never meets zero\n // (design/simulation.md#drawing).\n const first = 1 - this.nextFloat();\n const second = this.nextFloat();\n\n return mean + (deviation * Math.sqrt(-2 * Math.log(first)) * Math.cos(2 * Math.PI * second));\n }\n\n pick<Item>(items : readonly Item[]) : Item\n {\n if(items.length === 0)\n {\n throw new RangeError('Cannot pick from an empty items array.');\n }\n\n return items[this.wholeBetween(0, items.length - 1)];\n }\n\n shuffle<Item>(items : readonly Item[]) : Item[]\n {\n const shuffled = [ ...items ];\n\n for(let index = shuffled.length - 1; index > 0; index--)\n {\n const swap = this.wholeBetween(0, index);\n const held = shuffled[index];\n\n shuffled[index] = shuffled[swap];\n shuffled[swap] = held;\n }\n\n return shuffled;\n }\n\n fork() : GameRandom\n {\n return new GameRandom(this.nextInt());\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SimulationBus\n//\n// The bus a simulation's entities see. Detached, it is an ordinary bus of its own; attached, everything it is asked\n// goes to the engine's bus instead, and what it held moves across (design/events.md#one-bus-per-engine).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { EventBus } from './eventBus.ts';\n\nimport type { EventSubscription, GameEvent, RawGameEvent, Subscriber, Unsubscribe } from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface Registration\n{\n subscriber : Subscriber;\n subscriptions : readonly EventSubscription[];\n teardown : Unsubscribe;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationBus extends EventBus\n{\n readonly #name : string;\n readonly #engineBus : EventBus | undefined;\n readonly #registrations : Registration[] = [];\n\n #attachedAs : Unsubscribe | undefined;\n #forwarded : RawGameEvent[] = [];\n\n constructor(name : string, engineBus : EventBus | undefined)\n {\n super();\n\n this.#name = name;\n this.#engineBus = engineBus;\n }\n\n get attached() : boolean\n {\n return this.#attachedAs !== undefined;\n }\n\n // A subscriber made here reaches the engine bus beside the engine's own, so its ID carries the simulation's\n // name the way the simulation's entities' IDs do (design/events.md#registration).\n protected override mintSubscriberID(ordinal : number) : string\n {\n return `${ this.#name }-subscriber-${ ordinal }`;\n }\n\n override register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n const attachedTo = this.#attachedTo();\n\n let teardown : Unsubscribe;\n\n if(attachedTo !== undefined)\n {\n teardown = attachedTo.register(subscriber, subscriptions);\n }\n else\n {\n teardown = super.register(subscriber, subscriptions);\n }\n\n const registration : Registration = { subscriber, subscriptions, teardown };\n this.#registrations.push(registration);\n\n return () : void =>\n {\n const index = this.#registrations.indexOf(registration);\n\n if(index < 0)\n {\n return;\n }\n\n this.#registrations.splice(index, 1);\n registration.teardown();\n };\n }\n\n override emit(event : GameEvent) : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n // Kept so detach can reclaim it if the engine bus has not drained it yet\n // (design/events.md#one-bus-per-engine).\n this.#forwarded.push(event as RawGameEvent);\n attachedTo.emit(event);\n }\n else\n {\n super.emit(event);\n }\n }\n\n override drain() : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n try\n {\n attachedTo.drain();\n }\n finally\n {\n this.#forwarded = [];\n }\n }\n else\n {\n super.drain();\n }\n }\n\n attach() : void\n {\n // With no engine bus this already holds every registration and every emission attaching would move, which\n // is the state attaching produces, so it changes nothing the way attaching an attached simulation does\n // (design/events.md#one-bus-per-engine).\n if(this.#engineBus === undefined || this.#attachedAs !== undefined)\n {\n return;\n }\n\n // The simulation registers under its own name first, which is what refuses a second attached simulation\n // of the same name (design/simulation.md#the-simulations-name).\n this.#attachedAs = this.#engineBus.register({ id: this.#name, receive: () => false }, []);\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = this.#engineBus.register(registration.subscriber, registration.subscriptions);\n }\n\n for(const event of this.takeQueuedEvents())\n {\n this.#engineBus.emit(event);\n }\n }\n\n detach() : void\n {\n if(this.#attachedAs === undefined)\n {\n return;\n }\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = super.register(registration.subscriber, registration.subscriptions);\n }\n\n // Detaching reverses attaching in both directions: subscribers move back here, and so does whichever of\n // this simulation's own emissions the engine bus has not yet drained (design/events.md#one-bus-per-engine).\n const engineBus = this.#engineBus;\n\n if(engineBus !== undefined)\n {\n for(const event of EventBus.reclaimQueuedEvents(engineBus, this.#forwarded))\n {\n super.emit(event);\n }\n }\n\n this.#forwarded = [];\n\n this.#attachedAs();\n this.#attachedAs = undefined;\n }\n\n #attachedTo() : EventBus | undefined\n {\n if(this.#attachedAs === undefined)\n {\n return undefined;\n }\n\n return this.#engineBus;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameSimulation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { GameRandom, type RandomSeed, type RandomState } from './gameRandom.ts';\nimport { SimulationBus } from './simulationBus.ts';\nimport {\n type ComposedOf, type GameEntity, type GameEntityClass, type GameEntityState, type GameEntityType, type StateBag,\n type StateFor, type ValidTuple, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { BehaviorClass } from './behavior.ts';\n\nimport type { EventBus } from './eventBus.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n id : string;\n tick : number;\n idCounter : number;\n randomState : RandomState;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\n// The classes a simulation spawns from, each constructable with the context the simulation provides.\nexport type GameEntityRegistry<Context extends GameSimulationView = GameSimulationView>\n = Readonly<Record<string, GameEntityClass<Context>>>;\n\n// Every class in the registry carries the composition check its own `create` carries, so a composition the\n// compiler refuses is refused at the registry (design/entities.md#the-entity-registry).\nexport type ValidEntityRegistry<Registry extends GameEntityRegistry<never>> = {\n readonly [Name in keyof Registry] : Registry[Name] & {\n readonly behaviors : ValidTuple<Registry[Name]['behaviors'], Registry[Name]['prototype']>;\n };\n};\n\nexport interface GameSimulationOptions<Registry extends GameEntityRegistry<never> = GameEntityRegistry>\n{\n id : string;\n fixedStepDuration : number;\n entityClasses : Registry;\n allocator ?: IDAllocator;\n seed ?: RandomSeed;\n bus ?: EventBus;\n}\n\n// A table typed as any class at all types nothing about a spawn, so its state is the plain bag.\ntype SpawnState<Type extends GameEntityType> = readonly BehaviorClass[] extends Type['behaviors']\n ? StateBag\n : Partial<StateFor<Type['behaviors']>>;\n\n// The persistent simulation, built after 0.10.0, takes this name (design/simulation.md#the-simulations-name).\nconst reservedName = 'global';\n\n// How close to a whole interval the accumulator has to be to owe a fixed step, as a fraction of the fixed-step\n// duration. A unit in the last place is proportional to the value it sits in, so an absolute constant is a wide\n// tolerance for a duration in seconds and no tolerance at all for the same rate written in milliseconds\n// (design/simulation.md#the-fixed-step).\nconst fixedStepTolerance = 128 * Number.EPSILON;\n/**\n * A simulation with its entity registry unknown: every member of `GameSimulation` whose type does not name the\n * registry. `GameSimulation` is invariant in its registry, so this is what code takes when it holds a simulation\n * without knowing what it spawns; `spawn` and `buildFromSnapshot` are the two members it lacks. It is also what an\n * entity is created with, and what a behavior reaches as `this.simulation`\n * (design/simulation.md#the-simulation, design/entities.md#cross-entity-access).\n */\nexport interface GameSimulationView\n{\n readonly id : string;\n readonly bus : EventBus;\n readonly attached : boolean;\n readonly tick : number;\n readonly entities : readonly GameEntity[];\n attach() : void;\n detach() : void;\n spawnByName(name : string, state ?: StateBag, id ?: string) : GameEntity;\n despawn(id : string) : boolean;\n getEntity(id : string) : GameEntity | undefined;\n getEntitiesByTag(tag : string) : readonly GameEntity[];\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[];\n runStepsForElapsedTime(elapsed : number) : void;\n runFixedSteps(count : number) : void;\n readonly random : GameRandom;\n snapshot() : GameSimulationSnapshot;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The entity registry is typed by the context this simulation provides to `create`: a subclass providing a narrower one\n// names it as `Context` and implements it, which is what lets a class needing that context into its registry\n// (design/entities.md#the-entity-registry).\nexport class GameSimulation<\n Registry extends GameEntityRegistry<Context> = GameEntityRegistry,\n Context extends GameSimulationView = GameSimulationView,\n> implements GameSimulationView, GameSimulationView\n{\n readonly #id : string;\n readonly #bus : SimulationBus;\n readonly #engineBus : EventBus | undefined;\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Registry;\n readonly #allocator : IDAllocator | undefined;\n readonly #seed : RandomSeed;\n readonly #random : GameRandom;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n #byID = new Map<string, GameEntity>();\n #retiredIDs = new Set<string>();\n\n constructor(options : GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n this.#id = options.id;\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n\n // A simulation constructed with no seed seeds from its own name, so it rolls the same sequence every run\n // (design/simulation.md#the-simulations-generator).\n this.#seed = options.seed ?? options.id;\n this.#random = new GameRandom(this.#seed);\n\n this.#engineBus = options.bus;\n this.#bus = new SimulationBus(options.id, options.bus);\n\n if(this.#id === reservedName)\n {\n throw new Error(`The simulation name \"${ reservedName }\" is reserved for the persistent simulation.`);\n }\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 id() : string\n {\n return this.#id;\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get attached() : boolean\n {\n return this.#bus.attached;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get stepFraction() : number\n {\n return this.#accumulator / this.#fixedStepDuration;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n attach() : void\n {\n this.#bus.attach();\n }\n\n detach() : void\n {\n this.#bus.detach();\n }\n\n spawn<Name extends keyof Registry & string>(\n name : Name,\n state ?: SpawnState<Registry[Name]>,\n id ?: string\n ) : ComposedOf<Registry[Name]>\n {\n return this.spawnByName(name, state as StateBag | undefined, id) as ComposedOf<Registry[Name]>;\n }\n\n /**\n * The same spawn for a name known only at run time, a marker's or a console's: the registry checks the name here\n * rather than the compiler at the call (design/simulation.md#spawn-and-despawn).\n */\n spawnByName(name : string, state ?: StateBag, id ?: string) : 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.#byID.has(entityID))\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n // An ID is unique for the simulation's whole life, so one already despawned never spawns again\n // (design/entities.md#ids).\n if(this.#retiredIDs.has(entityID))\n {\n throw new Error(`The simulation already retired the ID \"${ entityID }\"; an ID is never reused.`);\n }\n\n const entity = entityClass.create(entityID, this.#context(), state ?? {}, name);\n\n this.#entries.push(entity);\n this.#byID.set(entityID, entity);\n\n return entity;\n }\n\n despawn(id : string) : boolean\n {\n return this.#despawn(id, new Set<string>());\n }\n\n #despawn(id : string, inFlight : Set<string>) : boolean\n {\n const entity = this.#byID.get(id);\n\n if(entity === undefined)\n {\n return false;\n }\n\n inFlight.add(id);\n\n // Children first, each found by the parent ID in its state (design/simulation.md#spawn-and-despawn). A\n // parent ID is a state field, and writing state notifies nobody, so the children are found by looking rather\n // than kept in an index that a write would leave stale. A parent ID cycle would otherwise recurse without\n // end; an ID already in flight is skipped instead, so a cycle despawns each entity once.\n for(const child of this.#childrenOf(id))\n {\n if(!inFlight.has(child.id))\n {\n this.#despawn(child.id, inFlight);\n }\n }\n\n entity.destroy();\n\n this.#entries.splice(this.#entries.indexOf(entity), 1);\n this.#byID.delete(id);\n this.#retiredIDs.add(id);\n\n return true;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#byID.get(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(GameSimulation.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[]\n {\n const found : ComposedOf<Type>[] = [];\n\n for(const entity of this.#entries)\n {\n if(entity instanceof (entityClass as unknown as abstract new () => GameEntity))\n {\n found.push(entity as ComposedOf<Type>);\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({ fromAccumulator: true });\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 runFixedSteps(count : number) : void\n {\n if(!Number.isInteger(count) || count < 0)\n {\n throw new RangeError(`runFixedSteps requires a whole number of steps, 0 or more, got ${ count }.`);\n }\n\n for(let step = 0; step < count; step += 1)\n {\n this.#runFixedStep({ fromAccumulator: false });\n }\n }\n\n get random() : GameRandom\n {\n return this.#random;\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 {\n id: this.#id,\n tick: this.#tick,\n idCounter: this.#idCounter,\n randomState: this.#random.state,\n entities,\n };\n }\n\n buildFromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulation<Registry, Context>\n {\n // A snapshot read back from JSON is typed only by trust, and one written before snapshots carried the\n // generator has none to restore.\n if(snapshot.randomState === undefined)\n {\n throw new Error(\n `The snapshot of \"${ snapshot.id }\" carries no randomState, so its generator cannot be restored.`\n );\n }\n\n const restored = this.buildReplacementSimulation({\n id: snapshot.id,\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n seed: this.#seed,\n bus: this.#engineBus,\n });\n\n // Built on this simulation's seed, the replacement still rolls what the simulation that took the snapshot\n // would have rolled next (design/simulation.md#the-simulations-generator).\n restored.#random.state = snapshot.randomState;\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(\n options : GameSimulationOptions<Registry>\n ) : GameSimulation<Registry, Context>\n {\n return new GameSimulation<Registry, Context>(options as GameSimulationOptions<Registry> & {\n entityClasses : ValidEntityRegistry<Registry>;\n });\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(\n row.id,\n this.#context(),\n row.state as GameEntityState & StateBag,\n row.name\n );\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.#byID = new Map();\n\n for(const entity of entries)\n {\n this.#byID.set(entity.id, entity);\n }\n\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 tolerance three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n const tolerance = fixedStepTolerance * this.#fixedStepDuration;\n\n return this.#accumulator + tolerance >= this.#fixedStepDuration;\n }\n\n /**\n * Advances whatever else this simulation carries, once per fixed step, after every entity has updated\n * and before the bus drains.\n *\n * `sage-core` carries nothing and does nothing here. A simulation holding a physics engine steps it\n * from this, so what a behavior told a body during an update takes effect in the step it was told and\n * what physics reports drains in the step it happened (design/simulation.md#the-fixed-step). It is\n * synchronous because a fixed step is, and a throw from it leaves the simulation dead exactly as a\n * throw from an entity's update does (design/simulation.md#failure).\n *\n * @param duration - The fixed step's own duration, never a frame's elapsed time.\n */\n protected advanceFixedStep(duration : number) : void\n {\n void duration;\n }\n\n // A step `runStepsForElapsedTime` runs comes out of the accumulator; one `runFixedSteps` runs leaves the\n // accumulator as it was (design/simulation.md#the-fixed-step).\n #runFixedStep(options : { fromAccumulator : boolean }) : void\n {\n // An entity already despawned by an earlier one's update this step never starts its own; an entity that\n // despawns itself partway through its own update stops there too, in GameEntity.update\n // (design/entities.md#destruction).\n for(const entity of [ ...this.#entries ])\n {\n if(this.#byID.get(entity.id) === entity)\n {\n entity.update(this.#fixedStepDuration);\n }\n }\n\n this.advanceFixedStep(this.#fixedStepDuration);\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 if(options.fromAccumulator)\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= fixedStepTolerance * this.#fixedStepDuration)\n {\n this.#accumulator = 0;\n }\n }\n\n this.#tick += 1;\n }\n }\n\n #childrenOf(parentID : string) : GameEntity[]\n {\n const children : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if((entity.state as StateBag)['parentID'] === parentID)\n {\n children.push(entity);\n }\n }\n\n return children;\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n while(this.#idIsSpent(`${ this.#id }-${ this.#idCounter }`))\n {\n this.#idCounter += 1;\n }\n\n return `${ this.#id }-${ this.#idCounter }`;\n }\n\n // A despawned ID is spent and never minted again (design/entities.md#ids).\n #idIsSpent(candidate : string) : boolean\n {\n return this.#byID.has(candidate) || this.#retiredIDs.has(candidate);\n }\n\n // This simulation is the context its entities are created with. The cast is unchecked: TypeScript accepts a\n // subclass naming a narrower Context whether or not the subclass provides it; only a subclass that also writes\n // its own `implements Context` is checked against the shape it named.\n #context() : Context\n {\n return this as unknown as Context;\n }\n\n #entityClass(name : string) : GameEntityClass<Context>\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity registry holds no class named \"${ name }\".`);\n }\n\n return entityClass;\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\n"],"mappings":"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"}
1
+ {"version":3,"file":"sage-core.js","names":[],"sources":["../src/computedState.ts","../src/behavior.ts","../src/namedBehavior.ts","../src/stateMachineBehavior.ts","../src/constants.ts","../src/events.ts","../src/eventBus.ts","../src/gameEntity.ts","../src/gameRandom.ts","../src/simulationBus.ts","../src/simulation.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Computed State\n//\n// A field can carry more than one computation, so what each one replaced is remembered rather than assumed to have\n// been a plain value (design/entities.md#computed-state).\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a computed field is read and written through (design/entities.md#computed-state). */\nexport interface Computation<Value>\n{\n get : () => Value;\n set : (value : Value) => void;\n}\n\ninterface Replaced\n{\n field : string;\n beneath : PropertyDescriptor;\n}\n\nconst replaced = new WeakMap<object, Replaced[]>();\n\n//----------------------------------------------------------------------------------------------------------------------\n\nfunction replacedOn(state : object) : Replaced[]\n{\n let entries = replaced.get(state);\n\n if(entries === undefined)\n {\n entries = [];\n replaced.set(state, entries);\n }\n\n return entries;\n}\n\nexport function defineComputed<Value>(state : object, field : string, computation : Computation<Value>) : void\n{\n let beneath = Object.getOwnPropertyDescriptor(state, field);\n\n if(beneath === undefined)\n {\n beneath = { value: undefined, writable: true, enumerable: true, configurable: true };\n }\n\n replacedOn(state).push({ field, beneath });\n\n Object.defineProperty(state, field, {\n get: computation.get,\n set: computation.set,\n enumerable: true,\n configurable: true,\n });\n}\n\n// A computation may answer with a live view of what it computes from, so what a removal leaves behind is copied\n// out of the answer rather than kept (design/entities.md#computed-state). Records carry no prototype, the way\n// every other record in a state bag does.\nfunction plainValue(value : unknown) : unknown\n{\n if(Array.isArray(value))\n {\n const items : unknown[] = [];\n\n for(const item of value)\n {\n items.push(plainValue(item));\n }\n\n return items;\n }\n\n if(typeof value === 'object' && value !== null)\n {\n const copy = Object.create(null) as Record<string, unknown>;\n\n for(const [ key, field ] of Object.entries(value))\n {\n copy[key] = plainValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\n/**\n * Take every computation off a state bag, newest first, so a computation over a computation leaves the one beneath\n * it answering and the field ends as the plain value the last one answered with.\n */\nexport function removeComputed(state : object) : void\n{\n const entries = replaced.get(state);\n\n if(entries === undefined)\n {\n return;\n }\n\n for(let index = entries.length - 1; index >= 0; index--)\n {\n const entry = entries[index];\n const answered = plainValue((state as Record<string, unknown>)[entry.field]);\n\n if('value' in entry.beneath)\n {\n Object.defineProperty(state, entry.field, {\n value: answered,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n else\n {\n Object.defineProperty(state, entry.field, entry.beneath);\n }\n }\n\n entries.length = 0;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// Behavior\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Computation, defineComputed } from './computedState.ts';\n\nimport type { EventSubscription, GameEvent } from './events.ts';\nimport type { GameEntity } from './gameEntity.ts';\nimport type { GameSimulationView } from './simulation.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The simulation an entity of this kind is created with: a `NodeEntity`'s behavior reads a `NodeSimulationContext`\n// through `this.simulation`, and every other behavior reads the plain view\n// (design/behaviors.md#inside-a-behavior).\nexport type SimulationFor<Entity extends GameEntity> = Entity extends GameEntity<infer Context>\n ? Context\n : GameSimulationView;\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport abstract class Behavior<\n State extends object,\n Requires extends object = object,\n Entity extends GameEntity = GameEntity,\n>\n{\n declare readonly state : State;\n declare readonly ops : Requires;\n declare readonly entity : Entity;\n\n // The simulation the entity was created with. `GameEntity` puts it on beside `entity`, and it is protected\n // because the raw bus behind it emits as anyone (design/entities.md#cross-entity-access).\n protected declare readonly simulation : SimulationFor<Entity>;\n\n abstract readonly defaults : State;\n\n onStateReady?() : void;\n onCreate?() : void;\n onUpdate?(dt : number) : void;\n onEvent?(event : GameEvent) : boolean | undefined;\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed\n // (design/entities.md#computed-state).\n protected computed<Field extends keyof State & string>(\n field : Field,\n computation : Computation<State[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\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\nimport type { GameEntity } from './gameEntity.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`, so the object literal gets a contextual type\n// and every enter, exit, and update inside it sees `this.state` and its own parameter typed. The two plainer forms\n// both fail: a method returning the table cannot name `this` in its own return type without TS7022, and a field\n// annotated by hand repeats the union of state names that `currentState` already declares.\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 Entity extends GameEntity = GameEntity,\n> extends Behavior<OwnState, Requires, Entity>\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 if(!Object.hasOwn(this.states, current))\n {\n throw new Error(\n `StateMachineBehavior on entity \"${ this.entity.id }\": currentState is \"${ current }\", `\n + 'which states does not declare.'\n );\n }\n\n this.states[current].update?.call(this, dt);\n }\n\n transitionTo(to : OwnState['currentState']) : void\n {\n if(!Object.hasOwn(this.states, to))\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\n// The events a subscription hears: the one type it names, or every type under the prefix a pattern names.\nexport type EventsMatching<Subscription extends EventSubscription> = Subscription extends `${ infer Prefix }:*`\n ? Extract<GameEvent, { type : `${ Prefix }:${ string }` }>\n : Extract<GameEvent, { type : Subscription }>;\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 EventsMatching, type GameEvent, type RawGameEvent, type Subscriber,\n type Unsubscribe, compileEventPattern,\n} from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type EventCallback<Subscription extends EventSubscription = EventSubscription>\n = (event : EventsMatching<Subscription>) => void;\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 #mintedSubscribers = 0;\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 // Anything that is not an entity subscribes this way. The bus mints the subscriber ID, and what the callback\n // answers with is ignored: consuming is an entity's own chain mechanic (design/events.md#registration).\n on<Subscription extends EventSubscription>(\n subscription : Subscription,\n callback : EventCallback<Subscription>\n ) : Unsubscribe\n {\n this.#mintedSubscribers += 1;\n\n const subscriber : Subscriber = {\n id: this.mintSubscriberID(this.#mintedSubscribers),\n receive(event : GameEvent) : boolean\n {\n callback(event as EventsMatching<Subscription>);\n return false;\n },\n };\n\n return this.register(subscriber, [ subscription ]);\n }\n\n /**\n * The ID the n-th subscriber `on` makes registers under (design/events.md#registration).\n */\n protected mintSubscriberID(ordinal : number) : string\n {\n return `subscriber-${ ordinal }`;\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 // What a simulation's bus hands over when the simulation attaches to the engine's, in emission order.\n protected takeQueuedEvents() : GameEvent[]\n {\n const queued = this.#queue;\n this.#queue = [];\n\n return queued as GameEvent[];\n }\n\n // What a simulation's bus hands back to itself on detach: whichever of its own emissions the given bus has not\n // yet drained, in their original relative order, pulled out of that bus's queue rather than everyone's queued\n // events (design/events.md#one-bus-per-engine).\n protected static reclaimQueuedEvents(bus : EventBus, events : readonly RawGameEvent[]) : GameEvent[]\n {\n const reclaimed : RawGameEvent[] = [];\n const remaining : RawGameEvent[] = [];\n\n for(const event of bus.#queue)\n {\n if(events.includes(event))\n {\n reclaimed.push(event);\n }\n else\n {\n remaining.push(event);\n }\n }\n\n bus.#queue = remaining;\n\n return reclaimed as GameEvent[];\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 { type Computation, defineComputed, removeComputed } from './computedState.ts';\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 { GameSimulationView } from './simulation.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 let fieldPath = `${ path }.${ key }`;\n\n if(path === '')\n {\n fieldPath = key;\n }\n\n copy[key] = copyStateValue(field, id, fieldPath, 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\ntype EntityOf<Class> = Class extends new () => { entity : infer Entity } ? Entity : GameEntity;\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 }\ninterface WrongEntity<Class> { 'ERROR: a behavior attaches to an entity of another class' : Class }\n\ntype ValidateEntity<Behaviors extends readonly BehaviorClass[], Entity> = {\n [Index in keyof Behaviors] : Entity extends EntityOf<Behaviors[Index]>\n ? Behaviors[Index]\n : WrongEntity<Behaviors[Index]>\n};\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[], Entity = GameEntity>\n = [DuplicateNames<Behaviors>] extends [never]\n ? [IncompatibleStateKeys<Behaviors>] extends [never]\n ? Behaviors extends ValidateRequires<Behaviors>\n ? Behaviors extends ValidateEntity<Behaviors, Entity>\n ? Behaviors\n : ValidateEntity<Behaviors, Entity>\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\n// An entity class as an entity registry holds it: the construction signature is a function-typed property rather than a\n// method, so a subclass narrowing the context it constructs with is checked wherever the class is handed around as\n// a type, the entity registry included (design/entities.md#the-entity-registry).\nexport interface GameEntityClass<Context extends GameSimulationView = GameSimulationView> extends GameEntityType\n{\n readonly create : (id : string, context : Context, state ?: object, name ?: string) => GameEntity;\n readonly restore : (id : string, context : Context, state : GameEntityState, name ?: string) => GameEntity;\n}\n\n// An entity's own state is deep read-only; the one writable view of a slice is the behavior's own `this.state`\n// (design/entities.md#cross-entity-access).\nexport type ComposedOf<Type extends GameEntityType> = Type['prototype'] & {\n readonly state : DeepReadonly<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 : GameSimulationView,\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\n// The context an entity is created with is its type parameter, so a behavior declaring that entity reads the same\n// kind through `this.simulation` (design/behaviors.md#inside-a-behavior).\nexport abstract class GameEntity<Context extends GameSimulationView = GameSimulationView> implements Subscriber\n{\n readonly id : string;\n readonly name : string;\n protected readonly context : Context;\n readonly state : object;\n readonly ops : object;\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 #destroyed = false;\n\n static create<Type extends GameEntityType>(\n this : Type & { behaviors : ValidTuple<Type['behaviors'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\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'], Type['prototype']> },\n id : string,\n context : GameSimulationView,\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 : GameSimulationView,\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 : Context,\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, simulation: context });\n this.#bindOperations(instance, behaviorClass, ops);\n this.#bindEvents(instance, behaviorClass, subscriptions);\n }\n }\n\n // A field the entity class computes itself goes on before any behavior's onStateReady, so a behavior\n // computing the same field lands over it rather than under it (design/entities.md#computed-state).\n // This runs inside the base constructor: a subclass's own fields and private methods do not exist yet, so\n // an override reaches this.state and nothing else of its own.\n this.declareComputedState(providedState);\n\n // Every behavior reaches its own state here, before a creation hook or a step could read it, and a restored\n // entity runs these exactly as a created one does (design/behaviors.md#hooks).\n for(const instance of this.#instances)\n {\n instance.onStateReady?.();\n }\n\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 // A behavior that despawns this entity ends the loop here, so a despawn partway through leaves the\n // remaining behaviors' onUpdate unrun (design/entities.md#destruction).\n if(this.#destroyed)\n {\n return;\n }\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.#destroyed = true;\n this.removeComputedState();\n this.#unsubscribe();\n }\n\n // Overridden by an entity class with a field of its own to compute. The state its caller supplied is what\n // tells a field the spawn asked for from one composition filled in (design/entities.md#computed-state).\n protected declareComputedState(suppliedState : object) : void\n {\n void suppliedState;\n }\n\n // Reading the field asks `get` and writing it tells `set`, from here until the entity is destroyed. The\n // declaring class names its own state, since an entity's `state` is typed by its behaviors rather than by\n // the fields the class declares beside them (design/entities.md#computed-state).\n protected computed<OwnState extends object, Field extends keyof OwnState & string>(\n field : Field,\n computation : Computation<OwnState[Field]>\n ) : void\n {\n defineComputed(this.state, field, computation);\n }\n\n // Called before a subclass runs any teardown of its own, so every getter is asked while whatever answers it\n // is still there (design/entities.md#computed-state).\n protected removeComputedState() : void\n {\n removeComputed(this.state);\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 // No prototype, so nothing here inherits a toString: String(state) and an untemplated interpolation of it throw\n // instead of reading \"[object Object]\".\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// GameRandom\n//----------------------------------------------------------------------------------------------------------------------\n\nexport type RandomSeed = number | string;\n\nexport interface RandomState\n{\n readonly a : number;\n readonly b : number;\n readonly c : number;\n readonly d : number;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// FNV-1a's offset basis and prime, which hash a string seed to one word (design/simulation.md#the-generator).\nconst hashOffsetBasis = 2166136261;\nconst hashPrime = 16777619;\n\n// splitmix32's three constants, which spread one seed word across the four state words.\nconst splitmixStep = 0x9e3779b9;\nconst splitmixFirstPrime = 0x21f0aaad;\nconst splitmixSecondPrime = 0x735a2d97;\n\nconst wordLimit = 2 ** 32;\n\nfunction toWord(value : number) : number\n{\n return value >>> 0;\n}\n\nfunction isWord(value : unknown) : boolean\n{\n if(typeof value !== 'number')\n {\n return false;\n }\n\n if(!Number.isInteger(value))\n {\n return false;\n }\n\n return value >= 0 && value < wordLimit;\n}\n\n/**\n * Whether a value is a state a generator takes: four words as `a`, `b`, `c`, and `d`, not all zero\n * (design/simulation.md#the-generator).\n */\nexport function isRandomState(value : unknown) : boolean\n{\n if(typeof value !== 'object' || value === null)\n {\n return false;\n }\n\n const words = value as Partial<Record<keyof RandomState, unknown>>;\n\n if(!isWord(words.a) || !isWord(words.b) || !isWord(words.c) || !isWord(words.d))\n {\n return false;\n }\n\n // Four zeros is the one state xoshiro128** never leaves, and no seed reaches it.\n if(words.a === 0 && words.b === 0 && words.c === 0 && words.d === 0)\n {\n return false;\n }\n\n return true;\n}\n\nfunction multiply(left : number, right : number) : number\n{\n return toWord(Math.imul(left, right));\n}\n\nfunction rotateLeft(word : number, bits : number) : number\n{\n return toWord((word << bits) | (word >>> (32 - bits)));\n}\n\nfunction hashString(text : string) : number\n{\n let hash = toWord(hashOffsetBasis);\n\n for(let index = 0; index < text.length; index++)\n {\n hash = toWord(hash ^ text.charCodeAt(index));\n hash = multiply(hash, hashPrime);\n }\n\n return hash;\n}\n\nfunction seedWord(seed : RandomSeed) : number\n{\n if(typeof seed === 'string')\n {\n return hashString(seed);\n }\n\n return toWord(seed);\n}\n\nfunction mix(word : number) : number\n{\n let mixed = word;\n\n mixed = toWord(mixed ^ (mixed >>> 16));\n mixed = multiply(mixed, splitmixFirstPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n mixed = multiply(mixed, splitmixSecondPrime);\n mixed = toWord(mixed ^ (mixed >>> 15));\n\n return mixed;\n}\n\nfunction spread(word : number) : RandomState\n{\n const first = toWord(word + splitmixStep);\n const second = toWord(first + splitmixStep);\n const third = toWord(second + splitmixStep);\n const fourth = toWord(third + splitmixStep);\n\n return { a: mix(first), b: mix(second), c: mix(third), d: mix(fourth) };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * The seeded generator, running xoshiro128** over four 32-bit words (design/simulation.md#random-numbers).\n */\nexport class GameRandom\n{\n readonly seed : number;\n\n #a : number;\n #b : number;\n #c : number;\n #d : number;\n\n constructor(seed ?: RandomSeed)\n {\n let chosen = seed;\n\n if(chosen === undefined)\n {\n // A generator built with no seed is the one sequence SAGE does not repeat, and this is the one clock\n // SAGE's own code reads (design/simulation.md#the-generator).\n chosen = Date.now();\n }\n\n this.seed = seedWord(chosen);\n\n const words = spread(this.seed);\n\n this.#a = words.a;\n this.#b = words.b;\n this.#c = words.c;\n this.#d = words.d;\n }\n\n get state() : RandomState\n {\n return { a: this.#a, b: this.#b, c: this.#c, d: this.#d };\n }\n\n set state(value : RandomState)\n {\n if(!isRandomState(value))\n {\n throw new RangeError(\n 'A GameRandom state is four whole numbers from 0 to 2^32 - 1 as a, b, c, and d, not all zero.'\n );\n }\n\n this.#a = value.a;\n this.#b = value.b;\n this.#c = value.c;\n this.#d = value.d;\n }\n\n nextInt() : number\n {\n const result = multiply(rotateLeft(multiply(this.#b, 5), 7), 9);\n const shifted = toWord(this.#b << 9);\n\n this.#c = toWord(this.#c ^ this.#a);\n this.#d = toWord(this.#d ^ this.#b);\n this.#b = toWord(this.#b ^ this.#c);\n this.#a = toWord(this.#a ^ this.#d);\n this.#c = toWord(this.#c ^ shifted);\n this.#d = rotateLeft(this.#d, 11);\n\n return result;\n }\n\n nextFloat() : number\n {\n return this.nextInt() / wordLimit;\n }\n\n between(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + (this.nextFloat() * (max - min));\n }\n\n wholeBetween(min : number, max : number) : number\n {\n if(max < min)\n {\n throw new RangeError(`A max of ${ max } is below its min of ${ min }.`);\n }\n\n return min + Math.floor(this.nextFloat() * (max - min + 1));\n }\n\n chance(probability : number) : boolean\n {\n if(probability < 0 || probability > 1)\n {\n throw new RangeError(`A probability of ${ probability } is outside [0, 1].`);\n }\n\n return this.nextFloat() < probability;\n }\n\n normal(mean = 0, deviation = 1) : number\n {\n if(deviation < 0)\n {\n throw new RangeError(`A deviation of ${ deviation } is negative.`);\n }\n\n // Box-Muller. The first draw is taken as 1 - nextFloat() so the logarithm never meets zero\n // (design/simulation.md#drawing).\n const first = 1 - this.nextFloat();\n const second = this.nextFloat();\n\n return mean + (deviation * Math.sqrt(-2 * Math.log(first)) * Math.cos(2 * Math.PI * second));\n }\n\n pick<Item>(items : readonly Item[]) : Item\n {\n if(items.length === 0)\n {\n throw new RangeError('Cannot pick from an empty items array.');\n }\n\n return items[this.wholeBetween(0, items.length - 1)];\n }\n\n shuffle<Item>(items : readonly Item[]) : Item[]\n {\n const shuffled = [ ...items ];\n\n for(let index = shuffled.length - 1; index > 0; index--)\n {\n const swap = this.wholeBetween(0, index);\n const held = shuffled[index];\n\n shuffled[index] = shuffled[swap];\n shuffled[swap] = held;\n }\n\n return shuffled;\n }\n\n fork() : GameRandom\n {\n return new GameRandom(this.nextInt());\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// SimulationBus\n//\n// The bus a simulation's entities see. Detached, it is an ordinary bus of its own; attached, everything it is asked\n// goes to the engine's bus instead, and what it held moves across (design/events.md#one-bus-per-engine).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { EventBus } from './eventBus.ts';\n\nimport type { EventSubscription, GameEvent, RawGameEvent, Subscriber, Unsubscribe } from './events.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface Registration\n{\n subscriber : Subscriber;\n subscriptions : readonly EventSubscription[];\n teardown : Unsubscribe;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport class SimulationBus extends EventBus\n{\n readonly #name : string;\n readonly #engineBus : EventBus | undefined;\n readonly #registrations : Registration[] = [];\n\n #attachedAs : Unsubscribe | undefined;\n #forwarded : RawGameEvent[] = [];\n\n constructor(name : string, engineBus : EventBus | undefined)\n {\n super();\n\n this.#name = name;\n this.#engineBus = engineBus;\n }\n\n get attached() : boolean\n {\n return this.#attachedAs !== undefined;\n }\n\n // A subscriber made here reaches the engine bus beside the engine's own, so its ID carries the simulation's\n // name the way the simulation's entities' IDs do (design/events.md#registration).\n protected override mintSubscriberID(ordinal : number) : string\n {\n return `${ this.#name }-subscriber-${ ordinal }`;\n }\n\n override register(subscriber : Subscriber, subscriptions : readonly EventSubscription[]) : Unsubscribe\n {\n const attachedTo = this.#attachedTo();\n\n let teardown : Unsubscribe;\n\n if(attachedTo !== undefined)\n {\n teardown = attachedTo.register(subscriber, subscriptions);\n }\n else\n {\n teardown = super.register(subscriber, subscriptions);\n }\n\n const registration : Registration = { subscriber, subscriptions, teardown };\n this.#registrations.push(registration);\n\n return () : void =>\n {\n const index = this.#registrations.indexOf(registration);\n\n if(index < 0)\n {\n return;\n }\n\n this.#registrations.splice(index, 1);\n registration.teardown();\n };\n }\n\n override emit(event : GameEvent) : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n // Kept so detach can reclaim it if the engine bus has not drained it yet\n // (design/events.md#one-bus-per-engine).\n this.#forwarded.push(event as RawGameEvent);\n attachedTo.emit(event);\n }\n else\n {\n super.emit(event);\n }\n }\n\n override drain() : void\n {\n const attachedTo = this.#attachedTo();\n\n if(attachedTo !== undefined)\n {\n try\n {\n attachedTo.drain();\n }\n finally\n {\n this.#forwarded = [];\n }\n }\n else\n {\n super.drain();\n }\n }\n\n attach() : void\n {\n // With no engine bus this already holds every registration and every emission attaching would move, which\n // is the state attaching produces, so it changes nothing the way attaching an attached simulation does\n // (design/events.md#one-bus-per-engine).\n if(this.#engineBus === undefined || this.#attachedAs !== undefined)\n {\n return;\n }\n\n // The simulation registers under its own name first, which is what refuses a second attached simulation\n // of the same name (design/simulation.md#the-simulations-name).\n this.#attachedAs = this.#engineBus.register({ id: this.#name, receive: () => false }, []);\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = this.#engineBus.register(registration.subscriber, registration.subscriptions);\n }\n\n for(const event of this.takeQueuedEvents())\n {\n this.#engineBus.emit(event);\n }\n }\n\n detach() : void\n {\n if(this.#attachedAs === undefined)\n {\n return;\n }\n\n for(const registration of this.#registrations)\n {\n registration.teardown();\n registration.teardown = super.register(registration.subscriber, registration.subscriptions);\n }\n\n // Detaching reverses attaching in both directions: subscribers move back here, and so does whichever of\n // this simulation's own emissions the engine bus has not yet drained (design/events.md#one-bus-per-engine).\n const engineBus = this.#engineBus;\n\n if(engineBus !== undefined)\n {\n for(const event of EventBus.reclaimQueuedEvents(engineBus, this.#forwarded))\n {\n super.emit(event);\n }\n }\n\n this.#forwarded = [];\n\n this.#attachedAs();\n this.#attachedAs = undefined;\n }\n\n #attachedTo() : EventBus | undefined\n {\n if(this.#attachedAs === undefined)\n {\n return undefined;\n }\n\n return this.#engineBus;\n }\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// GameSimulation\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { dilationLimit } from './constants.ts';\nimport { GameRandom, type RandomSeed, type RandomState, isRandomState } from './gameRandom.ts';\nimport { SimulationBus } from './simulationBus.ts';\nimport {\n type ComposedOf, type GameEntity, type GameEntityClass, type GameEntityState, type GameEntityType, type StateBag,\n type StateFor, type ValidTuple, copyStateBag,\n} from './gameEntity.ts';\n\nimport type { BehaviorClass } from './behavior.ts';\n\nimport type { EventBus } from './eventBus.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport interface GameEntitySnapshot\n{\n id : string;\n name : string;\n state : StateBag;\n}\n\nexport interface GameSimulationSnapshot\n{\n id : string;\n tick : number;\n idCounter : number;\n randomState : RandomState;\n entities : GameEntitySnapshot[];\n}\n\nexport type IDAllocator = () => string;\n\n// The classes a simulation spawns from, each constructable with the context the simulation provides.\nexport type GameEntityRegistry<Context extends GameSimulationView = GameSimulationView>\n = Readonly<Record<string, GameEntityClass<Context>>>;\n\n// Every class in the registry carries the composition check its own `create` carries, so a composition the\n// compiler refuses is refused at the registry (design/entities.md#the-entity-registry).\nexport type ValidEntityRegistry<Registry extends GameEntityRegistry<never>> = {\n readonly [Name in keyof Registry] : Registry[Name] & {\n readonly behaviors : ValidTuple<Registry[Name]['behaviors'], Registry[Name]['prototype']>;\n };\n};\n\nexport interface GameSimulationOptions<Registry extends GameEntityRegistry<never> = GameEntityRegistry>\n{\n id : string;\n fixedStepDuration : number;\n entityClasses : Registry;\n allocator ?: IDAllocator;\n seed ?: RandomSeed;\n bus ?: EventBus;\n}\n\n// A table typed as any class at all types nothing about a spawn, so its state is the plain bag.\ntype SpawnState<Type extends GameEntityType> = readonly BehaviorClass[] extends Type['behaviors']\n ? StateBag\n : Partial<StateFor<Type['behaviors']>>;\n\n// The persistent simulation, built after 0.10.0, takes this name (design/simulation.md#the-simulations-name).\nconst reservedName = 'global';\n\n// How close to a whole interval the accumulator has to be to owe a fixed step, as a fraction of the fixed-step\n// duration. A unit in the last place is proportional to the value it sits in, so an absolute constant is a wide\n// tolerance for a duration in seconds and no tolerance at all for the same rate written in milliseconds\n// (design/simulation.md#the-fixed-step).\nconst fixedStepTolerance = 128 * Number.EPSILON;\n/**\n * A simulation with its entity registry unknown: every member of `GameSimulation` whose type does not name the\n * registry. `GameSimulation` is invariant in its registry, so this is what code takes when it holds a simulation\n * without knowing what it spawns; `spawn` and `buildFromSnapshot` are the two members it lacks. It is also what an\n * entity is created with, and what a behavior reaches as `this.simulation`\n * (design/simulation.md#the-simulation, design/entities.md#cross-entity-access).\n */\nexport interface GameSimulationView\n{\n readonly id : string;\n readonly bus : EventBus;\n readonly attached : boolean;\n readonly tick : number;\n readonly entities : readonly GameEntity[];\n attach() : void;\n detach() : void;\n spawnByName(name : string, state ?: StateBag, id ?: string) : GameEntity;\n despawn(id : string) : boolean;\n getEntity(id : string) : GameEntity | undefined;\n getEntitiesByTag(tag : string) : readonly GameEntity[];\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[];\n runStepsForElapsedTime(elapsed : number) : void;\n runFixedSteps(count : number) : void;\n readonly random : GameRandom;\n snapshot() : GameSimulationSnapshot;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// The entity registry is typed by the context this simulation provides to `create`: a subclass providing a narrower one\n// names it as `Context` and implements it, which is what lets a class needing that context into its registry\n// (design/entities.md#the-entity-registry).\nexport class GameSimulation<\n Registry extends GameEntityRegistry<Context> = GameEntityRegistry,\n Context extends GameSimulationView = GameSimulationView,\n> implements GameSimulationView, GameSimulationView\n{\n readonly #id : string;\n readonly #bus : SimulationBus;\n readonly #engineBus : EventBus | undefined;\n readonly #fixedStepDuration : number;\n readonly #entityClasses : Registry;\n readonly #allocator : IDAllocator | undefined;\n readonly #seed : RandomSeed;\n readonly #random : GameRandom;\n\n #tick = 0;\n #idCounter = 0;\n #accumulator = 0;\n #entries : GameEntity[] = [];\n #byID = new Map<string, GameEntity>();\n #retiredIDs = new Set<string>();\n\n constructor(options : GameSimulationOptions<Registry> & { entityClasses : ValidEntityRegistry<Registry> })\n {\n this.#id = options.id;\n this.#fixedStepDuration = options.fixedStepDuration;\n this.#entityClasses = options.entityClasses;\n this.#allocator = options.allocator;\n\n // A simulation constructed with no seed seeds from its own name, so it rolls the same sequence every run\n // (design/simulation.md#the-simulations-generator).\n this.#seed = options.seed ?? options.id;\n this.#random = new GameRandom(this.#seed);\n\n this.#engineBus = options.bus;\n this.#bus = new SimulationBus(options.id, options.bus);\n\n if(this.#id === reservedName)\n {\n throw new Error(`The simulation name \"${ reservedName }\" is reserved for the persistent simulation.`);\n }\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 id() : string\n {\n return this.#id;\n }\n\n get bus() : EventBus\n {\n return this.#bus;\n }\n\n get attached() : boolean\n {\n return this.#bus.attached;\n }\n\n get tick() : number\n {\n return this.#tick;\n }\n\n get stepFraction() : number\n {\n return this.#accumulator / this.#fixedStepDuration;\n }\n\n get entities() : readonly GameEntity[]\n {\n return [ ...this.#entries ];\n }\n\n attach() : void\n {\n this.#bus.attach();\n }\n\n detach() : void\n {\n this.#bus.detach();\n }\n\n spawn<Name extends keyof Registry & string>(\n name : Name,\n state ?: SpawnState<Registry[Name]>,\n id ?: string\n ) : ComposedOf<Registry[Name]>\n {\n return this.spawnByName(name, state as StateBag | undefined, id) as ComposedOf<Registry[Name]>;\n }\n\n /**\n * The same spawn for a name known only at run time, a marker's or a console's: the registry checks the name here\n * rather than the compiler at the call (design/simulation.md#spawn-and-despawn).\n */\n spawnByName(name : string, state ?: StateBag, id ?: string) : 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.#byID.has(entityID))\n {\n throw new Error(`The simulation already holds an entity under the ID \"${ entityID }\".`);\n }\n\n // An ID is unique for the simulation's whole life, so one already despawned never spawns again\n // (design/entities.md#ids).\n if(this.#retiredIDs.has(entityID))\n {\n throw new Error(`The simulation already retired the ID \"${ entityID }\"; an ID is never reused.`);\n }\n\n const entity = entityClass.create(entityID, this.#context(), state ?? {}, name);\n\n this.#entries.push(entity);\n this.#byID.set(entityID, entity);\n\n return entity;\n }\n\n despawn(id : string) : boolean\n {\n return this.#despawn(id, new Set<string>());\n }\n\n #despawn(id : string, inFlight : Set<string>) : boolean\n {\n const entity = this.#byID.get(id);\n\n if(entity === undefined)\n {\n return false;\n }\n\n inFlight.add(id);\n\n // Children first, each found by the parent ID in its state (design/simulation.md#spawn-and-despawn). A\n // parent ID is a state field, and writing state notifies nobody, so the children are found by looking rather\n // than kept in an index that a write would leave stale. A parent ID cycle would otherwise recurse without\n // end; an ID already in flight is skipped instead, so a cycle despawns each entity once.\n for(const child of this.#childrenOf(id))\n {\n if(!inFlight.has(child.id))\n {\n this.#despawn(child.id, inFlight);\n }\n }\n\n entity.destroy();\n\n this.#entries.splice(this.#entries.indexOf(entity), 1);\n this.#byID.delete(id);\n this.#retiredIDs.add(id);\n\n return true;\n }\n\n getEntity(id : string) : GameEntity | undefined\n {\n return this.#byID.get(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(GameSimulation.#entityState(entity).tags.includes(tag))\n {\n found.push(entity);\n }\n }\n\n return found;\n }\n\n getEntitiesOfClass<Type extends GameEntityType>(entityClass : Type) : readonly ComposedOf<Type>[]\n {\n const found : ComposedOf<Type>[] = [];\n\n for(const entity of this.#entries)\n {\n if(entity instanceof (entityClass as unknown as abstract new () => GameEntity))\n {\n found.push(entity as ComposedOf<Type>);\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({ fromAccumulator: true });\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 runFixedSteps(count : number) : void\n {\n if(!Number.isInteger(count) || count < 0)\n {\n throw new RangeError(`runFixedSteps requires a whole number of steps, 0 or more, got ${ count }.`);\n }\n\n for(let step = 0; step < count; step += 1)\n {\n this.#runFixedStep({ fromAccumulator: false });\n }\n }\n\n get random() : GameRandom\n {\n return this.#random;\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 {\n id: this.#id,\n tick: this.#tick,\n idCounter: this.#idCounter,\n randomState: this.#random.state,\n entities,\n };\n }\n\n buildFromSnapshot(snapshot : GameSimulationSnapshot) : GameSimulation<Registry, Context>\n {\n // A snapshot read back from JSON is typed only by trust: one written before snapshots carried the generator\n // has none to restore, and a damaged one may carry a state no generator takes.\n if(!isRandomState(snapshot.randomState))\n {\n throw new Error(\n `The snapshot of \"${ snapshot.id }\" carries no randomState a generator takes, so it cannot be restored.`\n );\n }\n\n const restored = this.buildReplacementSimulation({\n id: snapshot.id,\n fixedStepDuration: this.#fixedStepDuration,\n entityClasses: this.#entityClasses,\n allocator: this.#allocator,\n seed: this.#seed,\n bus: this.#engineBus,\n });\n\n // Built on this simulation's seed, the replacement still rolls what the simulation that took the snapshot\n // would have rolled next (design/simulation.md#the-simulations-generator).\n restored.#random.state = snapshot.randomState;\n\n restored.#load(snapshot);\n\n return restored;\n }\n\n protected buildReplacementSimulation(\n options : GameSimulationOptions<Registry>\n ) : GameSimulation<Registry, Context>\n {\n return new GameSimulation<Registry, Context>(options as GameSimulationOptions<Registry> & {\n entityClasses : ValidEntityRegistry<Registry>;\n });\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(\n row.id,\n this.#context(),\n row.state as GameEntityState & StateBag,\n row.name\n );\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.#byID = new Map();\n\n for(const entity of entries)\n {\n this.#byID.set(entity.id, entity);\n }\n\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 tolerance three calls of a third of a step run no step at all.\n #owesAFixedStep() : boolean\n {\n const tolerance = fixedStepTolerance * this.#fixedStepDuration;\n\n return this.#accumulator + tolerance >= this.#fixedStepDuration;\n }\n\n /**\n * Advances whatever else this simulation carries, once per fixed step, after every entity has updated\n * and before the bus drains.\n *\n * `sage-core` carries nothing and does nothing here. A simulation holding a physics engine steps it\n * from this, so what a behavior told a body during an update takes effect in the step it was told and\n * what physics reports drains in the step it happened (design/simulation.md#the-fixed-step). It is\n * synchronous because a fixed step is, and a throw from it leaves the simulation dead exactly as a\n * throw from an entity's update does (design/simulation.md#failure).\n *\n * @param duration - The fixed step's own duration, never a frame's elapsed time.\n */\n protected advanceFixedStep(duration : number) : void\n {\n void duration;\n }\n\n // A step `runStepsForElapsedTime` runs comes out of the accumulator; one `runFixedSteps` runs leaves the\n // accumulator as it was (design/simulation.md#the-fixed-step).\n #runFixedStep(options : { fromAccumulator : boolean }) : void\n {\n // An entity already despawned by an earlier one's update this step never starts its own; an entity that\n // despawns itself partway through its own update stops there too, in GameEntity.update\n // (design/entities.md#destruction).\n for(const entity of [ ...this.#entries ])\n {\n if(this.#byID.get(entity.id) === entity)\n {\n entity.update(this.#fixedStepDuration);\n }\n }\n\n this.advanceFixedStep(this.#fixedStepDuration);\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 if(options.fromAccumulator)\n {\n this.#accumulator -= this.#fixedStepDuration;\n\n if(Math.abs(this.#accumulator) <= fixedStepTolerance * this.#fixedStepDuration)\n {\n this.#accumulator = 0;\n }\n }\n\n this.#tick += 1;\n }\n }\n\n #childrenOf(parentID : string) : GameEntity[]\n {\n const children : GameEntity[] = [];\n\n for(const entity of this.#entries)\n {\n if((entity.state as StateBag)['parentID'] === parentID)\n {\n children.push(entity);\n }\n }\n\n return children;\n }\n\n #mintID() : string\n {\n if(this.#allocator !== undefined)\n {\n return this.#allocator();\n }\n\n this.#idCounter += 1;\n\n while(this.#idIsSpent(`${ this.#id }-${ this.#idCounter }`))\n {\n this.#idCounter += 1;\n }\n\n return `${ this.#id }-${ this.#idCounter }`;\n }\n\n // A despawned ID is spent and never minted again (design/entities.md#ids).\n #idIsSpent(candidate : string) : boolean\n {\n return this.#byID.has(candidate) || this.#retiredIDs.has(candidate);\n }\n\n // This simulation is the context its entities are created with. The cast is unchecked: TypeScript accepts a\n // subclass naming a narrower Context whether or not the subclass provides it; only a subclass that also writes\n // its own `implements Context` is checked against the shape it named.\n #context() : Context\n {\n return this as unknown as Context;\n }\n\n #entityClass(name : string) : GameEntityClass<Context>\n {\n const entityClass = this.#entityClasses[name];\n\n if(entityClass === undefined)\n {\n throw new Error(`The entity registry holds no class named \"${ name }\".`);\n }\n\n return entityClass;\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\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sage-core",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "dist/sage-core.js",
6
6
  "module": "dist/sage-core.js",