@its-not-rocket-science/ananke 0.1.69 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/README.md +150 -432
  3. package/STABLE_API.md +113 -490
  4. package/conformance/README.md +46 -44
  5. package/conformance/lockstep-sequence.json +395 -395
  6. package/conformance/phase-order.json +21 -0
  7. package/conformance/replay-parity.json +65 -65
  8. package/conformance/state-hash-regression.json +39 -0
  9. package/conformance/state-hash.json +23 -22
  10. package/dist/src/conformance.d.ts +8 -2
  11. package/dist/src/content/index.d.ts +3 -0
  12. package/dist/src/content/index.js +2 -0
  13. package/dist/src/content/injector.d.ts +19 -0
  14. package/dist/src/content/injector.js +61 -0
  15. package/dist/src/content/loader.d.ts +3 -0
  16. package/dist/src/content/loader.js +170 -0
  17. package/dist/src/content/types.d.ts +56 -0
  18. package/dist/src/content/types.js +1 -0
  19. package/dist/src/content/validator.d.ts +6 -0
  20. package/dist/src/content/validator.js +14 -0
  21. package/dist/src/content-pack.d.ts +3 -2
  22. package/dist/src/content-pack.js +205 -10
  23. package/dist/src/data-governance.d.ts +73 -0
  24. package/dist/src/data-governance.js +188 -0
  25. package/dist/src/debug.d.ts +6 -0
  26. package/dist/src/debug.js +9 -0
  27. package/dist/src/determinism.d.ts +12 -0
  28. package/dist/src/determinism.js +93 -0
  29. package/dist/src/dialogue.d.ts +41 -0
  30. package/dist/src/dialogue.js +113 -2
  31. package/dist/src/economy.d.ts +97 -3
  32. package/dist/src/economy.js +191 -37
  33. package/dist/src/history/autosave.d.ts +19 -0
  34. package/dist/src/history/autosave.js +98 -0
  35. package/dist/src/history/timetravel.d.ts +28 -0
  36. package/dist/src/history/timetravel.js +61 -0
  37. package/dist/src/index.d.ts +19 -31
  38. package/dist/src/index.js +20 -38
  39. package/dist/src/model3d.js +63 -2
  40. package/dist/src/narrative/combat-logger.d.ts +25 -0
  41. package/dist/src/narrative/combat-logger.js +97 -0
  42. package/dist/src/narrative/plausibility.d.ts +30 -0
  43. package/dist/src/narrative/plausibility.js +70 -0
  44. package/dist/src/narrative-layer.d.ts +2 -0
  45. package/dist/src/narrative-layer.js +2 -0
  46. package/dist/src/navigation/causal-chain.d.ts +26 -0
  47. package/dist/src/navigation/causal-chain.js +74 -0
  48. package/dist/src/netcode.d.ts +1 -1
  49. package/dist/src/netcode.js +1 -1
  50. package/dist/src/performance/adaptive-tick.d.ts +23 -0
  51. package/dist/src/performance/adaptive-tick.js +50 -0
  52. package/dist/src/plugins/loader.d.ts +23 -0
  53. package/dist/src/plugins/loader.js +86 -0
  54. package/dist/src/plugins/registry.d.ts +11 -0
  55. package/dist/src/plugins/registry.js +28 -0
  56. package/dist/src/plugins/types.d.ts +31 -0
  57. package/dist/src/plugins/types.js +7 -0
  58. package/dist/src/replay.js +5 -2
  59. package/dist/src/scenario.js +2 -1
  60. package/dist/src/serialization/binary.d.ts +5 -0
  61. package/dist/src/serialization/binary.js +193 -0
  62. package/dist/src/session.d.ts +115 -0
  63. package/dist/src/session.js +228 -0
  64. package/dist/src/sim/ai/decide.js +1 -1
  65. package/dist/src/sim/ai/personality.js +2 -2
  66. package/dist/src/sim/bodyplan.d.ts +30 -0
  67. package/dist/src/sim/context.d.ts +2 -0
  68. package/dist/src/sim/kernel.js +80 -1294
  69. package/dist/src/sim/normalization.d.ts +12 -0
  70. package/dist/src/sim/normalization.js +92 -0
  71. package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
  72. package/dist/src/sim/resolvers/attack-resolver.js +310 -0
  73. package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
  74. package/dist/src/sim/resolvers/capability-resolver.js +270 -0
  75. package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
  76. package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
  77. package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
  78. package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
  79. package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
  80. package/dist/src/sim/resolvers/treat-resolver.js +77 -0
  81. package/dist/src/sim/step/apply/intents.d.ts +6 -0
  82. package/dist/src/sim/step/apply/intents.js +83 -0
  83. package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
  84. package/dist/src/sim/step/kernel-pipeline.js +15 -0
  85. package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
  86. package/dist/src/sim/step/phases/capability-phase.js +65 -0
  87. package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
  88. package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
  89. package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
  90. package/dist/src/sim/step/phases/prepare-phase.js +33 -0
  91. package/dist/src/sim/step/pipeline.d.ts +2 -0
  92. package/dist/src/sim/step/pipeline.js +15 -0
  93. package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
  94. package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
  95. package/dist/src/sim/step/world-phases.d.ts +7 -0
  96. package/dist/src/sim/step/world-phases.js +15 -0
  97. package/dist/src/sim/step/world-step-context.d.ts +23 -0
  98. package/dist/src/sim/step/world-step-context.js +1 -0
  99. package/dist/src/sim/systemic-toxicology.js +1 -1
  100. package/dist/src/sim/team.js +2 -2
  101. package/dist/src/sim/testing.js +4 -3
  102. package/dist/src/sim/toxicology.d.ts +1 -1
  103. package/dist/src/sim/toxicology.js +1 -1
  104. package/dist/src/sim/world.d.ts +17 -24
  105. package/dist/src/snapshot.js +3 -6
  106. package/dist/src/tier2.d.ts +19 -0
  107. package/dist/src/tier2.js +21 -0
  108. package/dist/src/tier3.d.ts +10 -0
  109. package/dist/src/tier3.js +12 -0
  110. package/dist/src/version.d.ts +6 -0
  111. package/dist/src/version.js +8 -0
  112. package/dist/src/wasm/bridge.d.ts +14 -0
  113. package/dist/src/wasm/bridge.js +84 -0
  114. package/dist/src/wasm-kernel.d.ts +2 -0
  115. package/dist/src/wasm-kernel.js +1 -0
  116. package/dist/src/world-evolution-backend/engine.d.ts +4 -0
  117. package/dist/src/world-evolution-backend/engine.js +347 -0
  118. package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
  119. package/dist/src/world-evolution-backend/host-schema.js +375 -0
  120. package/dist/src/world-evolution-backend/index.d.ts +1 -0
  121. package/dist/src/world-evolution-backend/index.js +1 -0
  122. package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
  123. package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
  124. package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
  125. package/dist/src/world-evolution-backend/profiles.js +136 -0
  126. package/dist/src/world-evolution-backend/public.d.ts +11 -0
  127. package/dist/src/world-evolution-backend/public.js +7 -0
  128. package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
  129. package/dist/src/world-evolution-backend/reproducibility.js +35 -0
  130. package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
  131. package/dist/src/world-evolution-backend/timeline.js +337 -0
  132. package/dist/src/world-evolution-backend/types.d.ts +138 -0
  133. package/dist/src/world-evolution-backend/types.js +1 -0
  134. package/dist/src/world-evolution-host-backend.d.ts +51 -0
  135. package/dist/src/world-evolution-host-backend.js +97 -0
  136. package/dist/src/world-evolution.d.ts +201 -0
  137. package/dist/src/world-evolution.js +543 -0
  138. package/dist/src/world-factory.js +2 -1
  139. package/dist/tools/pack-cli.js +55 -2
  140. package/docs/bridge-contract.md +140 -332
  141. package/docs/completion-evidence-dashboard.md +63 -0
  142. package/docs/host-contract.md +86 -310
  143. package/docs/host-world-evolution-schema.md +155 -0
  144. package/docs/integration-primer.md +29 -22
  145. package/docs/migration-monolith-to-modular.md +102 -120
  146. package/docs/package-architecture.md +116 -323
  147. package/docs/plugins/README.md +42 -0
  148. package/docs/plugins/hooks.md +22 -0
  149. package/docs/plugins/publishing.md +29 -0
  150. package/docs/plugins/security-model.md +273 -0
  151. package/docs/project-overview.md +30 -30
  152. package/docs/release-dashboard.md +47 -0
  153. package/docs/session-api.md +127 -0
  154. package/docs/stable-api-manifest.json +52 -0
  155. package/docs/trust-dashboard.md +37 -0
  156. package/docs/versioning.md +256 -245
  157. package/docs/wire-protocol.md +140 -209
  158. package/docs/world-evolution-backend.md +200 -0
  159. package/docs/world-evolution-engine.md +150 -0
  160. package/docs/world-evolution-host-backend.md +70 -0
  161. package/docs/world-evolution-orchestration.md +248 -0
  162. package/package.json +125 -6
  163. package/schema/content-pack.schema.json +85 -0
  164. package/schema/plugin.schema.json +57 -0
  165. package/schema/world-evolution-input.schema.json +196 -0
@@ -0,0 +1,31 @@
1
+ export declare const PLUGIN_PERMISSIONS: readonly ["read:worldState", "write:worldState", "read:events", "write:telemetry", "write:artifacts"];
2
+ export type PluginPermission = (typeof PLUGIN_PERMISSIONS)[number];
3
+ export interface PluginManifest {
4
+ id: string;
5
+ version: string;
6
+ hooks: string[];
7
+ dependencies: Record<string, string>;
8
+ permissions: PluginPermission[];
9
+ }
10
+ export interface PluginHookContext {
11
+ worldState: Record<string, unknown>;
12
+ events?: unknown[];
13
+ [key: string]: unknown;
14
+ }
15
+ export type PluginHooks = Record<string, (context: PluginHookContext) => void | Promise<void>>;
16
+ export interface PluginRuntimeApi {
17
+ readonly manifest: PluginManifest;
18
+ hasPermission(permission: PluginPermission): boolean;
19
+ readWorldState<T>(value: T): Readonly<T>;
20
+ mutateWorld<T>(worldState: T, mutator: (draft: T) => void): void;
21
+ emitTelemetry(metric: string, payload: unknown): void;
22
+ writeArtifact(path: string, contents: string): void;
23
+ }
24
+ export interface PluginModule {
25
+ setup(api: PluginRuntimeApi): PluginHooks;
26
+ }
27
+ export interface LoadedPlugin {
28
+ manifest: PluginManifest;
29
+ hooks: PluginHooks;
30
+ runHook(hook: string, context: PluginHookContext): Promise<void>;
31
+ }
@@ -0,0 +1,7 @@
1
+ export const PLUGIN_PERMISSIONS = [
2
+ "read:worldState",
3
+ "write:worldState",
4
+ "read:events",
5
+ "write:telemetry",
6
+ "write:artifacts",
7
+ ];
@@ -6,6 +6,7 @@
6
6
  // Recording: snapshot the initial WorldState, then log commands per tick.
7
7
  // Replaying: restore the snapshot, apply logged commands in order.
8
8
  import { stepWorld } from "./sim/kernel.js";
9
+ import { normalizeWorldInPlace } from "./sim/normalization.js";
9
10
  /**
10
11
  * Records commands applied each tick so the simulation can be replayed later.
11
12
  *
@@ -44,7 +45,7 @@ export class ReplayRecorder {
44
45
  * Pass `ctx.trace` to collect all replayed events for analysis.
45
46
  */
46
47
  export function replayTo(replay, targetTick, ctx) {
47
- const world = structuredClone(replay.initialState);
48
+ const world = normalizeWorldInPlace(structuredClone(replay.initialState));
48
49
  for (const frame of replay.frames) {
49
50
  if (frame.tick > targetTick)
50
51
  break;
@@ -78,5 +79,7 @@ export function serializeReplay(replay) {
78
79
  }
79
80
  /** Deserialize a JSON string produced by `serializeReplay` back into a Replay. */
80
81
  export function deserializeReplay(json) {
81
- return JSON.parse(json, mapAwareReviver);
82
+ const replay = JSON.parse(json, mapAwareReviver);
83
+ normalizeWorldInPlace(replay.initialState);
84
+ return replay;
82
85
  }
@@ -4,6 +4,7 @@
4
4
  * Provides typed AnankeScenario interface, structural validation, and
5
5
  * a loadScenario() function that converts validated JSON into a WorldState.
6
6
  */
7
+ import { normalizeWorldInPlace } from "./sim/normalization.js";
7
8
  import { createWorld } from "./world-factory.js";
8
9
  // ── Validation ────────────────────────────────────────────────────────────────
9
10
  /**
@@ -105,5 +106,5 @@ export function loadScenario(json) {
105
106
  spec.y_m = ent.y_m;
106
107
  return spec;
107
108
  });
108
- return createWorld(scenario.seed, specs);
109
+ return normalizeWorldInPlace(createWorld(scenario.seed, specs));
109
110
  }
@@ -0,0 +1,5 @@
1
+ import type { WorldState } from "../sim/world.js";
2
+ export declare function exportWorldState(world: WorldState): Uint8Array;
3
+ export declare function importWorldState(data: Uint8Array): WorldState;
4
+ export declare function resetSerializationContext(): void;
5
+ export declare function exportLegacyV1WorldState(world: WorldState): Uint8Array;
@@ -0,0 +1,193 @@
1
+ import { applyDiff, diffWorldState } from "../snapshot.js";
2
+ import { migrateWorld } from "../schema-migration.js";
3
+ const MAGIC = 0x414E4B57; // ANKW
4
+ const FORMAT_VERSION = 2;
5
+ const COMPRESSION_NONE = 0;
6
+ const COMPRESSION_LZ4_WASM = 1;
7
+ let lastExportedWorld;
8
+ let lastImportedWorld;
9
+ export function exportWorldState(world) {
10
+ const canDiff = lastExportedWorld !== undefined &&
11
+ lastExportedWorld.seed === world.seed &&
12
+ world.tick >= lastExportedWorld.tick;
13
+ const payload = canDiff
14
+ ? {
15
+ schemaVersion: FORMAT_VERSION,
16
+ tick: world.tick,
17
+ incremental: {
18
+ baseTick: lastExportedWorld.tick,
19
+ diff: diffWorldState(lastExportedWorld, world),
20
+ },
21
+ }
22
+ : {
23
+ schemaVersion: FORMAT_VERSION,
24
+ tick: world.tick,
25
+ full: worldToSerializable(world),
26
+ };
27
+ const payloadRaw = utf8(JSON.stringify(payload, mapAwareReplacer));
28
+ const { codec, bytes } = compress(payloadRaw);
29
+ const checksum = crc32(bytes);
30
+ const w = new BytesWriter(28 + bytes.length);
31
+ w.u32(MAGIC);
32
+ w.u32(FORMAT_VERSION);
33
+ w.u64(BigInt(Date.now()));
34
+ w.u32(checksum);
35
+ w.u8(codec);
36
+ w.u8(0); // reserved
37
+ w.u16(0); // reserved
38
+ w.u32(bytes.length);
39
+ w.bytes(bytes);
40
+ lastExportedWorld = structuredClone(world);
41
+ return w.finish();
42
+ }
43
+ export function importWorldState(data) {
44
+ const r = new BytesReader(data);
45
+ const magic = r.u32();
46
+ if (magic !== MAGIC)
47
+ throw new Error("serialization: bad magic");
48
+ const version = r.u32();
49
+ const _ts = r.u64();
50
+ const checksum = r.u32();
51
+ const codec = r.u8();
52
+ r.u8();
53
+ r.u16();
54
+ const payloadLen = r.u32();
55
+ const payloadBytes = r.bytes(payloadLen);
56
+ if (crc32(payloadBytes) !== checksum) {
57
+ throw new Error("serialization: checksum mismatch");
58
+ }
59
+ const payloadRaw = decompress(codec, payloadBytes);
60
+ const payload = JSON.parse(text(payloadRaw), mapAwareReviver);
61
+ let world;
62
+ if (version === 1) {
63
+ const legacy = payload;
64
+ const candidate = legacy.world;
65
+ const migrated = typeof candidate["_ananke_version"] === "string" ? migrateWorld(candidate) : candidate;
66
+ world = serializableToWorld(migrated);
67
+ }
68
+ else if (version === FORMAT_VERSION) {
69
+ const v2 = payload;
70
+ if (v2.full !== undefined) {
71
+ world = serializableToWorld(v2.full);
72
+ }
73
+ else {
74
+ if (!v2.incremental || !lastImportedWorld) {
75
+ throw new Error("serialization: incremental snapshot requires prior base world");
76
+ }
77
+ if (lastImportedWorld.tick !== v2.incremental.baseTick) {
78
+ throw new Error("serialization: incremental base tick mismatch");
79
+ }
80
+ world = applyDiff(lastImportedWorld, v2.incremental.diff);
81
+ }
82
+ }
83
+ else {
84
+ throw new Error(`serialization: unsupported version ${version}`);
85
+ }
86
+ lastImportedWorld = structuredClone(world);
87
+ return world;
88
+ }
89
+ export function resetSerializationContext() {
90
+ lastExportedWorld = undefined;
91
+ lastImportedWorld = undefined;
92
+ }
93
+ export function exportLegacyV1WorldState(world) {
94
+ const payloadRaw = utf8(JSON.stringify({ world: worldToSerializable(world) }, mapAwareReplacer));
95
+ const checksum = crc32(payloadRaw);
96
+ const w = new BytesWriter(28 + payloadRaw.length);
97
+ w.u32(MAGIC);
98
+ w.u32(1);
99
+ w.u64(BigInt(Date.now()));
100
+ w.u32(checksum);
101
+ w.u8(COMPRESSION_NONE);
102
+ w.u8(0);
103
+ w.u16(0);
104
+ w.u32(payloadRaw.length);
105
+ w.bytes(payloadRaw);
106
+ return w.finish();
107
+ }
108
+ function worldToSerializable(world) {
109
+ return structuredClone(world);
110
+ }
111
+ function serializableToWorld(payload) {
112
+ const cloned = structuredClone(payload);
113
+ delete cloned["_ananke_version"];
114
+ delete cloned["_schema"];
115
+ return cloned;
116
+ }
117
+ function mapAwareReplacer(_key, value) {
118
+ if (value instanceof Map)
119
+ return { __ananke_map__: true, entries: [...value.entries()] };
120
+ return value;
121
+ }
122
+ function mapAwareReviver(_key, value) {
123
+ if (value && typeof value === "object" && value.__ananke_map__ === true) {
124
+ return new Map(value.entries);
125
+ }
126
+ return value;
127
+ }
128
+ function compress(bytes) {
129
+ const maybe = globalThis.__ANANKE_LZ4_WASM__;
130
+ if (maybe)
131
+ return { codec: COMPRESSION_LZ4_WASM, bytes: maybe.compress(bytes) };
132
+ return { codec: COMPRESSION_NONE, bytes };
133
+ }
134
+ function decompress(codec, bytes) {
135
+ if (codec === COMPRESSION_NONE)
136
+ return bytes;
137
+ if (codec === COMPRESSION_LZ4_WASM) {
138
+ const maybe = globalThis.__ANANKE_LZ4_WASM__;
139
+ if (!maybe)
140
+ throw new Error("serialization: lz4 wasm codec unavailable");
141
+ return maybe.decompress(bytes);
142
+ }
143
+ throw new Error(`serialization: unknown compression codec ${codec}`);
144
+ }
145
+ function utf8(str) {
146
+ return new TextEncoder().encode(str);
147
+ }
148
+ function text(bytes) {
149
+ return new TextDecoder().decode(bytes);
150
+ }
151
+ function crc32(data) {
152
+ let c = 0xFFFFFFFF;
153
+ for (let i = 0; i < data.length; i++) {
154
+ c ^= data[i];
155
+ for (let b = 0; b < 8; b++) {
156
+ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
157
+ }
158
+ }
159
+ return (c ^ 0xFFFFFFFF) >>> 0;
160
+ }
161
+ class BytesWriter {
162
+ out;
163
+ cursor = 0;
164
+ view;
165
+ constructor(size) {
166
+ this.out = new Uint8Array(size);
167
+ this.view = new DataView(this.out.buffer);
168
+ }
169
+ u8(v) { this.view.setUint8(this.cursor, v); this.cursor += 1; }
170
+ u16(v) { this.view.setUint16(this.cursor, v, true); this.cursor += 2; }
171
+ u32(v) { this.view.setUint32(this.cursor, v >>> 0, true); this.cursor += 4; }
172
+ u64(v) { this.view.setBigUint64(this.cursor, v, true); this.cursor += 8; }
173
+ bytes(v) { this.out.set(v, this.cursor); this.cursor += v.length; }
174
+ finish() { return this.out.slice(0, this.cursor); }
175
+ }
176
+ class BytesReader {
177
+ data;
178
+ cursor = 0;
179
+ view;
180
+ constructor(data) {
181
+ this.data = data;
182
+ this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
183
+ }
184
+ u8() { const v = this.view.getUint8(this.cursor); this.cursor += 1; return v; }
185
+ u16() { const v = this.view.getUint16(this.cursor, true); this.cursor += 2; return v; }
186
+ u32() { const v = this.view.getUint32(this.cursor, true); this.cursor += 4; return v; }
187
+ u64() { const v = this.view.getBigUint64(this.cursor, true); this.cursor += 8; return v; }
188
+ bytes(n) {
189
+ const start = this.cursor;
190
+ this.cursor += n;
191
+ return this.data.slice(start, start + n);
192
+ }
193
+ }
@@ -0,0 +1,115 @@
1
+ import { type EntitySpec } from "./world-factory.js";
2
+ import type { Command } from "./sim/commands.js";
3
+ import type { KernelContext } from "./sim/context.js";
4
+ import type { WorldState } from "./sim/world.js";
5
+ import { getEvolutionSummary, type EvolutionRequest, type EvolutionRuleset, type EvolutionSession } from "./world-evolution.js";
6
+ import { type WorldEvolutionRulesetId, type WorldEvolutionSnapshot } from "./world-evolution-backend/index.js";
7
+ import { type AnankePackManifest, type LoadPackResult, type PackValidationError } from "./content-pack.js";
8
+ import { type ReplayFrame } from "./replay.js";
9
+ /** Tier 2 / experimental host session mode. */
10
+ export type SessionMode = "tactical" | "world_evolution";
11
+ export interface TacticalSessionConfig {
12
+ mode: "tactical";
13
+ /** Explicit tactical world snapshot. */
14
+ worldState?: WorldState;
15
+ /** Scenario JSON loaded via validateScenario + loadScenario. */
16
+ scenarioJson?: unknown;
17
+ /** Optional convenience bootstrap via createWorld(seed, entities). */
18
+ worldSeed?: number;
19
+ entities?: EntitySpec[];
20
+ /** Enable in-session replay recording. */
21
+ enableReplay?: boolean;
22
+ id?: string;
23
+ }
24
+ export interface WorldEvolutionSessionConfig {
25
+ mode: "world_evolution";
26
+ canonicalSnapshot: WorldEvolutionSnapshot;
27
+ rulesetId?: WorldEvolutionRulesetId;
28
+ ruleset?: EvolutionRuleset;
29
+ checkpointInterval?: number;
30
+ includeDeltas?: boolean;
31
+ label?: string;
32
+ id?: string;
33
+ }
34
+ export type SessionConfig = TacticalSessionConfig | WorldEvolutionSessionConfig;
35
+ interface TacticalSessionState {
36
+ world: WorldState;
37
+ replay?: MutableReplay;
38
+ }
39
+ interface WorldEvolutionSessionState {
40
+ evolution: EvolutionSession;
41
+ }
42
+ interface MutableReplay {
43
+ initialState: WorldState;
44
+ frames: ReplayFrame[];
45
+ }
46
+ export type SessionHandle = {
47
+ mode: "tactical";
48
+ id: string;
49
+ createdAt: number;
50
+ state: TacticalSessionState;
51
+ } | {
52
+ mode: "world_evolution";
53
+ id: string;
54
+ createdAt: number;
55
+ state: WorldEvolutionSessionState;
56
+ };
57
+ export interface RunSessionRequest {
58
+ /**
59
+ * Number of simulation steps to run. Defaults to 1 when omitted.
60
+ */
61
+ steps?: number;
62
+ /** Tactical command frames, one frame per requested step. */
63
+ tacticalCommandFrames?: ReadonlyArray<ReadonlyArray<readonly [entityId: number, cmds: ReadonlyArray<Command>]>>;
64
+ /** Alias for tacticalCommandFrames. Prefer this for new host integrations. */
65
+ commandFrames?: ReadonlyArray<ReadonlyArray<readonly [entityId: number, cmds: ReadonlyArray<Command>]>>;
66
+ tacticalContext?: KernelContext;
67
+ /** Alias for tacticalContext. Prefer this for new host integrations. */
68
+ context?: KernelContext;
69
+ evolution?: Omit<EvolutionRequest, "steps">;
70
+ /** Alias for evolution. Prefer this for new host integrations. */
71
+ worldEvolution?: Omit<EvolutionRequest, "steps">;
72
+ }
73
+ export interface RunSessionResult {
74
+ mode: SessionMode;
75
+ executedSteps: number;
76
+ summary: SessionSummary;
77
+ }
78
+ export type SessionSummary = {
79
+ mode: "tactical";
80
+ id: string;
81
+ tick: number;
82
+ entityCount: number;
83
+ hasReplay: boolean;
84
+ } | {
85
+ mode: "world_evolution";
86
+ id: string;
87
+ summary: ReturnType<typeof getEvolutionSummary>;
88
+ };
89
+ export interface ForkSessionRequest {
90
+ id?: string;
91
+ label?: string;
92
+ rulesetId?: WorldEvolutionRulesetId;
93
+ ruleset?: EvolutionRuleset;
94
+ seed?: number;
95
+ }
96
+ export interface LoadSessionPackResult {
97
+ pack: LoadPackResult;
98
+ validationErrors: PackValidationError[];
99
+ scenarioJson?: unknown;
100
+ worldState?: WorldState;
101
+ }
102
+ export interface LoadSessionPackRequest {
103
+ manifest: AnankePackManifest;
104
+ scenarioId?: string;
105
+ instantiateScenario?: boolean;
106
+ }
107
+ export declare function createSession(config: SessionConfig): SessionHandle;
108
+ export declare function runSession(session: SessionHandle, request: RunSessionRequest): RunSessionResult;
109
+ export declare function stepSession(session: SessionHandle, request?: Omit<RunSessionRequest, "steps">): RunSessionResult;
110
+ export declare function getSessionSummary(session: SessionHandle): SessionSummary;
111
+ export declare function forkSession(session: SessionHandle, request?: ForkSessionRequest): SessionHandle;
112
+ export declare function loadSessionPack(input: LoadSessionPackRequest | AnankePackManifest): LoadSessionPackResult;
113
+ export declare function serializeSession(session: SessionHandle): string;
114
+ export declare function deserializeSession(json: string): SessionHandle;
115
+ export {};
@@ -0,0 +1,228 @@
1
+ import { ANANKE_ENGINE_VERSION } from "./version.js";
2
+ import { createWorld } from "./world-factory.js";
3
+ import { loadScenario, validateScenario } from "./scenario.js";
4
+ import { stepWorld } from "./sim/kernel.js";
5
+ import { q } from "./units.js";
6
+ import { createEvolutionBranch, createEvolutionSession, deserializeEvolutionCheckpoint, forkEvolutionBranch, getEvolutionSummary, resumeEvolutionSessionFromCheckpoint, runEvolution, serializeEvolutionIntermediateState, stepEvolution, } from "./world-evolution.js";
7
+ import { getPackScenario, instantiatePackScenario, loadPack, validatePack, } from "./content-pack.js";
8
+ import { deserializeReplay, serializeReplay } from "./replay.js";
9
+ const SESSION_SCHEMA_VERSION = "ananke.session.v1";
10
+ export function createSession(config) {
11
+ if (config.mode === "tactical") {
12
+ const world = createTacticalWorld(config);
13
+ const replay = config.enableReplay
14
+ ? { initialState: structuredClone(world), frames: [] }
15
+ : undefined;
16
+ return {
17
+ mode: "tactical",
18
+ id: config.id ?? `session-tactical-${world.seed}-${world.tick}`,
19
+ createdAt: world.tick,
20
+ state: replay ? { world, replay } : { world },
21
+ };
22
+ }
23
+ const evolution = createEvolutionSession({
24
+ seed: config.canonicalSnapshot.worldSeed,
25
+ canonicalSnapshot: config.canonicalSnapshot,
26
+ ...(config.ruleset != null ? { ruleset: config.ruleset } : {}),
27
+ ...(config.rulesetId != null ? { rulesetId: config.rulesetId } : {}),
28
+ ...(config.checkpointInterval != null ? { checkpointInterval: config.checkpointInterval } : {}),
29
+ ...(config.includeDeltas != null ? { includeDeltas: config.includeDeltas } : {}),
30
+ ...(config.label != null ? { label: config.label } : {}),
31
+ });
32
+ return {
33
+ mode: "world_evolution",
34
+ id: config.id ?? evolution.sessionId,
35
+ createdAt: config.canonicalSnapshot.tick,
36
+ state: { evolution },
37
+ };
38
+ }
39
+ export function runSession(session, request) {
40
+ const requestedSteps = Math.max(0, Math.floor(request.steps ?? 1));
41
+ const commandFrames = request.commandFrames ?? request.tacticalCommandFrames;
42
+ const tacticalContext = request.context ?? request.tacticalContext;
43
+ const evolutionRequest = request.worldEvolution ?? request.evolution;
44
+ if (session.mode === "tactical") {
45
+ for (let i = 0; i < requestedSteps; i += 1) {
46
+ const frame = commandFrames?.[i] ?? [];
47
+ if (session.state.replay) {
48
+ session.state.replay.frames.push({
49
+ tick: session.state.world.tick,
50
+ commands: frame.map(([entityId, cmds]) => [entityId, [...cmds]]),
51
+ });
52
+ }
53
+ stepWorld(session.state.world, toCommandMap(frame), tacticalContext ?? { tractionCoeff: q(0.85) });
54
+ }
55
+ return { mode: "tactical", executedSteps: requestedSteps, summary: getSessionSummary(session) };
56
+ }
57
+ runEvolution(session.state.evolution, {
58
+ steps: requestedSteps,
59
+ ...(evolutionRequest?.checkpointInterval != null ? { checkpointInterval: evolutionRequest.checkpointInterval } : {}),
60
+ ...(evolutionRequest?.includeDeltas != null ? { includeDeltas: evolutionRequest.includeDeltas } : {}),
61
+ ...(evolutionRequest?.includeCheckpointDiffs != null ? { includeCheckpointDiffs: evolutionRequest.includeCheckpointDiffs } : {}),
62
+ });
63
+ return { mode: "world_evolution", executedSteps: requestedSteps, summary: getSessionSummary(session) };
64
+ }
65
+ export function stepSession(session, request = {}) {
66
+ const evolutionRequest = request.worldEvolution ?? request.evolution;
67
+ if (session.mode === "world_evolution") {
68
+ stepEvolution(session.state.evolution, evolutionRequest ?? {});
69
+ return { mode: "world_evolution", executedSteps: 1, summary: getSessionSummary(session) };
70
+ }
71
+ return runSession(session, { ...request, steps: 1 });
72
+ }
73
+ export function getSessionSummary(session) {
74
+ if (session.mode === "tactical") {
75
+ return {
76
+ mode: "tactical",
77
+ id: session.id,
78
+ tick: session.state.world.tick,
79
+ entityCount: session.state.world.entities.length,
80
+ hasReplay: session.state.replay != null,
81
+ };
82
+ }
83
+ return { mode: "world_evolution", id: session.id, summary: getEvolutionSummary(session.state.evolution) };
84
+ }
85
+ export function forkSession(session, request = {}) {
86
+ if (session.mode === "tactical") {
87
+ return {
88
+ mode: "tactical",
89
+ id: request.id ?? `${session.id}-fork-${session.state.world.tick}`,
90
+ createdAt: session.state.world.tick,
91
+ state: session.state.replay
92
+ ? {
93
+ world: structuredClone(session.state.world),
94
+ replay: structuredClone(session.state.replay),
95
+ }
96
+ : {
97
+ world: structuredClone(session.state.world),
98
+ },
99
+ };
100
+ }
101
+ const baseBranch = createEvolutionBranch({
102
+ baseSnapshot: session.state.evolution.state.currentSnapshot,
103
+ baseStep: session.state.evolution.state.totalSteps,
104
+ metadata: {
105
+ name: `${session.id}-base`,
106
+ seed: session.state.evolution.seed,
107
+ rulesetProfile: session.state.evolution.ruleset,
108
+ createdAtStep: session.state.evolution.state.totalSteps,
109
+ },
110
+ });
111
+ const branch = forkEvolutionBranch(baseBranch, {
112
+ metadata: {
113
+ name: request.label ?? `${session.id}-fork`,
114
+ ...(request.seed != null ? { seed: request.seed } : {}),
115
+ ...(request.ruleset != null ? { rulesetProfile: request.ruleset } : {}),
116
+ ...(request.rulesetId != null ? { rulesetId: request.rulesetId } : {}),
117
+ },
118
+ });
119
+ const evolution = createEvolutionSession({
120
+ seed: branch.branchMetadata.seed,
121
+ canonicalSnapshot: branch.derivedState.currentSnapshot,
122
+ ruleset: branch.branchMetadata.rulesetProfile,
123
+ ...(request.label != null ? { label: request.label } : {}),
124
+ });
125
+ return {
126
+ mode: "world_evolution",
127
+ id: request.id ?? `${session.id}-fork-${session.state.evolution.state.totalSteps}`,
128
+ createdAt: evolution.state.currentSnapshot.tick,
129
+ state: { evolution },
130
+ };
131
+ }
132
+ export function loadSessionPack(input) {
133
+ const request = isPackManifest(input) ? { manifest: input } : input;
134
+ const validationErrors = validatePack(request.manifest);
135
+ const pack = loadPack(request.manifest);
136
+ const out = { pack, validationErrors };
137
+ if (request.scenarioId) {
138
+ out.scenarioJson = getPackScenario(pack.packId, request.scenarioId);
139
+ if (request.instantiateScenario && out.scenarioJson != null) {
140
+ out.worldState = instantiatePackScenario(pack.packId, request.scenarioId);
141
+ }
142
+ }
143
+ return out;
144
+ }
145
+ export function serializeSession(session) {
146
+ const envelope = session.mode === "tactical"
147
+ ? {
148
+ schemaVersion: SESSION_SCHEMA_VERSION,
149
+ engineVersion: ANANKE_ENGINE_VERSION,
150
+ mode: "tactical",
151
+ payload: {
152
+ id: session.id,
153
+ createdAt: session.createdAt,
154
+ worldReplayJson: serializeReplay({ initialState: session.state.world, frames: [] }),
155
+ ...(session.state.replay ? { replayJson: serializeReplay(session.state.replay) } : {}),
156
+ },
157
+ }
158
+ : {
159
+ schemaVersion: SESSION_SCHEMA_VERSION,
160
+ engineVersion: ANANKE_ENGINE_VERSION,
161
+ mode: "world_evolution",
162
+ payload: {
163
+ id: session.id,
164
+ createdAt: session.createdAt,
165
+ checkpointJson: serializeEvolutionIntermediateState(session.state.evolution),
166
+ },
167
+ };
168
+ return JSON.stringify(envelope);
169
+ }
170
+ export function deserializeSession(json) {
171
+ const envelope = JSON.parse(json);
172
+ if (envelope.schemaVersion !== SESSION_SCHEMA_VERSION) {
173
+ throw new Error(`deserializeSession: unsupported schemaVersion '${String(envelope.schemaVersion)}'`);
174
+ }
175
+ if (envelope.mode === "tactical") {
176
+ const payload = envelope.payload;
177
+ const worldReplay = deserializeReplay(payload.worldReplayJson);
178
+ const replay = payload.replayJson ? deserializeReplay(payload.replayJson) : undefined;
179
+ return {
180
+ mode: "tactical",
181
+ id: payload.id,
182
+ createdAt: payload.createdAt,
183
+ state: replay
184
+ ? {
185
+ world: worldReplay.initialState,
186
+ replay: {
187
+ initialState: structuredClone(replay.initialState),
188
+ frames: replay.frames.map((frame) => ({
189
+ tick: frame.tick,
190
+ commands: frame.commands.map(([entityId, cmds]) => [entityId, [...cmds]]),
191
+ })),
192
+ },
193
+ }
194
+ : {
195
+ world: worldReplay.initialState,
196
+ },
197
+ };
198
+ }
199
+ const payload = envelope.payload;
200
+ const evolution = resumeEvolutionSessionFromCheckpoint(deserializeEvolutionCheckpoint(payload.checkpointJson));
201
+ return {
202
+ mode: "world_evolution",
203
+ id: payload.id,
204
+ createdAt: payload.createdAt,
205
+ state: { evolution },
206
+ };
207
+ }
208
+ function createTacticalWorld(config) {
209
+ if (config.worldState != null)
210
+ return structuredClone(config.worldState);
211
+ if (config.scenarioJson != null) {
212
+ const validation = validateScenario(config.scenarioJson);
213
+ if (validation.length > 0) {
214
+ throw new Error(`createSession(tactical): invalid scenario JSON:\n${validation.map((e) => `- ${e}`).join("\n")}`);
215
+ }
216
+ return loadScenario(config.scenarioJson);
217
+ }
218
+ if (config.worldSeed != null && config.entities != null) {
219
+ return createWorld(config.worldSeed, config.entities);
220
+ }
221
+ throw new Error("createSession(tactical): provide worldState, scenarioJson, or worldSeed+entities");
222
+ }
223
+ function toCommandMap(frame) {
224
+ return new Map(frame.map(([entityId, cmds]) => [entityId, [...cmds]]));
225
+ }
226
+ function isPackManifest(input) {
227
+ return !("manifest" in input);
228
+ }
@@ -114,7 +114,7 @@ export function decideCommandsForEntity(world, index, spatial, self, policy, env
114
114
  // Self-defence override: if self has taken damage (shock > 0 or fluid loss > 0),
115
115
  // faction check is bypassed (attacker is fought back regardless of standing).
116
116
  if (target && self.faction) {
117
- const factionRegistry = (world).__factionRegistry;
117
+ const factionRegistry = world.runtimeState?.factionRegistry;
118
118
  if (factionRegistry) {
119
119
  const standing = effectiveStanding(factionRegistry, self, target);
120
120
  const selfDefence = self.injury.shock > 0 || self.injury.fluidLoss > 0;
@@ -88,7 +88,7 @@ export function derivePersonalityFromCognition(attrs) {
88
88
  export function computeEffectiveLoyalty(self, world) {
89
89
  const personality = self.personality;
90
90
  const baseLoyalty = personality?.loyalty ?? q(0.50);
91
- const partyRegistry = world.__partyRegistry;
91
+ const partyRegistry = world.runtimeState?.partyRegistry;
92
92
  if (!partyRegistry)
93
93
  return baseLoyalty;
94
94
  const partyId = self.party;
@@ -100,7 +100,7 @@ export function computeEffectiveLoyalty(self, world) {
100
100
  const leaderId = party.leaderId;
101
101
  if (leaderId === self.id)
102
102
  return baseLoyalty; // leader is self
103
- const relationshipGraph = world.__relationshipGraph;
103
+ const relationshipGraph = world.runtimeState?.relationshipGraph;
104
104
  if (!relationshipGraph)
105
105
  return baseLoyalty;
106
106
  const companionLoyalty = computeCompanionLoyalty(self, leaderId, relationshipGraph);