@its-not-rocket-science/ananke 0.1.68 → 0.5.1
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 +274 -0
- package/README.md +150 -431
- 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
|
@@ -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
|
}
|
package/dist/tools/pack-cli.js
CHANGED
|
@@ -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();
|