@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.
- package/CHANGELOG.md +288 -0
- package/README.md +150 -432
- package/STABLE_API.md +113 -490
- package/conformance/README.md +46 -44
- package/conformance/lockstep-sequence.json +395 -395
- package/conformance/phase-order.json +21 -0
- package/conformance/replay-parity.json +65 -65
- package/conformance/state-hash-regression.json +39 -0
- package/conformance/state-hash.json +23 -22
- package/dist/src/conformance.d.ts +8 -2
- package/dist/src/content/index.d.ts +3 -0
- package/dist/src/content/index.js +2 -0
- package/dist/src/content/injector.d.ts +19 -0
- package/dist/src/content/injector.js +61 -0
- package/dist/src/content/loader.d.ts +3 -0
- package/dist/src/content/loader.js +170 -0
- package/dist/src/content/types.d.ts +56 -0
- package/dist/src/content/types.js +1 -0
- package/dist/src/content/validator.d.ts +6 -0
- package/dist/src/content/validator.js +14 -0
- package/dist/src/content-pack.d.ts +3 -2
- package/dist/src/content-pack.js +205 -10
- package/dist/src/data-governance.d.ts +73 -0
- package/dist/src/data-governance.js +188 -0
- package/dist/src/debug.d.ts +6 -0
- package/dist/src/debug.js +9 -0
- package/dist/src/determinism.d.ts +12 -0
- package/dist/src/determinism.js +93 -0
- package/dist/src/dialogue.d.ts +41 -0
- package/dist/src/dialogue.js +113 -2
- package/dist/src/economy.d.ts +97 -3
- package/dist/src/economy.js +191 -37
- package/dist/src/history/autosave.d.ts +19 -0
- package/dist/src/history/autosave.js +98 -0
- package/dist/src/history/timetravel.d.ts +28 -0
- package/dist/src/history/timetravel.js +61 -0
- package/dist/src/index.d.ts +19 -31
- package/dist/src/index.js +20 -38
- package/dist/src/model3d.js +63 -2
- package/dist/src/narrative/combat-logger.d.ts +25 -0
- package/dist/src/narrative/combat-logger.js +97 -0
- package/dist/src/narrative/plausibility.d.ts +30 -0
- package/dist/src/narrative/plausibility.js +70 -0
- package/dist/src/narrative-layer.d.ts +2 -0
- package/dist/src/narrative-layer.js +2 -0
- package/dist/src/navigation/causal-chain.d.ts +26 -0
- package/dist/src/navigation/causal-chain.js +74 -0
- package/dist/src/netcode.d.ts +1 -1
- package/dist/src/netcode.js +1 -1
- package/dist/src/performance/adaptive-tick.d.ts +23 -0
- package/dist/src/performance/adaptive-tick.js +50 -0
- package/dist/src/plugins/loader.d.ts +23 -0
- package/dist/src/plugins/loader.js +86 -0
- package/dist/src/plugins/registry.d.ts +11 -0
- package/dist/src/plugins/registry.js +28 -0
- package/dist/src/plugins/types.d.ts +31 -0
- package/dist/src/plugins/types.js +7 -0
- package/dist/src/replay.js +5 -2
- package/dist/src/scenario.js +2 -1
- package/dist/src/serialization/binary.d.ts +5 -0
- package/dist/src/serialization/binary.js +193 -0
- package/dist/src/session.d.ts +115 -0
- package/dist/src/session.js +228 -0
- package/dist/src/sim/ai/decide.js +1 -1
- package/dist/src/sim/ai/personality.js +2 -2
- package/dist/src/sim/bodyplan.d.ts +30 -0
- package/dist/src/sim/context.d.ts +2 -0
- package/dist/src/sim/kernel.js +80 -1294
- package/dist/src/sim/normalization.d.ts +12 -0
- package/dist/src/sim/normalization.js +92 -0
- package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
- package/dist/src/sim/resolvers/attack-resolver.js +310 -0
- package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
- package/dist/src/sim/resolvers/capability-resolver.js +270 -0
- package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
- package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
- package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
- package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
- package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
- package/dist/src/sim/resolvers/treat-resolver.js +77 -0
- package/dist/src/sim/step/apply/intents.d.ts +6 -0
- package/dist/src/sim/step/apply/intents.js +83 -0
- package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
- package/dist/src/sim/step/kernel-pipeline.js +15 -0
- package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
- package/dist/src/sim/step/phases/capability-phase.js +65 -0
- package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
- package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
- package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
- package/dist/src/sim/step/phases/prepare-phase.js +33 -0
- package/dist/src/sim/step/pipeline.d.ts +2 -0
- package/dist/src/sim/step/pipeline.js +15 -0
- package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
- package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
- package/dist/src/sim/step/world-phases.d.ts +7 -0
- package/dist/src/sim/step/world-phases.js +15 -0
- package/dist/src/sim/step/world-step-context.d.ts +23 -0
- package/dist/src/sim/step/world-step-context.js +1 -0
- package/dist/src/sim/systemic-toxicology.js +1 -1
- package/dist/src/sim/team.js +2 -2
- package/dist/src/sim/testing.js +4 -3
- package/dist/src/sim/toxicology.d.ts +1 -1
- package/dist/src/sim/toxicology.js +1 -1
- package/dist/src/sim/world.d.ts +17 -24
- package/dist/src/snapshot.js +3 -6
- package/dist/src/tier2.d.ts +19 -0
- package/dist/src/tier2.js +21 -0
- package/dist/src/tier3.d.ts +10 -0
- package/dist/src/tier3.js +12 -0
- package/dist/src/version.d.ts +6 -0
- package/dist/src/version.js +8 -0
- package/dist/src/wasm/bridge.d.ts +14 -0
- package/dist/src/wasm/bridge.js +84 -0
- package/dist/src/wasm-kernel.d.ts +2 -0
- package/dist/src/wasm-kernel.js +1 -0
- package/dist/src/world-evolution-backend/engine.d.ts +4 -0
- package/dist/src/world-evolution-backend/engine.js +347 -0
- package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
- package/dist/src/world-evolution-backend/host-schema.js +375 -0
- package/dist/src/world-evolution-backend/index.d.ts +1 -0
- package/dist/src/world-evolution-backend/index.js +1 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
- package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
- package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
- package/dist/src/world-evolution-backend/profiles.js +136 -0
- package/dist/src/world-evolution-backend/public.d.ts +11 -0
- package/dist/src/world-evolution-backend/public.js +7 -0
- package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
- package/dist/src/world-evolution-backend/reproducibility.js +35 -0
- package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
- package/dist/src/world-evolution-backend/timeline.js +337 -0
- package/dist/src/world-evolution-backend/types.d.ts +138 -0
- package/dist/src/world-evolution-backend/types.js +1 -0
- package/dist/src/world-evolution-host-backend.d.ts +51 -0
- package/dist/src/world-evolution-host-backend.js +97 -0
- package/dist/src/world-evolution.d.ts +201 -0
- package/dist/src/world-evolution.js +543 -0
- package/dist/src/world-factory.js +2 -1
- package/dist/tools/pack-cli.js +55 -2
- package/docs/bridge-contract.md +140 -332
- package/docs/completion-evidence-dashboard.md +63 -0
- package/docs/host-contract.md +86 -310
- package/docs/host-world-evolution-schema.md +155 -0
- package/docs/integration-primer.md +29 -22
- package/docs/migration-monolith-to-modular.md +102 -120
- package/docs/package-architecture.md +116 -323
- package/docs/plugins/README.md +42 -0
- package/docs/plugins/hooks.md +22 -0
- package/docs/plugins/publishing.md +29 -0
- package/docs/plugins/security-model.md +273 -0
- package/docs/project-overview.md +30 -30
- package/docs/release-dashboard.md +47 -0
- package/docs/session-api.md +127 -0
- package/docs/stable-api-manifest.json +52 -0
- package/docs/trust-dashboard.md +37 -0
- package/docs/versioning.md +256 -245
- package/docs/wire-protocol.md +140 -209
- package/docs/world-evolution-backend.md +200 -0
- package/docs/world-evolution-engine.md +150 -0
- package/docs/world-evolution-host-backend.md +70 -0
- package/docs/world-evolution-orchestration.md +248 -0
- package/package.json +125 -6
- package/schema/content-pack.schema.json +85 -0
- package/schema/plugin.schema.json +57 -0
- package/schema/world-evolution-input.schema.json +196 -0
package/dist/src/snapshot.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* 0x09 object (uint32 LE count + key-value pairs, keys as 0x07 strings)
|
|
28
28
|
* 0x0A undefined/absent (skipped in round-trip)
|
|
29
29
|
*/
|
|
30
|
+
import { normalizeWorldInPlace } from "./sim/normalization.js";
|
|
30
31
|
// ── Diff ──────────────────────────────────────────────────────────────────────
|
|
31
32
|
/**
|
|
32
33
|
* Compute the diff between two `WorldState` snapshots.
|
|
@@ -46,11 +47,7 @@ export function diffWorldState(prev, next) {
|
|
|
46
47
|
const worldKeys = [
|
|
47
48
|
"tick", "seed",
|
|
48
49
|
"activeFieldEffects",
|
|
49
|
-
"
|
|
50
|
-
"__factionRegistry",
|
|
51
|
-
"__partyRegistry",
|
|
52
|
-
"__relationshipGraph",
|
|
53
|
-
"__nutritionAccum",
|
|
50
|
+
"runtimeState",
|
|
54
51
|
];
|
|
55
52
|
for (const key of worldKeys) {
|
|
56
53
|
if (key === "entities")
|
|
@@ -120,7 +117,7 @@ export function applyDiff(base, diff) {
|
|
|
120
117
|
entities = [...entities, ...diff.added];
|
|
121
118
|
// Restore canonical sort order (ascending id)
|
|
122
119
|
entities.sort((a, b) => a.id - b.id);
|
|
123
|
-
return { ...next, entities };
|
|
120
|
+
return normalizeWorldInPlace({ ...next, entities });
|
|
124
121
|
}
|
|
125
122
|
// ── isEmpty / stats ───────────────────────────────────────────────────────────
|
|
126
123
|
/**
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./archetypes.js";
|
|
2
|
+
export * from "./generate.js";
|
|
3
|
+
export * from "./equipment.js";
|
|
4
|
+
export * from "./weapons.js";
|
|
5
|
+
export * from "./presets.js";
|
|
6
|
+
export * from "./channels.js";
|
|
7
|
+
export * from "./traits.js";
|
|
8
|
+
export * from "./describe.js";
|
|
9
|
+
export * from "./sim/vec3.js";
|
|
10
|
+
export * from "./sim/condition.js";
|
|
11
|
+
export * from "./sim/injury.js";
|
|
12
|
+
export * from "./sim/kinds.js";
|
|
13
|
+
export * from "./sim/body.js";
|
|
14
|
+
export * from "./model3d.js";
|
|
15
|
+
export * from "./bridge/index.js";
|
|
16
|
+
export * from "./world-factory.js";
|
|
17
|
+
export * from "./scenario.js";
|
|
18
|
+
export * from "./navigation/causal-chain.js";
|
|
19
|
+
export * from "./data-governance.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Tier 2 — Experimental host-facing surface.
|
|
2
|
+
// Usable, but subject to change across minor versions.
|
|
3
|
+
export * from "./archetypes.js";
|
|
4
|
+
export * from "./generate.js";
|
|
5
|
+
export * from "./equipment.js";
|
|
6
|
+
export * from "./weapons.js";
|
|
7
|
+
export * from "./presets.js";
|
|
8
|
+
export * from "./channels.js";
|
|
9
|
+
export * from "./traits.js";
|
|
10
|
+
export * from "./describe.js";
|
|
11
|
+
export * from "./sim/vec3.js";
|
|
12
|
+
export * from "./sim/condition.js";
|
|
13
|
+
export * from "./sim/injury.js";
|
|
14
|
+
export * from "./sim/kinds.js";
|
|
15
|
+
export * from "./sim/body.js";
|
|
16
|
+
export * from "./model3d.js";
|
|
17
|
+
export * from "./bridge/index.js";
|
|
18
|
+
export * from "./world-factory.js";
|
|
19
|
+
export * from "./scenario.js";
|
|
20
|
+
export * from "./navigation/causal-chain.js";
|
|
21
|
+
export * from "./data-governance.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./catalog.js";
|
|
2
|
+
export * from "./sim/formation-combat.js";
|
|
3
|
+
export * from "./sim/cover.js";
|
|
4
|
+
export * from "./sim/ai/behavior-trees.js";
|
|
5
|
+
export * from "./snapshot.js";
|
|
6
|
+
export * from "./parallel.js";
|
|
7
|
+
export * from "./modding.js";
|
|
8
|
+
export * from "./serialization/binary.js";
|
|
9
|
+
export * from "./history/timetravel.js";
|
|
10
|
+
export * from "./history/autosave.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Tier 3 — Internal / advanced surface.
|
|
2
|
+
// No stability guarantees; pin commit hashes if consumed directly.
|
|
3
|
+
export * from "./catalog.js";
|
|
4
|
+
export * from "./sim/formation-combat.js";
|
|
5
|
+
export * from "./sim/cover.js";
|
|
6
|
+
export * from "./sim/ai/behavior-trees.js";
|
|
7
|
+
export * from "./snapshot.js";
|
|
8
|
+
export * from "./parallel.js";
|
|
9
|
+
export * from "./modding.js";
|
|
10
|
+
export * from "./serialization/binary.js";
|
|
11
|
+
export * from "./history/timetravel.js";
|
|
12
|
+
export * from "./history/autosave.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WasmSnapshotEntity {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
hp: number;
|
|
5
|
+
alive: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AnankeWasmBridge {
|
|
8
|
+
backend: "wasm" | "ts";
|
|
9
|
+
world_create: (seed: number) => number;
|
|
10
|
+
world_step: (commands: Int32Array) => number;
|
|
11
|
+
world_stepBatch?: (commands: Int32Array, commandCount: number) => number;
|
|
12
|
+
world_extractSnapshot: () => WasmSnapshotEntity[];
|
|
13
|
+
}
|
|
14
|
+
export declare function initAnankeWasm(): Promise<AnankeWasmBridge>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
function createTsFallback() {
|
|
4
|
+
const entities = [];
|
|
5
|
+
let seed = 1;
|
|
6
|
+
const next = () => (seed = (seed * 1664525 + 1013904223) | 0);
|
|
7
|
+
return {
|
|
8
|
+
backend: "ts",
|
|
9
|
+
world_create(worldSeed) {
|
|
10
|
+
entities.length = 0;
|
|
11
|
+
seed = worldSeed | 0;
|
|
12
|
+
for (let i = 0; i < 256; i++) {
|
|
13
|
+
entities.push({ x: (next() & 0x3fff) - 0x1fff, y: (next() & 0x3fff) - 0x1fff, hp: 1000, alive: 1 });
|
|
14
|
+
}
|
|
15
|
+
return entities.length;
|
|
16
|
+
},
|
|
17
|
+
world_step(commands) {
|
|
18
|
+
for (let i = 0; i < commands.length; i += 3) {
|
|
19
|
+
const idx = commands[i];
|
|
20
|
+
if (!entities[idx] || entities[idx].alive === 0)
|
|
21
|
+
continue;
|
|
22
|
+
entities[idx].x += commands[i + 1];
|
|
23
|
+
entities[idx].y += commands[i + 2];
|
|
24
|
+
}
|
|
25
|
+
return entities.length;
|
|
26
|
+
},
|
|
27
|
+
world_extractSnapshot() {
|
|
28
|
+
return entities.map((entity) => ({ ...entity }));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function initAnankeWasm() {
|
|
33
|
+
try {
|
|
34
|
+
if (typeof WebAssembly === "undefined")
|
|
35
|
+
return createTsFallback();
|
|
36
|
+
const wasmPath = fileURLToPath(new URL("../../wasm/ananke-core.wasm", import.meta.url));
|
|
37
|
+
const bytes = await readFile(wasmPath);
|
|
38
|
+
const instance = await WebAssembly.instantiate(bytes, {});
|
|
39
|
+
const ex = instance.instance.exports;
|
|
40
|
+
let commandPtr = 0;
|
|
41
|
+
let commandCapacity = 0;
|
|
42
|
+
function ensureCommandBuffer(commandsLength) {
|
|
43
|
+
if (commandsLength <= commandCapacity && commandPtr !== 0)
|
|
44
|
+
return commandPtr;
|
|
45
|
+
commandPtr = ex.alloc(commandsLength * Int32Array.BYTES_PER_ELEMENT);
|
|
46
|
+
commandCapacity = commandsLength;
|
|
47
|
+
return commandPtr;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
backend: "wasm",
|
|
51
|
+
world_create(seed) {
|
|
52
|
+
return ex.world_create(seed | 0);
|
|
53
|
+
},
|
|
54
|
+
world_step(commands) {
|
|
55
|
+
const ptr = ensureCommandBuffer(commands.length);
|
|
56
|
+
new Int32Array(ex.memory.buffer, ptr, commands.length).set(commands);
|
|
57
|
+
return ex.world_step(ptr, Math.trunc(commands.length / 3));
|
|
58
|
+
},
|
|
59
|
+
world_stepBatch(commands, commandCount) {
|
|
60
|
+
const used = Math.max(0, Math.min(commands.length, commandCount * 3));
|
|
61
|
+
const ptr = ensureCommandBuffer(used);
|
|
62
|
+
new Int32Array(ex.memory.buffer, ptr, used).set(commands.subarray(0, used));
|
|
63
|
+
return ex.world_step(ptr, Math.trunc(used / 3));
|
|
64
|
+
},
|
|
65
|
+
world_extractSnapshot() {
|
|
66
|
+
const count = ex.world_step(ex.alloc(0), 0);
|
|
67
|
+
const size = ex.snapshot_size_for(count);
|
|
68
|
+
const outPtr = ex.alloc(size);
|
|
69
|
+
ex.world_extractSnapshot(outPtr);
|
|
70
|
+
const raw = new Int32Array(ex.memory.buffer, outPtr, Math.trunc(size / 4));
|
|
71
|
+
const entities = [];
|
|
72
|
+
const n = raw[0] ?? 0;
|
|
73
|
+
for (let i = 0; i < n; i++) {
|
|
74
|
+
const o = 1 + i * 4;
|
|
75
|
+
entities.push({ x: raw[o], y: raw[o + 1], hp: raw[o + 2], alive: raw[o + 3] });
|
|
76
|
+
}
|
|
77
|
+
return entities;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return createTsFallback();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -15,6 +15,7 @@ interface InjuryExports {
|
|
|
15
15
|
readConsciousness: (slot: number) => number;
|
|
16
16
|
readDead: (slot: number) => number;
|
|
17
17
|
stepBleedAndShock: (n: number) => void;
|
|
18
|
+
readBleedingRate: (slot: number, region: number) => number;
|
|
18
19
|
}
|
|
19
20
|
export interface WasmEntityReport {
|
|
20
21
|
entityId: number;
|
|
@@ -26,6 +27,7 @@ export interface WasmEntityReport {
|
|
|
26
27
|
projShock: number;
|
|
27
28
|
projConsciousness: number;
|
|
28
29
|
projDead: boolean;
|
|
30
|
+
projHeadBleedingRate: number;
|
|
29
31
|
}
|
|
30
32
|
export interface WasmStepReport {
|
|
31
33
|
tick: number;
|
package/dist/src/wasm-kernel.js
CHANGED
|
@@ -64,6 +64,7 @@ export class WasmKernel {
|
|
|
64
64
|
projShock: this.injury.readShock(i),
|
|
65
65
|
projConsciousness: this.injury.readConsciousness(i),
|
|
66
66
|
projDead: this.injury.readDead(i) === 1,
|
|
67
|
+
projHeadBleedingRate: this.injury.readBleedingRate(i, 0),
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
const summary = reports
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type WorldEvolutionRunRequest, type WorldEvolutionRunResult, type WorldEvolutionRulesetProfile, type WorldEvolutionSnapshot } from "./types.js";
|
|
2
|
+
export declare function runWorldEvolution(request: WorldEvolutionRunRequest): WorldEvolutionRunResult;
|
|
3
|
+
export declare function createWorldEvolutionSnapshot(snapshot: WorldEvolutionSnapshot): WorldEvolutionSnapshot;
|
|
4
|
+
export declare function listAvailableWorldEvolutionProfiles(): WorldEvolutionRulesetProfile[];
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { aggregateClimateEffects, generateClimateEvent, isClimateEventExpired, stepClimateEvent, } from "../climate.js";
|
|
2
|
+
import { isTreatyExpired, stepTreatyStrength, } from "../diplomacy.js";
|
|
3
|
+
import { computeEpidemicDeathPressure, deriveHealthCapacity, stepEpidemic, } from "../epidemic.js";
|
|
4
|
+
import { computeGovernanceModifiers, stepGovernanceCooldown, stepGovernanceStability, } from "../governance.js";
|
|
5
|
+
import { applyMigrationFlows, resolveMigration, } from "../migration.js";
|
|
6
|
+
import { createPolityRegistry, stepPolityDay, } from "../polity.js";
|
|
7
|
+
import { applyDailyTrade, isRouteViable, routeKey, stepRouteEfficiency, } from "../trade-routes.js";
|
|
8
|
+
import { hashString } from "../sim/seeds.js";
|
|
9
|
+
import { SCALE, clampQ } from "../units.js";
|
|
10
|
+
import { listWorldEvolutionProfiles, mergeWorldEvolutionProfileWithOverrides } from "./profiles.js";
|
|
11
|
+
import { WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION, } from "./types.js";
|
|
12
|
+
export function runWorldEvolution(request) {
|
|
13
|
+
const steps = Math.max(0, Math.floor(request.steps));
|
|
14
|
+
const initialSnapshot = normalizeSnapshot(request.snapshot);
|
|
15
|
+
const runtime = createRuntimeState(initialSnapshot, request);
|
|
16
|
+
const timeline = [];
|
|
17
|
+
const deltas = [];
|
|
18
|
+
const checkpoints = [];
|
|
19
|
+
for (let step = 1; step <= steps; step++) {
|
|
20
|
+
const before = snapshotPolityStats(runtime.snapshot.polities);
|
|
21
|
+
const event = evolveSingleStep(runtime, step);
|
|
22
|
+
timeline.push(event);
|
|
23
|
+
if (request.includeDeltas) {
|
|
24
|
+
deltas.push(buildDelta(step, runtime.snapshot.tick, before, runtime.snapshot.polities));
|
|
25
|
+
}
|
|
26
|
+
if (request.checkpointInterval != null && request.checkpointInterval > 0 && step % request.checkpointInterval === 0) {
|
|
27
|
+
checkpoints.push({
|
|
28
|
+
step,
|
|
29
|
+
tick: runtime.snapshot.tick,
|
|
30
|
+
snapshot: exportSnapshot(runtime),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const finalSnapshot = exportSnapshot(runtime);
|
|
35
|
+
const result = {
|
|
36
|
+
initialSnapshot,
|
|
37
|
+
finalSnapshot,
|
|
38
|
+
profile: runtime.profile,
|
|
39
|
+
timeline,
|
|
40
|
+
metrics: computeWorldMetrics(runtime, [], 0),
|
|
41
|
+
};
|
|
42
|
+
if (request.includeDeltas)
|
|
43
|
+
result.deltas = deltas;
|
|
44
|
+
if (request.checkpointInterval != null && request.checkpointInterval > 0)
|
|
45
|
+
result.checkpoints = checkpoints;
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
export function createWorldEvolutionSnapshot(snapshot) {
|
|
49
|
+
return normalizeSnapshot(snapshot);
|
|
50
|
+
}
|
|
51
|
+
export function listAvailableWorldEvolutionProfiles() {
|
|
52
|
+
return listWorldEvolutionProfiles();
|
|
53
|
+
}
|
|
54
|
+
function createRuntimeState(snapshot, request) {
|
|
55
|
+
const profile = mergeWorldEvolutionProfileWithOverrides(request.profileId, request.profile);
|
|
56
|
+
const polityRegistry = createPolityRegistry(snapshot.polities.map(clonePolity));
|
|
57
|
+
for (const [a, b] of snapshot.activeWars) {
|
|
58
|
+
const key = a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
59
|
+
polityRegistry.activeWars.add(key);
|
|
60
|
+
}
|
|
61
|
+
const treaties = new Map(snapshot.treaties.map((t) => [t.treatyId, { ...t }]));
|
|
62
|
+
const routes = new Map(snapshot.tradeRoutes.map((r) => [r.routeId, { ...r }]));
|
|
63
|
+
const governanceByPolity = new Map(snapshot.governanceStates.map((g) => [g.polityId, { ...g, activeLawIds: [...g.activeLawIds] }]));
|
|
64
|
+
const lawRegistry = new Map(snapshot.governanceLawRegistry.map((law) => [law.lawId, { ...law }]));
|
|
65
|
+
const epidemicsByPolity = new Map();
|
|
66
|
+
for (const e of snapshot.epidemics) {
|
|
67
|
+
const list = epidemicsByPolity.get(e.polityId) ?? [];
|
|
68
|
+
list.push({ ...e });
|
|
69
|
+
epidemicsByPolity.set(e.polityId, list);
|
|
70
|
+
}
|
|
71
|
+
const diseasesById = new Map(snapshot.diseases.map((d) => [d.id, { ...d }]));
|
|
72
|
+
const climateByPolity = new Map(snapshot.climateByPolity.map((entry) => [entry.polityId, entry.active.map(cloneClimateEvent)]));
|
|
73
|
+
return {
|
|
74
|
+
profile,
|
|
75
|
+
snapshot: normalizeSnapshot({
|
|
76
|
+
...snapshot,
|
|
77
|
+
polities: [...polityRegistry.polities.values()],
|
|
78
|
+
activeWars: [...snapshot.activeWars],
|
|
79
|
+
treaties: [...treaties.values()],
|
|
80
|
+
tradeRoutes: [...routes.values()],
|
|
81
|
+
governanceStates: [...governanceByPolity.values()],
|
|
82
|
+
governanceLawRegistry: [...lawRegistry.values()],
|
|
83
|
+
epidemics: [...snapshot.epidemics],
|
|
84
|
+
diseases: [...diseasesById.values()],
|
|
85
|
+
climateByPolity: [...climateByPolity.entries()].map(([polityId, active]) => ({ polityId, active })),
|
|
86
|
+
}),
|
|
87
|
+
polityRegistry,
|
|
88
|
+
lawRegistry,
|
|
89
|
+
governanceByPolity,
|
|
90
|
+
treaties,
|
|
91
|
+
routes,
|
|
92
|
+
epidemicsByPolity,
|
|
93
|
+
diseasesById,
|
|
94
|
+
climateByPolity,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function evolveSingleStep(runtime, step) {
|
|
98
|
+
const { snapshot, polityRegistry, profile } = runtime;
|
|
99
|
+
const tick = snapshot.tick;
|
|
100
|
+
const polityDayResult = profile.polityDayEnabled
|
|
101
|
+
? stepPolityDay(polityRegistry, snapshot.pairs, snapshot.worldSeed, tick)
|
|
102
|
+
: { trade: [], moraleDeltas: new Map(), stabilityDeltas: new Map() };
|
|
103
|
+
const wars = applyWarEffectsForMetrics(polityRegistry, snapshot.worldSeed, tick);
|
|
104
|
+
if (profile.governanceEnabled) {
|
|
105
|
+
for (const polity of sortedPolities(polityRegistry)) {
|
|
106
|
+
const state = runtime.governanceByPolity.get(polity.id);
|
|
107
|
+
if (!state)
|
|
108
|
+
continue;
|
|
109
|
+
stepGovernanceCooldown(state, 1);
|
|
110
|
+
stepGovernanceStability(polity, state, profile.governanceStabilityDaysPerStep, runtime.lawRegistry);
|
|
111
|
+
const modifiers = computeGovernanceModifiers(state, runtime.lawRegistry);
|
|
112
|
+
const taxDaily = Math.floor(polity.population * modifiers.taxEfficiencyMul_Q / SCALE.Q / 1000);
|
|
113
|
+
polity.treasury_cu += Math.max(0, taxDaily);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (profile.diplomacyEnabled) {
|
|
117
|
+
for (const treaty of [...runtime.treaties.values()].sort((a, b) => a.treatyId.localeCompare(b.treatyId))) {
|
|
118
|
+
stepTreatyStrength(treaty, profile.treatyStrengthBoost_Q);
|
|
119
|
+
if (isTreatyExpired(treaty, tick) || treaty.strength_Q <= 0) {
|
|
120
|
+
runtime.treaties.delete(treaty.treatyId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (profile.tradeEnabled) {
|
|
125
|
+
for (const route of [...runtime.routes.values()].sort((a, b) => a.routeId.localeCompare(b.routeId))) {
|
|
126
|
+
stepRouteEfficiency(route, profile.routeEfficiencyBoost_Q);
|
|
127
|
+
const polityA = polityRegistry.polities.get(route.polityAId);
|
|
128
|
+
const polityB = polityRegistry.polities.get(route.polityBId);
|
|
129
|
+
if (!polityA || !polityB)
|
|
130
|
+
continue;
|
|
131
|
+
const hasTradePact = hasTreaty(runtime.treaties, route.polityAId, route.polityBId, "trade_pact");
|
|
132
|
+
applyDailyTrade(polityA, polityB, route, hasTradePact);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const migrations = profile.migrationEnabled
|
|
136
|
+
? resolveMigration([...polityRegistry.polities.values()], buildMigrationContext(polityRegistry))
|
|
137
|
+
: [];
|
|
138
|
+
if (profile.migrationEnabled) {
|
|
139
|
+
applyMigrationFlows(polityRegistry, migrations);
|
|
140
|
+
}
|
|
141
|
+
let epidemicPopulationDelta = 0;
|
|
142
|
+
const climateEventIds = [];
|
|
143
|
+
if (profile.climateEnabled || profile.epidemicEnabled) {
|
|
144
|
+
for (const polity of sortedPolities(polityRegistry)) {
|
|
145
|
+
const climateEvents = runtime.climateByPolity.get(polity.id) ?? [];
|
|
146
|
+
if (profile.climateEnabled) {
|
|
147
|
+
const generated = generateClimateEvent(hashString(polity.id), snapshot.worldSeed, tick);
|
|
148
|
+
if (generated) {
|
|
149
|
+
climateEvents.push({ event: generated, remainingDays: generated.durationDays, elapsedDays: 0 });
|
|
150
|
+
climateEventIds.push(generated.eventId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const climateEffects = aggregateClimateEffects(climateEvents);
|
|
154
|
+
polity.stabilityQ = clampQ(polity.stabilityQ - climateEffects.unrestPressure_Q, 0, SCALE.Q);
|
|
155
|
+
if (profile.epidemicEnabled) {
|
|
156
|
+
const states = runtime.epidemicsByPolity.get(polity.id) ?? [];
|
|
157
|
+
const health = clampQ(deriveHealthCapacity(polity) + profile.epidemicHealthBuffer_Q - climateEffects.epidemicGrowthBonus_Q, 0, SCALE.Q);
|
|
158
|
+
for (const state of states) {
|
|
159
|
+
const disease = runtime.diseasesById.get(state.diseaseId);
|
|
160
|
+
if (!disease)
|
|
161
|
+
continue;
|
|
162
|
+
stepEpidemic(state, disease, 1, health);
|
|
163
|
+
const deathPressure_Q = computeEpidemicDeathPressure(state, disease);
|
|
164
|
+
const deathFromEpidemic = Math.floor(polity.population * deathPressure_Q / SCALE.Q);
|
|
165
|
+
const deathFromClimate = Math.floor(polity.population * climateEffects.deathPressure_Q / SCALE.Q / 100);
|
|
166
|
+
const totalDeaths = Math.max(0, deathFromEpidemic + deathFromClimate);
|
|
167
|
+
polity.population = Math.max(0, polity.population - totalDeaths);
|
|
168
|
+
epidemicPopulationDelta -= totalDeaths;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (profile.climateEnabled) {
|
|
172
|
+
for (const active of climateEvents) {
|
|
173
|
+
stepClimateEvent(active, 1);
|
|
174
|
+
}
|
|
175
|
+
runtime.climateByPolity.set(polity.id, climateEvents.filter((a) => !isClimateEventExpired(a)));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
snapshot.tick += 1;
|
|
180
|
+
syncRuntimeSnapshot(runtime);
|
|
181
|
+
return {
|
|
182
|
+
step,
|
|
183
|
+
tick: snapshot.tick,
|
|
184
|
+
trade: polityDayResult.trade,
|
|
185
|
+
wars,
|
|
186
|
+
migrations,
|
|
187
|
+
climateEventIds,
|
|
188
|
+
epidemicPopulationDelta,
|
|
189
|
+
metrics: computeWorldMetrics(runtime, migrations, epidemicPopulationDelta),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function applyWarEffectsForMetrics(registry, worldSeed, tick) {
|
|
193
|
+
// Wars are already stepped inside stepPolityDay; this returns a deterministic log representation.
|
|
194
|
+
const wars = [];
|
|
195
|
+
for (const pair of [...registry.activeWars].sort()) {
|
|
196
|
+
const [aId, bId] = pair.split(":");
|
|
197
|
+
if (!aId || !bId)
|
|
198
|
+
continue;
|
|
199
|
+
const attacker = registry.polities.get(aId);
|
|
200
|
+
const defender = registry.polities.get(bId);
|
|
201
|
+
if (!attacker || !defender)
|
|
202
|
+
continue;
|
|
203
|
+
wars.push({
|
|
204
|
+
attackerId: attacker.id,
|
|
205
|
+
defenderId: defender.id,
|
|
206
|
+
attackerWins: attacker.militaryStrength_Q >= defender.militaryStrength_Q,
|
|
207
|
+
stabilityDeltaAttacker: 0,
|
|
208
|
+
stabilityDeltaDefender: 0,
|
|
209
|
+
territoryGained: [],
|
|
210
|
+
});
|
|
211
|
+
// worldSeed and tick remain part of signature to preserve deterministic call-site identity.
|
|
212
|
+
void worldSeed;
|
|
213
|
+
void tick;
|
|
214
|
+
}
|
|
215
|
+
return wars;
|
|
216
|
+
}
|
|
217
|
+
function buildMigrationContext(registry) {
|
|
218
|
+
const atWar = new Set();
|
|
219
|
+
for (const pair of registry.activeWars) {
|
|
220
|
+
const [a, b] = pair.split(":");
|
|
221
|
+
if (!a || !b)
|
|
222
|
+
continue;
|
|
223
|
+
atWar.add(a);
|
|
224
|
+
atWar.add(b);
|
|
225
|
+
}
|
|
226
|
+
const out = new Map();
|
|
227
|
+
for (const polity of registry.polities.values()) {
|
|
228
|
+
out.set(polity.id, { polityId: polity.id, isAtWar: atWar.has(polity.id) });
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
function hasTreaty(treaties, polityAId, polityBId, type) {
|
|
233
|
+
const lo = polityAId < polityBId ? polityAId : polityBId;
|
|
234
|
+
const hi = polityAId < polityBId ? polityBId : polityAId;
|
|
235
|
+
const expected = `${lo}:${hi}:${type}`;
|
|
236
|
+
return treaties.has(expected);
|
|
237
|
+
}
|
|
238
|
+
function computeWorldMetrics(runtime, migrations, migrationsTotalPopulation) {
|
|
239
|
+
const polities = [...runtime.polityRegistry.polities.values()];
|
|
240
|
+
const totalPopulation = polities.reduce((sum, p) => sum + p.population, 0);
|
|
241
|
+
const totalTreasury_cu = polities.reduce((sum, p) => sum + p.treasury_cu, 0);
|
|
242
|
+
const avgStability_Q = polities.length > 0
|
|
243
|
+
? Math.floor(polities.reduce((sum, p) => sum + p.stabilityQ, 0) / polities.length)
|
|
244
|
+
: 0;
|
|
245
|
+
const avgMorale_Q = polities.length > 0
|
|
246
|
+
? Math.floor(polities.reduce((sum, p) => sum + p.moraleQ, 0) / polities.length)
|
|
247
|
+
: 0;
|
|
248
|
+
return {
|
|
249
|
+
totalPopulation,
|
|
250
|
+
totalTreasury_cu,
|
|
251
|
+
avgStability_Q,
|
|
252
|
+
avgMorale_Q,
|
|
253
|
+
activeWars: runtime.polityRegistry.activeWars.size,
|
|
254
|
+
activeTreaties: runtime.treaties.size,
|
|
255
|
+
viableTradeRoutes: [...runtime.routes.values()].filter(isRouteViable).length,
|
|
256
|
+
activeEpidemics: [...runtime.epidemicsByPolity.values()].reduce((sum, list) => sum + list.length, 0),
|
|
257
|
+
activeClimateEvents: [...runtime.climateByPolity.values()].reduce((sum, list) => sum + list.length, 0),
|
|
258
|
+
migrationsThisStep: migrations.length,
|
|
259
|
+
migrationsTotalPopulation,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function syncRuntimeSnapshot(runtime) {
|
|
263
|
+
runtime.snapshot.polities = sortedPolities(runtime.polityRegistry).map(clonePolity);
|
|
264
|
+
runtime.snapshot.activeWars = [...runtime.polityRegistry.activeWars]
|
|
265
|
+
.sort()
|
|
266
|
+
.map((pair) => pair.split(":"));
|
|
267
|
+
runtime.snapshot.treaties = [...runtime.treaties.values()].sort((a, b) => a.treatyId.localeCompare(b.treatyId)).map((t) => ({ ...t }));
|
|
268
|
+
runtime.snapshot.tradeRoutes = [...runtime.routes.values()].sort((a, b) => a.routeId.localeCompare(b.routeId)).map((r) => ({ ...r }));
|
|
269
|
+
runtime.snapshot.governanceStates = [...runtime.governanceByPolity.values()]
|
|
270
|
+
.sort((a, b) => a.polityId.localeCompare(b.polityId))
|
|
271
|
+
.map((g) => ({ ...g, activeLawIds: [...g.activeLawIds] }));
|
|
272
|
+
runtime.snapshot.epidemics = [...runtime.epidemicsByPolity.values()].flat().map((e) => ({ ...e }));
|
|
273
|
+
runtime.snapshot.climateByPolity = [...runtime.climateByPolity.entries()]
|
|
274
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
275
|
+
.map(([polityId, active]) => ({ polityId, active: active.map(cloneClimateEvent) }));
|
|
276
|
+
}
|
|
277
|
+
function exportSnapshot(runtime) {
|
|
278
|
+
syncRuntimeSnapshot(runtime);
|
|
279
|
+
return normalizeSnapshot(runtime.snapshot);
|
|
280
|
+
}
|
|
281
|
+
function normalizeSnapshot(snapshot) {
|
|
282
|
+
return {
|
|
283
|
+
schemaVersion: WORLD_EVOLUTION_BACKEND_SCHEMA_VERSION,
|
|
284
|
+
worldSeed: snapshot.worldSeed,
|
|
285
|
+
tick: snapshot.tick,
|
|
286
|
+
polities: [...snapshot.polities].map(clonePolity).sort((a, b) => a.id.localeCompare(b.id)),
|
|
287
|
+
pairs: [...snapshot.pairs].map((p) => ({ ...p })).sort((a, b) => routeKey(a.polityAId, a.polityBId).localeCompare(routeKey(b.polityAId, b.polityBId))),
|
|
288
|
+
activeWars: [...snapshot.activeWars].map((pair) => sortWarPair(pair)).sort((a, b) => `${a[0]}:${a[1]}`.localeCompare(`${b[0]}:${b[1]}`)),
|
|
289
|
+
treaties: [...snapshot.treaties].map((t) => ({ ...t })).sort((a, b) => a.treatyId.localeCompare(b.treatyId)),
|
|
290
|
+
tradeRoutes: [...snapshot.tradeRoutes].map((r) => ({ ...r })).sort((a, b) => a.routeId.localeCompare(b.routeId)),
|
|
291
|
+
governanceStates: [...snapshot.governanceStates].map((g) => ({ ...g, activeLawIds: [...g.activeLawIds] })).sort((a, b) => a.polityId.localeCompare(b.polityId)),
|
|
292
|
+
governanceLawRegistry: [...snapshot.governanceLawRegistry].map((law) => ({ ...law })).sort((a, b) => a.lawId.localeCompare(b.lawId)),
|
|
293
|
+
epidemics: [...snapshot.epidemics].map((e) => ({ ...e })).sort((a, b) => `${a.polityId}:${a.diseaseId}`.localeCompare(`${b.polityId}:${b.diseaseId}`)),
|
|
294
|
+
diseases: [...snapshot.diseases].map((d) => ({ ...d })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
295
|
+
climateByPolity: [...snapshot.climateByPolity]
|
|
296
|
+
.map((entry) => ({ polityId: entry.polityId, active: entry.active.map(cloneClimateEvent) }))
|
|
297
|
+
.sort((a, b) => a.polityId.localeCompare(b.polityId)),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function sortWarPair([a, b]) {
|
|
301
|
+
return a < b ? [a, b] : [b, a];
|
|
302
|
+
}
|
|
303
|
+
function sortedPolities(registry) {
|
|
304
|
+
return [...registry.polities.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
305
|
+
}
|
|
306
|
+
function clonePolity(polity) {
|
|
307
|
+
return {
|
|
308
|
+
...polity,
|
|
309
|
+
locationIds: [...polity.locationIds],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function cloneClimateEvent(active) {
|
|
313
|
+
return {
|
|
314
|
+
event: {
|
|
315
|
+
...active.event,
|
|
316
|
+
},
|
|
317
|
+
remainingDays: active.remainingDays,
|
|
318
|
+
elapsedDays: active.elapsedDays,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function snapshotPolityStats(polities) {
|
|
322
|
+
const map = new Map();
|
|
323
|
+
for (const polity of polities) {
|
|
324
|
+
map.set(polity.id, {
|
|
325
|
+
population: polity.population,
|
|
326
|
+
treasury: polity.treasury_cu,
|
|
327
|
+
stability: polity.stabilityQ,
|
|
328
|
+
morale: polity.moraleQ,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return map;
|
|
332
|
+
}
|
|
333
|
+
function buildDelta(step, tick, before, afterPolities) {
|
|
334
|
+
const polityDeltas = [...afterPolities]
|
|
335
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
336
|
+
.map((p) => {
|
|
337
|
+
const prev = before.get(p.id);
|
|
338
|
+
return {
|
|
339
|
+
polityId: p.id,
|
|
340
|
+
populationDelta: p.population - (prev?.population ?? 0),
|
|
341
|
+
treasuryDelta_cu: p.treasury_cu - (prev?.treasury ?? 0),
|
|
342
|
+
stabilityDelta_Q: p.stabilityQ - (prev?.stability ?? 0),
|
|
343
|
+
moraleDelta_Q: p.moraleQ - (prev?.morale ?? 0),
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
return { step, tick, polityDeltas };
|
|
347
|
+
}
|