@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,543 @@
1
+ import { WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION, createWorldEvolutionSnapshot, resolveWorldEvolutionProfile, runWorldEvolution, } from "./world-evolution-backend/index.js";
2
+ import { ANANKE_ENGINE_VERSION } from "./version.js";
3
+ import { hashString } from "./sim/seeds.js";
4
+ export const WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION = "ananke.world-evolution-orchestration.v1";
5
+ export const WORLD_EVOLUTION_ENGINE_VERSION = ANANKE_ENGINE_VERSION;
6
+ export const WORLD_EVOLUTION_CHECKPOINT_SCHEMA_VERSION = "ananke.world-evolution-checkpoint.v1";
7
+ export function createEvolutionSession(config) {
8
+ const canonicalSnapshot = createWorldEvolutionSnapshot({
9
+ ...config.canonicalSnapshot,
10
+ worldSeed: config.seed,
11
+ });
12
+ const ruleset = config.ruleset ? cloneRuleset(config.ruleset) : resolveWorldEvolutionProfile(config.rulesetId ?? "full_world_evolution");
13
+ const seed = canonicalSnapshot.worldSeed;
14
+ const state = {
15
+ totalSteps: 0,
16
+ currentSnapshot: cloneSnapshot(canonicalSnapshot),
17
+ timeline: [],
18
+ checkpoints: [],
19
+ lastMetrics: computeStaticMetrics(canonicalSnapshot),
20
+ defaultIncludeDeltas: config.includeDeltas ?? false,
21
+ };
22
+ if (config.checkpointInterval != null) {
23
+ state.defaultCheckpointInterval = config.checkpointInterval;
24
+ }
25
+ const session = {
26
+ sessionId: buildSessionId(seed, canonicalSnapshot.tick, ruleset.id),
27
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
28
+ engineVersion: WORLD_EVOLUTION_ENGINE_VERSION,
29
+ seed,
30
+ ruleset,
31
+ canonicalInitialSnapshot: cloneSnapshot(canonicalSnapshot),
32
+ state,
33
+ };
34
+ if (config.label != null) {
35
+ session.label = config.label;
36
+ }
37
+ return session;
38
+ }
39
+ export function runEvolution(session, request) {
40
+ const steps = Math.max(0, Math.floor(request.steps));
41
+ const initialSnapshot = cloneSnapshot(session.state.currentSnapshot);
42
+ const includeDeltas = request.includeDeltas ?? session.state.defaultIncludeDeltas;
43
+ const includeCheckpointDiffs = request.includeCheckpointDiffs ?? false;
44
+ const checkpointInterval = request.checkpointInterval ?? session.state.defaultCheckpointInterval;
45
+ const backendRequest = {
46
+ snapshot: initialSnapshot,
47
+ steps,
48
+ profile: session.ruleset,
49
+ includeDeltas,
50
+ ...(checkpointInterval != null ? { checkpointInterval } : {}),
51
+ };
52
+ const backendResult = runWorldEvolution(backendRequest);
53
+ const timeline = backendResult.timeline.map(toTimelineEvent);
54
+ const checkpoints = mapCheckpoints(session, backendResult.checkpoints);
55
+ session.state.totalSteps += steps;
56
+ session.state.currentSnapshot = cloneSnapshot(backendResult.finalSnapshot);
57
+ session.state.timeline.push(...timeline);
58
+ session.state.checkpoints.push(...checkpoints);
59
+ session.state.lastMetrics = { ...backendResult.metrics };
60
+ const result = {
61
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
62
+ engineVersion: session.engineVersion,
63
+ sessionId: session.sessionId,
64
+ request: {
65
+ steps,
66
+ includeDeltas,
67
+ ...(includeCheckpointDiffs ? { includeCheckpointDiffs: true } : {}),
68
+ ...(checkpointInterval != null ? { checkpointInterval } : {}),
69
+ },
70
+ ruleset: cloneRuleset(session.ruleset),
71
+ initialSnapshot,
72
+ finalSnapshot: cloneSnapshot(backendResult.finalSnapshot),
73
+ timeline,
74
+ metrics: { ...backendResult.metrics },
75
+ };
76
+ if (checkpoints.length > 0) {
77
+ result.checkpoints = checkpoints;
78
+ if (includeCheckpointDiffs) {
79
+ result.checkpointDiffs = buildEvolutionCheckpointDiffs(checkpoints);
80
+ }
81
+ }
82
+ if (backendResult.deltas) {
83
+ result.deltas = backendResult.deltas.map(cloneDelta);
84
+ }
85
+ return result;
86
+ }
87
+ export function stepEvolution(session, request = {}) {
88
+ return runEvolution(session, {
89
+ steps: request.steps ?? 1,
90
+ ...(request.checkpointInterval != null ? { checkpointInterval: request.checkpointInterval } : {}),
91
+ ...(request.includeDeltas != null ? { includeDeltas: request.includeDeltas } : {}),
92
+ });
93
+ }
94
+ export function getEvolutionSummary(session) {
95
+ const summary = {
96
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
97
+ sessionId: session.sessionId,
98
+ engineVersion: session.engineVersion,
99
+ seed: session.seed,
100
+ totalSteps: session.state.totalSteps,
101
+ ruleset: cloneRuleset(session.ruleset),
102
+ initialSnapshot: cloneSnapshot(session.canonicalInitialSnapshot),
103
+ currentSnapshot: cloneSnapshot(session.state.currentSnapshot),
104
+ metrics: { ...session.state.lastMetrics },
105
+ timelineEvents: session.state.timeline.length,
106
+ checkpointCount: session.state.checkpoints.length,
107
+ };
108
+ if (session.label != null) {
109
+ summary.label = session.label;
110
+ }
111
+ return summary;
112
+ }
113
+ export function resumeEvolutionSessionFromCheckpoint(checkpoint, options = {}) {
114
+ validateCheckpointCompatibility(checkpoint);
115
+ const session = createEvolutionSession({
116
+ seed: checkpoint.metadata.seed,
117
+ ruleset: checkpoint.metadata.rulesetProfile,
118
+ canonicalSnapshot: checkpoint.snapshot,
119
+ ...(options.checkpointInterval != null ? { checkpointInterval: options.checkpointInterval } : {}),
120
+ ...(options.includeDeltas != null ? { includeDeltas: options.includeDeltas } : {}),
121
+ ...(options.label != null ? { label: options.label } : {}),
122
+ });
123
+ session.state.totalSteps = checkpoint.step;
124
+ session.state.currentSnapshot = cloneSnapshot(checkpoint.snapshot);
125
+ session.state.checkpoints = [cloneCheckpoint(checkpoint)];
126
+ session.state.timeline = [];
127
+ session.state.lastMetrics = computeStaticMetrics(checkpoint.snapshot);
128
+ return session;
129
+ }
130
+ export function createEvolutionBranch(input) {
131
+ const baseStep = Math.max(0, Math.floor(input.baseStep ?? input.metadata.createdAtStep ?? 0));
132
+ const rulesetProfile = input.metadata.rulesetProfile
133
+ ? cloneRuleset(input.metadata.rulesetProfile)
134
+ : resolveWorldEvolutionProfile(input.metadata.rulesetId ?? "full_world_evolution");
135
+ const canonicalBaseSnapshot = createWorldEvolutionSnapshot({
136
+ ...input.baseSnapshot,
137
+ worldSeed: input.metadata.seed,
138
+ });
139
+ return {
140
+ branchId: buildBranchId(input.metadata.seed, canonicalBaseSnapshot.tick, rulesetProfile.id, baseStep),
141
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
142
+ baseSnapshotRef: {
143
+ snapshot: cloneSnapshot(canonicalBaseSnapshot),
144
+ step: baseStep,
145
+ tick: canonicalBaseSnapshot.tick,
146
+ snapshotHash: computeSnapshotHash(canonicalBaseSnapshot),
147
+ },
148
+ derivedState: {
149
+ totalSteps: 0,
150
+ currentSnapshot: cloneSnapshot(canonicalBaseSnapshot),
151
+ timeline: [],
152
+ lastMetrics: computeStaticMetrics(canonicalBaseSnapshot),
153
+ },
154
+ branchMetadata: {
155
+ name: input.metadata.name,
156
+ ...(input.metadata.description != null ? { description: input.metadata.description } : {}),
157
+ seed: input.metadata.seed,
158
+ rulesetProfile,
159
+ createdAtStep: baseStep,
160
+ ...(input.metadata.parentBranchId != null ? { parentBranchId: input.metadata.parentBranchId } : {}),
161
+ },
162
+ };
163
+ }
164
+ export function forkEvolutionBranch(branch, input) {
165
+ return createEvolutionBranch({
166
+ baseSnapshot: branch.derivedState.currentSnapshot,
167
+ baseStep: branch.baseSnapshotRef.step + branch.derivedState.totalSteps,
168
+ metadata: {
169
+ name: input.metadata.name,
170
+ ...(input.metadata.description != null ? { description: input.metadata.description } : {}),
171
+ seed: input.metadata.seed ?? branch.branchMetadata.seed,
172
+ ...(input.metadata.rulesetProfile != null ? { rulesetProfile: input.metadata.rulesetProfile } : {}),
173
+ ...(input.metadata.rulesetId != null ? { rulesetId: input.metadata.rulesetId } : {}),
174
+ createdAtStep: branch.baseSnapshotRef.step + branch.derivedState.totalSteps,
175
+ parentBranchId: branch.branchId,
176
+ },
177
+ });
178
+ }
179
+ export function runEvolutionOnBranch(branch, request) {
180
+ const runResult = runEvolutionFromSnapshot({
181
+ snapshot: branch.derivedState.currentSnapshot,
182
+ ruleset: branch.branchMetadata.rulesetProfile,
183
+ request,
184
+ });
185
+ branch.derivedState.totalSteps += runResult.request.steps;
186
+ branch.derivedState.currentSnapshot = cloneSnapshot(runResult.finalSnapshot);
187
+ branch.derivedState.timeline.push(...runResult.timeline);
188
+ branch.derivedState.lastMetrics = { ...runResult.metrics };
189
+ return runResult;
190
+ }
191
+ export function diffBranchAgainstBase(branch) {
192
+ const diff = diffSnapshots(branch.baseSnapshotRef.snapshot, branch.derivedState.currentSnapshot, branch.baseSnapshotRef.step, branch.baseSnapshotRef.tick, branch.baseSnapshotRef.snapshotHash, branch.baseSnapshotRef.step + branch.derivedState.totalSteps);
193
+ return {
194
+ branchId: branch.branchId,
195
+ baseSnapshotRef: {
196
+ step: branch.baseSnapshotRef.step,
197
+ tick: branch.baseSnapshotRef.tick,
198
+ snapshotHash: branch.baseSnapshotRef.snapshotHash,
199
+ },
200
+ branchStep: diff.toStep,
201
+ branchTick: diff.toTick,
202
+ branchSnapshotHash: diff.toSnapshotHash,
203
+ worldChanges: diff.worldChanges,
204
+ polityDeltas: diff.polityDeltas,
205
+ };
206
+ }
207
+ export function serializeEvolutionResult(result) {
208
+ return JSON.stringify({
209
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
210
+ payload: {
211
+ ...result,
212
+ ruleset: cloneRuleset(result.ruleset),
213
+ initialSnapshot: cloneSnapshot(result.initialSnapshot),
214
+ finalSnapshot: cloneSnapshot(result.finalSnapshot),
215
+ timeline: result.timeline.map((event) => ({ ...event, metrics: { ...event.metrics } })),
216
+ checkpoints: result.checkpoints?.map((checkpoint) => ({
217
+ ...checkpoint,
218
+ snapshot: cloneSnapshot(checkpoint.snapshot),
219
+ metadata: {
220
+ ...checkpoint.metadata,
221
+ rulesetProfile: cloneRuleset(checkpoint.metadata.rulesetProfile),
222
+ },
223
+ })),
224
+ checkpointDiffs: result.checkpointDiffs?.map(cloneCheckpointDiff),
225
+ deltas: result.deltas?.map(cloneDelta),
226
+ metrics: { ...result.metrics },
227
+ },
228
+ });
229
+ }
230
+ export function deserializeEvolutionResult(json) {
231
+ const parsed = JSON.parse(json);
232
+ const payload = parsed.payload;
233
+ if (parsed.schemaVersion !== WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION || !payload) {
234
+ throw new Error("Invalid evolution result payload: unsupported schema version or missing payload");
235
+ }
236
+ if (payload.schemaVersion !== WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION) {
237
+ throw new Error("Invalid evolution result payload: schema mismatch");
238
+ }
239
+ const result = {
240
+ ...payload,
241
+ request: {
242
+ steps: Math.max(0, Math.floor(payload.request.steps)),
243
+ ...(payload.request.includeDeltas != null ? { includeDeltas: payload.request.includeDeltas } : {}),
244
+ ...(payload.request.includeCheckpointDiffs != null ? { includeCheckpointDiffs: payload.request.includeCheckpointDiffs } : {}),
245
+ ...(payload.request.checkpointInterval != null ? { checkpointInterval: payload.request.checkpointInterval } : {}),
246
+ },
247
+ ruleset: cloneRuleset(payload.ruleset),
248
+ initialSnapshot: cloneSnapshot(payload.initialSnapshot),
249
+ finalSnapshot: cloneSnapshot(payload.finalSnapshot),
250
+ timeline: payload.timeline.map((event) => ({
251
+ ...event,
252
+ metrics: { ...event.metrics },
253
+ })),
254
+ metrics: { ...payload.metrics },
255
+ };
256
+ if (payload.checkpoints) {
257
+ result.checkpoints = payload.checkpoints.map((checkpoint) => ({
258
+ ...checkpoint,
259
+ snapshot: cloneSnapshot(checkpoint.snapshot),
260
+ metadata: {
261
+ ...checkpoint.metadata,
262
+ rulesetProfile: cloneRuleset(checkpoint.metadata.rulesetProfile),
263
+ },
264
+ }));
265
+ }
266
+ if (payload.checkpointDiffs) {
267
+ result.checkpointDiffs = payload.checkpointDiffs.map(cloneCheckpointDiff);
268
+ }
269
+ if (payload.deltas) {
270
+ result.deltas = payload.deltas.map(cloneDelta);
271
+ }
272
+ return result;
273
+ }
274
+ export function serializeEvolutionCheckpoint(checkpoint) {
275
+ const payload = {
276
+ schemaVersion: WORLD_EVOLUTION_CHECKPOINT_SCHEMA_VERSION,
277
+ checkpoint: cloneCheckpoint(checkpoint),
278
+ };
279
+ return JSON.stringify(payload);
280
+ }
281
+ export function deserializeEvolutionCheckpoint(json) {
282
+ const parsed = JSON.parse(json);
283
+ if (parsed.schemaVersion !== WORLD_EVOLUTION_CHECKPOINT_SCHEMA_VERSION || parsed.checkpoint == null) {
284
+ throw new Error("Invalid evolution checkpoint payload: unsupported schema version or missing checkpoint");
285
+ }
286
+ const checkpoint = cloneCheckpoint(parsed.checkpoint);
287
+ validateCheckpointCompatibility(checkpoint);
288
+ return checkpoint;
289
+ }
290
+ export function serializeEvolutionIntermediateState(session) {
291
+ return serializeEvolutionCheckpoint(buildSessionCheckpoint(session));
292
+ }
293
+ export function serializeEvolutionFinalState(result) {
294
+ const checkpoint = {
295
+ step: Number.MAX_SAFE_INTEGER,
296
+ tick: result.finalSnapshot.tick,
297
+ summary: `final-state tick=${result.finalSnapshot.tick}`,
298
+ snapshot: cloneSnapshot(result.finalSnapshot),
299
+ metadata: {
300
+ engineVersion: result.engineVersion,
301
+ seed: result.finalSnapshot.worldSeed,
302
+ rulesetProfile: cloneRuleset(result.ruleset),
303
+ step: Number.MAX_SAFE_INTEGER,
304
+ schemaVersion: WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION,
305
+ },
306
+ };
307
+ return serializeEvolutionCheckpoint(checkpoint);
308
+ }
309
+ export function buildEvolutionCheckpointDiffs(checkpoints) {
310
+ if (checkpoints.length < 2)
311
+ return [];
312
+ const diffs = [];
313
+ for (let i = 1; i < checkpoints.length; i += 1) {
314
+ const prev = checkpoints[i - 1];
315
+ const next = checkpoints[i];
316
+ if (!prev || !next)
317
+ continue;
318
+ diffs.push(diffEvolutionSnapshots(prev, next));
319
+ }
320
+ return diffs;
321
+ }
322
+ function toTimelineEvent(event) {
323
+ return {
324
+ step: event.step,
325
+ tick: event.tick,
326
+ kind: "evolution.step",
327
+ summary: `tick=${event.tick} trade=${event.trade.length} wars=${event.wars.length} migrations=${event.migrations.length} climate=${event.climateEventIds.length} epidemicΔ=${event.epidemicPopulationDelta}`,
328
+ tradeCount: event.trade.length,
329
+ warCount: event.wars.length,
330
+ migrationCount: event.migrations.length,
331
+ climateEventCount: event.climateEventIds.length,
332
+ epidemicPopulationDelta: event.epidemicPopulationDelta,
333
+ metrics: { ...event.metrics },
334
+ };
335
+ }
336
+ function mapCheckpoints(session, checkpoints) {
337
+ if (!checkpoints || checkpoints.length === 0)
338
+ return [];
339
+ return checkpoints.map((checkpoint) => ({
340
+ step: checkpoint.step,
341
+ tick: checkpoint.tick,
342
+ summary: `checkpoint step=${checkpoint.step} tick=${checkpoint.tick}`,
343
+ snapshot: cloneSnapshot(checkpoint.snapshot),
344
+ metadata: {
345
+ engineVersion: session.engineVersion,
346
+ seed: session.seed,
347
+ rulesetProfile: cloneRuleset(session.ruleset),
348
+ step: checkpoint.step,
349
+ schemaVersion: WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION,
350
+ },
351
+ }));
352
+ }
353
+ function cloneRuleset(ruleset) {
354
+ return { ...ruleset };
355
+ }
356
+ function cloneSnapshot(snapshot) {
357
+ return createWorldEvolutionSnapshot(snapshot);
358
+ }
359
+ function cloneDelta(delta) {
360
+ return {
361
+ step: delta.step,
362
+ tick: delta.tick,
363
+ polityDeltas: delta.polityDeltas.map((entry) => ({ ...entry })),
364
+ };
365
+ }
366
+ function cloneCheckpoint(checkpoint) {
367
+ return {
368
+ ...checkpoint,
369
+ snapshot: cloneSnapshot(checkpoint.snapshot),
370
+ metadata: {
371
+ ...checkpoint.metadata,
372
+ rulesetProfile: cloneRuleset(checkpoint.metadata.rulesetProfile),
373
+ },
374
+ };
375
+ }
376
+ function cloneCheckpointDiff(diff) {
377
+ return {
378
+ ...diff,
379
+ worldChanges: { ...diff.worldChanges },
380
+ polityDeltas: diff.polityDeltas.map((entry) => ({ ...entry })),
381
+ };
382
+ }
383
+ function runEvolutionFromSnapshot(input) {
384
+ const steps = Math.max(0, Math.floor(input.request.steps));
385
+ const includeDeltas = input.request.includeDeltas ?? false;
386
+ const includeCheckpointDiffs = input.request.includeCheckpointDiffs ?? false;
387
+ const checkpointInterval = input.request.checkpointInterval;
388
+ const initialSnapshot = createWorldEvolutionSnapshot({
389
+ ...input.snapshot,
390
+ worldSeed: input.snapshot.worldSeed,
391
+ });
392
+ const backendResult = runWorldEvolution({
393
+ snapshot: cloneSnapshot(initialSnapshot),
394
+ steps,
395
+ profile: cloneRuleset(input.ruleset),
396
+ includeDeltas,
397
+ ...(checkpointInterval != null ? { checkpointInterval } : {}),
398
+ });
399
+ const checkpoints = backendResult.checkpoints?.map((checkpoint) => ({
400
+ step: checkpoint.step,
401
+ tick: checkpoint.tick,
402
+ summary: `checkpoint step=${checkpoint.step} tick=${checkpoint.tick}`,
403
+ snapshot: cloneSnapshot(checkpoint.snapshot),
404
+ metadata: {
405
+ engineVersion: WORLD_EVOLUTION_ENGINE_VERSION,
406
+ seed: initialSnapshot.worldSeed,
407
+ rulesetProfile: cloneRuleset(input.ruleset),
408
+ step: checkpoint.step,
409
+ schemaVersion: WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION,
410
+ },
411
+ }));
412
+ return {
413
+ schemaVersion: WORLD_EVOLUTION_ORCHESTRATION_SCHEMA_VERSION,
414
+ engineVersion: WORLD_EVOLUTION_ENGINE_VERSION,
415
+ sessionId: buildSessionId(initialSnapshot.worldSeed, initialSnapshot.tick, input.ruleset.id),
416
+ request: {
417
+ steps,
418
+ includeDeltas,
419
+ ...(includeCheckpointDiffs ? { includeCheckpointDiffs: true } : {}),
420
+ ...(checkpointInterval != null ? { checkpointInterval } : {}),
421
+ },
422
+ ruleset: cloneRuleset(input.ruleset),
423
+ initialSnapshot,
424
+ finalSnapshot: cloneSnapshot(backendResult.finalSnapshot),
425
+ timeline: backendResult.timeline.map(toTimelineEvent),
426
+ metrics: { ...backendResult.metrics },
427
+ ...(checkpoints && checkpoints.length > 0
428
+ ? {
429
+ checkpoints,
430
+ ...(includeCheckpointDiffs ? { checkpointDiffs: buildEvolutionCheckpointDiffs(checkpoints) } : {}),
431
+ }
432
+ : {}),
433
+ ...(backendResult.deltas ? { deltas: backendResult.deltas.map(cloneDelta) } : {}),
434
+ };
435
+ }
436
+ function buildSessionId(seed, tick, rulesetId) {
437
+ return `evo-${seed}-${tick}-${rulesetId}`;
438
+ }
439
+ function buildBranchId(seed, tick, rulesetId, createdAtStep) {
440
+ return `branch-${seed}-${tick}-${rulesetId}-${createdAtStep}`;
441
+ }
442
+ function computeSnapshotHash(snapshot) {
443
+ return hashString(JSON.stringify(snapshot)).toString(16);
444
+ }
445
+ function buildSessionCheckpoint(session) {
446
+ return {
447
+ step: session.state.totalSteps,
448
+ tick: session.state.currentSnapshot.tick,
449
+ summary: `checkpoint step=${session.state.totalSteps} tick=${session.state.currentSnapshot.tick}`,
450
+ snapshot: cloneSnapshot(session.state.currentSnapshot),
451
+ metadata: {
452
+ engineVersion: session.engineVersion,
453
+ seed: session.seed,
454
+ rulesetProfile: cloneRuleset(session.ruleset),
455
+ step: session.state.totalSteps,
456
+ schemaVersion: WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION,
457
+ },
458
+ };
459
+ }
460
+ function validateCheckpointCompatibility(checkpoint) {
461
+ if (checkpoint.metadata.schemaVersion !== WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION) {
462
+ throw new Error(`Evolution checkpoint schema mismatch: checkpoint=${checkpoint.metadata.schemaVersion} runtime=${WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION}`);
463
+ }
464
+ if (checkpoint.metadata.engineVersion !== WORLD_EVOLUTION_ENGINE_VERSION) {
465
+ throw new Error(`Evolution checkpoint engine mismatch: checkpoint=${checkpoint.metadata.engineVersion} runtime=${WORLD_EVOLUTION_ENGINE_VERSION}`);
466
+ }
467
+ if (checkpoint.snapshot.worldSeed !== checkpoint.metadata.seed) {
468
+ throw new Error(`Evolution checkpoint seed mismatch: snapshot=${checkpoint.snapshot.worldSeed} metadata=${checkpoint.metadata.seed}`);
469
+ }
470
+ }
471
+ function diffEvolutionSnapshots(from, to) {
472
+ return diffSnapshots(from.snapshot, to.snapshot, from.step, from.tick, hashString(JSON.stringify(from.snapshot)).toString(16), to.step);
473
+ }
474
+ function diffSnapshots(fromSnapshotInput, toSnapshotInput, fromStep, fromTick, fromSnapshotHash, toStep) {
475
+ const fromSnapshot = cloneSnapshot(fromSnapshotInput);
476
+ const toSnapshot = cloneSnapshot(toSnapshotInput);
477
+ const worldChanges = {};
478
+ if (fromSnapshot.tick !== toSnapshot.tick)
479
+ worldChanges.tick = toSnapshot.tick;
480
+ if (fromSnapshot.worldSeed !== toSnapshot.worldSeed)
481
+ worldChanges.worldSeed = toSnapshot.worldSeed;
482
+ if (JSON.stringify(fromSnapshot.activeWars) !== JSON.stringify(toSnapshot.activeWars))
483
+ worldChanges.activeWars = toSnapshot.activeWars;
484
+ if (JSON.stringify(fromSnapshot.treaties) !== JSON.stringify(toSnapshot.treaties))
485
+ worldChanges.treaties = toSnapshot.treaties;
486
+ if (JSON.stringify(fromSnapshot.tradeRoutes) !== JSON.stringify(toSnapshot.tradeRoutes))
487
+ worldChanges.tradeRoutes = toSnapshot.tradeRoutes;
488
+ if (JSON.stringify(fromSnapshot.governanceStates) !== JSON.stringify(toSnapshot.governanceStates))
489
+ worldChanges.governanceStates = toSnapshot.governanceStates;
490
+ if (JSON.stringify(fromSnapshot.governanceLawRegistry) !== JSON.stringify(toSnapshot.governanceLawRegistry))
491
+ worldChanges.governanceLawRegistry = toSnapshot.governanceLawRegistry;
492
+ if (JSON.stringify(fromSnapshot.epidemics) !== JSON.stringify(toSnapshot.epidemics))
493
+ worldChanges.epidemics = toSnapshot.epidemics;
494
+ if (JSON.stringify(fromSnapshot.diseases) !== JSON.stringify(toSnapshot.diseases))
495
+ worldChanges.diseases = toSnapshot.diseases;
496
+ if (JSON.stringify(fromSnapshot.climateByPolity) !== JSON.stringify(toSnapshot.climateByPolity))
497
+ worldChanges.climateByPolity = toSnapshot.climateByPolity;
498
+ const beforeById = new Map(fromSnapshot.polities.map((polity) => [polity.id, polity]));
499
+ const polityDeltas = toSnapshot.polities.map((polity) => {
500
+ const prev = beforeById.get(polity.id);
501
+ return {
502
+ polityId: polity.id,
503
+ populationDelta: polity.population - (prev?.population ?? 0),
504
+ treasuryDelta_cu: polity.treasury_cu - (prev?.treasury_cu ?? 0),
505
+ stabilityDelta_Q: polity.stabilityQ - (prev?.stabilityQ ?? 0),
506
+ moraleDelta_Q: polity.moraleQ - (prev?.moraleQ ?? 0),
507
+ };
508
+ });
509
+ return {
510
+ fromStep,
511
+ toStep,
512
+ fromTick,
513
+ toTick: toSnapshot.tick,
514
+ fromSnapshotHash,
515
+ toSnapshotHash: hashString(JSON.stringify(toSnapshot)).toString(16),
516
+ worldChanges,
517
+ polityDeltas,
518
+ };
519
+ }
520
+ function computeStaticMetrics(snapshot) {
521
+ const polities = snapshot.polities;
522
+ const totalPopulation = polities.reduce((sum, polity) => sum + polity.population, 0);
523
+ const totalTreasury_cu = polities.reduce((sum, polity) => sum + polity.treasury_cu, 0);
524
+ const avgStability_Q = polities.length > 0
525
+ ? Math.floor(polities.reduce((sum, polity) => sum + polity.stabilityQ, 0) / polities.length)
526
+ : 0;
527
+ const avgMorale_Q = polities.length > 0
528
+ ? Math.floor(polities.reduce((sum, polity) => sum + polity.moraleQ, 0) / polities.length)
529
+ : 0;
530
+ return {
531
+ totalPopulation,
532
+ totalTreasury_cu,
533
+ avgStability_Q,
534
+ avgMorale_Q,
535
+ activeWars: snapshot.activeWars.length,
536
+ activeTreaties: snapshot.treaties.length,
537
+ viableTradeRoutes: snapshot.tradeRoutes.length,
538
+ activeEpidemics: snapshot.epidemics.length,
539
+ activeClimateEvents: snapshot.climateByPolity.reduce((sum, entry) => sum + entry.active.length, 0),
540
+ migrationsThisStep: 0,
541
+ migrationsTotalPopulation: 0,
542
+ };
543
+ }
@@ -16,6 +16,7 @@ import { defaultIntent } from "./sim/intent.js";
16
16
  import { defaultAction } from "./sim/action.js";
17
17
  import { defaultCondition } from "./sim/condition.js";
18
18
  import { defaultInjury } from "./sim/injury.js";
19
+ import { normalizeWorldInPlace } from "./sim/normalization.js";
19
20
  // ── Static archetype map ──────────────────────────────────────────────────────
20
21
  /** Map of string keys to Archetype objects for use with createWorld(). */
21
22
  export const ARCHETYPE_MAP = new Map([
@@ -155,5 +156,5 @@ export function createWorld(seed, entities) {
155
156
  if (dupes.length > 0) {
156
157
  throw new Error(`createWorld: duplicate entity IDs detected: ${[...new Set(dupes)].join(", ")}`);
157
158
  }
158
- return { tick: 0, seed, entities: built };
159
+ return normalizeWorldInPlace({ tick: 0, seed, entities: built });
159
160
  }
@@ -14,7 +14,10 @@ import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
14
14
  import { join, resolve, extname, basename } from "node:path";
15
15
  import { createHash } from "node:crypto";
16
16
  import { validatePack, loadPack } from "../src/content-pack.js";
17
+ import { loadContentPack } from "../src/content/loader.js";
18
+ import { runContentSemanticChecks } from "../src/content/validator.js";
17
19
  import { diffReplayJson } from "../src/netcode.js";
20
+ import { installPluginFromRegistry } from "../src/plugins/registry.js";
18
21
  import { q } from "../src/units.js";
19
22
  // ── Helpers ───────────────────────────────────────────────────────────────────
20
23
  function readJson(filePath) {
@@ -176,8 +179,50 @@ function cmdReplayDiff(args) {
176
179
  console.error(` hash B: ${result.hashB?.toString(16)}`);
177
180
  process.exit(1);
178
181
  }
182
+ async function cmdContentValidate(args) {
183
+ const filePath = args[0];
184
+ if (!filePath) {
185
+ console.error("Usage: ananke validate <content-pack.json>");
186
+ process.exit(1);
187
+ }
188
+ try {
189
+ const pack = await loadContentPack(resolve(filePath));
190
+ const warnings = runContentSemanticChecks(pack);
191
+ console.log(JSON.stringify({
192
+ ok: true,
193
+ pack: `${pack.name}@${pack.version}`,
194
+ schemaErrors: [],
195
+ semanticWarnings: warnings,
196
+ }, null, 2));
197
+ process.exit(0);
198
+ }
199
+ catch (error) {
200
+ console.log(JSON.stringify({
201
+ ok: false,
202
+ schemaErrors: [{ path: "$", message: String(error) }],
203
+ semanticWarnings: [],
204
+ }, null, 2));
205
+ process.exit(1);
206
+ }
207
+ }
208
+ async function cmdInstall(args) {
209
+ const pluginName = args[0];
210
+ if (!pluginName) {
211
+ console.error("Usage: ananke install <plugin-name>");
212
+ process.exit(1);
213
+ }
214
+ try {
215
+ const installPath = await installPluginFromRegistry(pluginName);
216
+ console.log(`✓ Installed ${pluginName} to ${installPath}`);
217
+ process.exit(0);
218
+ }
219
+ catch (error) {
220
+ console.error(`✗ Install failed: ${String(error)}`);
221
+ process.exit(1);
222
+ }
223
+ }
179
224
  // ── Entry point ───────────────────────────────────────────────────────────────
180
- function main() {
225
+ async function main() {
181
226
  const argv = process.argv.slice(2);
182
227
  const cmd = argv[0];
183
228
  if (!cmd) {
@@ -215,6 +260,12 @@ function main() {
215
260
  process.exit(1);
216
261
  }
217
262
  break;
263
+ case "validate":
264
+ await cmdContentValidate(argv.slice(1));
265
+ break;
266
+ case "install":
267
+ await cmdInstall(argv.slice(1));
268
+ break;
218
269
  default:
219
270
  console.error(`Unknown command: ${cmd}`);
220
271
  printHelp();
@@ -225,9 +276,11 @@ function printHelp() {
225
276
  console.log("Ananke CLI");
226
277
  console.log("");
227
278
  console.log("Commands:");
279
+ console.log(" install <plugin-name> — install a plugin from ananke-plugins registry");
280
+ console.log(" validate <file.json> — validate a content pack (schema + semantic checks)");
228
281
  console.log(" pack validate <file.json> — validate a pack manifest");
229
282
  console.log(" pack bundle <directory> [out.json] — merge JSON files into one pack");
230
283
  console.log(" pack load <file.json> — load a pack and report registered ids");
231
284
  console.log(" replay diff <replay-a.json> <replay-b.json> — find the first tick divergence between two replays");
232
285
  }
233
- main();
286
+ void main();