@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "conformance/v1",
|
|
3
|
+
"id": "phase-order-01",
|
|
4
|
+
"description": "World-step phases must execute in this exact order for deterministic behavior.",
|
|
5
|
+
"kind": "phase-order",
|
|
6
|
+
"notes": [
|
|
7
|
+
"phase order is part of deterministic runtime contract.",
|
|
8
|
+
"changing order can alter cooldown, movement, and combat interaction semantics.",
|
|
9
|
+
"fixtures and runtime tests should fail immediately on order drift."
|
|
10
|
+
],
|
|
11
|
+
"phases": [
|
|
12
|
+
"prepare",
|
|
13
|
+
"cooldowns",
|
|
14
|
+
"input",
|
|
15
|
+
"movement",
|
|
16
|
+
"actions",
|
|
17
|
+
"impacts",
|
|
18
|
+
"systems",
|
|
19
|
+
"finalize"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "conformance/v1",
|
|
3
|
-
"id": "replay-parity-01",
|
|
4
|
-
"description": "Re-simulating this replay must reproduce the hash after each recorded frame.",
|
|
5
|
-
"kind": "replay-parity",
|
|
6
|
-
"notes": [
|
|
7
|
-
"seed=42; 2 entities; 10 ticks of noMove commands.",
|
|
8
|
-
"Fixed commands are used (not AI) so replayTo starts from a clean initial state.",
|
|
9
|
-
"For each entry: call replayTo(replay, recordedAtTick, ctx).",
|
|
10
|
-
"The returned world.tick must equal expectedWorldTick.",
|
|
11
|
-
"hashWorldState(world) must equal hashHex."
|
|
12
|
-
],
|
|
13
|
-
"replayJson": "{\"initialState\":{\"tick\":0,\"seed\":42,\"entities\":[{\"id\":1,\"teamId\":1,\"attributes\":{\"morphology\":{\"stature_m\":17400,\"mass_kg\":74977,\"actuatorMass_kg\":30125,\"actuatorScale\":9946,\"structureScale\":20000,\"reachScale\":10233},\"performance\":{\"peakForce_N\":165802,\"peakPower_W\":3600,\"continuousPower_W\":128,\"reserveEnergy_J\":15800,\"conversionEfficiency\":8651},\"control\":{\"controlQuality\":6848,\"reactionTime_s\":2329,\"stability\":6855,\"fineControl\":7624},\"resilience\":{\"surfaceIntegrity\":9459,\"bulkIntegrity\":9437,\"structureIntegrity\":10163,\"distressTolerance\":4629,\"shockTolerance\":5169,\"concussionTolerance\":5526,\"heatTolerance\":4758,\"coldTolerance\":4505,\"fatigueRate\":9668,\"recoveryRate\":10195},\"perception\":{\"visionRange_m\":2000000,\"visionArcDeg\":120,\"halfArcCosQ\":5000,\"hearingRange_m\":500000,\"decisionLatency_s\":5000,\"attentionDepth\":4,\"threatHorizon_m\":400000},\"cognition\":{\"linguistic\":6500,\"logicalMathematical\":6000,\"spatial\":6000,\"bodilyKinesthetic\":6000,\"musical\":5000,\"interpersonal\":6000,\"intrapersonal\":5500,\"naturalist\":5000,\"interSpecies\":3500}},\"energy\":{\"reserveEnergy_J\":15800,\"fatigue\":0},\"loadout\":{\"items\":[{\"id\":\"wpn_longsword\",\"kind\":\"weapon\",\"name\":\"Longsword\",\"mass_kg\":1500,\"bulk\":15000,\"reach_m\":9000,\"handedness\":\"twoHand\",\"momentArm_m\":5500,\"handlingMul\":10500,\"strikeEffectiveMassFrac\":1500,\"strikeSpeedMul\":10000,\"readyTime_s\":7500,\"damage\":{\"surfaceFrac\":3500,\"internalFrac\":4500,\"structuralFrac\":2000,\"bleedFactor\":7000,\"penetrationBias\":4000}}]},\"traits\":[],\"position_m\":{\"x\":-5000,\"y\":0,\"z\":0},\"velocity_mps\":{\"x\":0,\"y\":0,\"z\":0},\"intent\":{\"move\":{\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"},\"defence\":{\"mode\":\"none\",\"intensity\":0},\"prone\":false},\"action\":{\"attackCooldownTicks\":0,\"defenceCooldownTicks\":0,\"grappleCooldownTicks\":0,\"facingDirQ\":{\"x\":10000,\"y\":0,\"z\":0},\"weaponBindPartnerId\":0,\"weaponBindTicks\":0,\"swingMomentumQ\":0,\"shootCooldownTicks\":0,\"aimTicks\":0,\"aimTargetId\":0},\"condition\":{\"onFire\":0,\"corrosiveExposure\":0,\"radiation\":0,\"electricalOverload\":0,\"suffocation\":0,\"stunned\":0,\"prone\":false,\"pinned\":false,\"standBlockedTicks\":0,\"unconsciousTicks\":0,\"suppressedTicks\":0,\"blindTicks\":0,\"fearQ\":0,\"suppressionFearMul\":10000,\"recentAllyDeaths\":0,\"lastAllyDeathTick\":-1,\"surrendered\":false,\"rallyCooldownTicks\":0},\"injury\":{\"byRegion\":{\"head\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"torso\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0}},\"fluidLoss\":0,\"shock\":0,\"consciousness\":10000,\"dead\":false,\"hemolymphLoss\":0},\"grapple\":{\"holdingTargetId\":0,\"heldByIds\":[],\"gripQ\":0,\"position\":\"standing\"}},{\"id\":2,\"teamId\":2,\"attributes\":{\"morphology\":{\"stature_m\":17440,\"mass_kg\":74182,\"actuatorMass_kg\":29420,\"actuatorScale\":9904,\"structureScale\":20000,\"reachScale\":9895},\"performance\":{\"peakForce_N\":154468,\"peakPower_W\":3600,\"continuousPower_W\":129,\"reserveEnergy_J\":14502,\"conversionEfficiency\":8557},\"control\":{\"controlQuality\":7058,\"reactionTime_s\":2091,\"stability\":6517,\"fineControl\":7109},\"resilience\":{\"surfaceIntegrity\":9544,\"bulkIntegrity\":10338,\"structureIntegrity\":9742,\"distressTolerance\":5605,\"shockTolerance\":5235,\"concussionTolerance\":4836,\"heatTolerance\":5353,\"coldTolerance\":4988,\"fatigueRate\":9233,\"recoveryRate\":9064},\"perception\":{\"visionRange_m\":2000000,\"visionArcDeg\":120,\"halfArcCosQ\":5000,\"hearingRange_m\":500000,\"decisionLatency_s\":5000,\"attentionDepth\":4,\"threatHorizon_m\":400000},\"cognition\":{\"linguistic\":6500,\"logicalMathematical\":6000,\"spatial\":6000,\"bodilyKinesthetic\":6000,\"musical\":5000,\"interpersonal\":6000,\"intrapersonal\":5500,\"naturalist\":5000,\"interSpecies\":3500}},\"energy\":{\"reserveEnergy_J\":14502,\"fatigue\":0},\"loadout\":{\"items\":[{\"id\":\"wpn_longsword\",\"kind\":\"weapon\",\"name\":\"Longsword\",\"mass_kg\":1500,\"bulk\":15000,\"reach_m\":9000,\"handedness\":\"twoHand\",\"momentArm_m\":5500,\"handlingMul\":10500,\"strikeEffectiveMassFrac\":1500,\"strikeSpeedMul\":10000,\"readyTime_s\":7500,\"damage\":{\"surfaceFrac\":3500,\"internalFrac\":4500,\"structuralFrac\":2000,\"bleedFactor\":7000,\"penetrationBias\":4000}}]},\"traits\":[],\"position_m\":{\"x\":5000,\"y\":0,\"z\":0},\"velocity_mps\":{\"x\":0,\"y\":0,\"z\":0},\"intent\":{\"move\":{\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"},\"defence\":{\"mode\":\"none\",\"intensity\":0},\"prone\":false},\"action\":{\"attackCooldownTicks\":0,\"defenceCooldownTicks\":0,\"grappleCooldownTicks\":0,\"facingDirQ\":{\"x\":10000,\"y\":0,\"z\":0},\"weaponBindPartnerId\":0,\"weaponBindTicks\":0,\"swingMomentumQ\":0,\"shootCooldownTicks\":0,\"aimTicks\":0,\"aimTargetId\":0},\"condition\":{\"onFire\":0,\"corrosiveExposure\":0,\"radiation\":0,\"electricalOverload\":0,\"suffocation\":0,\"stunned\":0,\"prone\":false,\"pinned\":false,\"standBlockedTicks\":0,\"unconsciousTicks\":0,\"suppressedTicks\":0,\"blindTicks\":0,\"fearQ\":0,\"suppressionFearMul\":10000,\"recentAllyDeaths\":0,\"lastAllyDeathTick\":-1,\"surrendered\":false,\"rallyCooldownTicks\":0},\"injury\":{\"byRegion\":{\"head\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"torso\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0}},\"fluidLoss\":0,\"shock\":0,\"consciousness\":10000,\"dead\":false,\"hemolymphLoss\":0},\"grapple\":{\"holdingTargetId\":0,\"heldByIds\":[],\"gripQ\":0,\"position\":\"standing\"}}]},\"frames\":[{\"tick\":0,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":1,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":2,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":3,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":4,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":5,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":6,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":7,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":8,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":9,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]}]}",
|
|
14
|
-
"hashTrace": [
|
|
15
|
-
{
|
|
16
|
-
"recordedAtTick": 0,
|
|
17
|
-
"expectedWorldTick": 1,
|
|
18
|
-
"hashHex": "0xfdf533148d08f459"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"recordedAtTick": 1,
|
|
22
|
-
"expectedWorldTick": 2,
|
|
23
|
-
"hashHex": "0xfdf233148d06be82"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"recordedAtTick": 2,
|
|
27
|
-
"expectedWorldTick": 3,
|
|
28
|
-
"hashHex": "0xfdee2f148d02cedf"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"recordedAtTick": 3,
|
|
32
|
-
"expectedWorldTick": 4,
|
|
33
|
-
"hashHex": "0xfe062f148d1760c0"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"recordedAtTick": 4,
|
|
37
|
-
"expectedWorldTick": 5,
|
|
38
|
-
"hashHex": "0xfe0333148d1531b5"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"recordedAtTick": 5,
|
|
42
|
-
"expectedWorldTick": 6,
|
|
43
|
-
"hashHex": "0xfdff33148d1148de"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"recordedAtTick": 6,
|
|
47
|
-
"expectedWorldTick": 7,
|
|
48
|
-
"hashHex": "0xfdfc2f148d0f0c3b"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"recordedAtTick": 7,
|
|
52
|
-
"expectedWorldTick": 8,
|
|
53
|
-
"hashHex": "0xfddd2f148cf45bac"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"recordedAtTick": 8,
|
|
57
|
-
"expectedWorldTick": 9,
|
|
58
|
-
"hashHex": "0xfdda33148cf22ca1"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"recordedAtTick": 9,
|
|
62
|
-
"expectedWorldTick": 10,
|
|
63
|
-
"hashHex": "0x8fe5f3eba59e0b75"
|
|
64
|
-
}
|
|
65
|
-
]
|
|
1
|
+
{
|
|
2
|
+
"version": "conformance/v1",
|
|
3
|
+
"id": "replay-parity-01",
|
|
4
|
+
"description": "Re-simulating this replay must reproduce the hash after each recorded frame.",
|
|
5
|
+
"kind": "replay-parity",
|
|
6
|
+
"notes": [
|
|
7
|
+
"seed=42; 2 entities; 10 ticks of noMove commands.",
|
|
8
|
+
"Fixed commands are used (not AI) so replayTo starts from a clean initial state.",
|
|
9
|
+
"For each entry: call replayTo(replay, recordedAtTick, ctx).",
|
|
10
|
+
"The returned world.tick must equal expectedWorldTick.",
|
|
11
|
+
"hashWorldState(world) must equal hashHex."
|
|
12
|
+
],
|
|
13
|
+
"replayJson": "{\"initialState\":{\"tick\":0,\"seed\":42,\"entities\":[{\"id\":1,\"teamId\":1,\"attributes\":{\"morphology\":{\"stature_m\":17400,\"mass_kg\":74977,\"actuatorMass_kg\":30125,\"actuatorScale\":9946,\"structureScale\":20000,\"reachScale\":10233},\"performance\":{\"peakForce_N\":165802,\"peakPower_W\":3600,\"continuousPower_W\":128,\"reserveEnergy_J\":15800,\"conversionEfficiency\":8651},\"control\":{\"controlQuality\":6848,\"reactionTime_s\":2329,\"stability\":6855,\"fineControl\":7624},\"resilience\":{\"surfaceIntegrity\":9459,\"bulkIntegrity\":9437,\"structureIntegrity\":10163,\"distressTolerance\":4629,\"shockTolerance\":5169,\"concussionTolerance\":5526,\"heatTolerance\":4758,\"coldTolerance\":4505,\"fatigueRate\":9668,\"recoveryRate\":10195},\"perception\":{\"visionRange_m\":2000000,\"visionArcDeg\":120,\"halfArcCosQ\":5000,\"hearingRange_m\":500000,\"decisionLatency_s\":5000,\"attentionDepth\":4,\"threatHorizon_m\":400000},\"cognition\":{\"linguistic\":6500,\"logicalMathematical\":6000,\"spatial\":6000,\"bodilyKinesthetic\":6000,\"musical\":5000,\"interpersonal\":6000,\"intrapersonal\":5500,\"naturalist\":5000,\"interSpecies\":3500}},\"energy\":{\"reserveEnergy_J\":15800,\"fatigue\":0},\"loadout\":{\"items\":[{\"id\":\"wpn_longsword\",\"kind\":\"weapon\",\"name\":\"Longsword\",\"mass_kg\":1500,\"bulk\":15000,\"reach_m\":9000,\"handedness\":\"twoHand\",\"momentArm_m\":5500,\"handlingMul\":10500,\"strikeEffectiveMassFrac\":1500,\"strikeSpeedMul\":10000,\"readyTime_s\":7500,\"damage\":{\"surfaceFrac\":3500,\"internalFrac\":4500,\"structuralFrac\":2000,\"bleedFactor\":7000,\"penetrationBias\":4000}}]},\"traits\":[],\"position_m\":{\"x\":-5000,\"y\":0,\"z\":0},\"velocity_mps\":{\"x\":0,\"y\":0,\"z\":0},\"intent\":{\"move\":{\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"},\"defence\":{\"mode\":\"none\",\"intensity\":0},\"prone\":false},\"action\":{\"attackCooldownTicks\":0,\"defenceCooldownTicks\":0,\"grappleCooldownTicks\":0,\"facingDirQ\":{\"x\":10000,\"y\":0,\"z\":0},\"weaponBindPartnerId\":0,\"weaponBindTicks\":0,\"swingMomentumQ\":0,\"shootCooldownTicks\":0,\"aimTicks\":0,\"aimTargetId\":0},\"condition\":{\"onFire\":0,\"corrosiveExposure\":0,\"radiation\":0,\"electricalOverload\":0,\"suffocation\":0,\"stunned\":0,\"prone\":false,\"pinned\":false,\"standBlockedTicks\":0,\"unconsciousTicks\":0,\"suppressedTicks\":0,\"blindTicks\":0,\"fearQ\":0,\"suppressionFearMul\":10000,\"recentAllyDeaths\":0,\"lastAllyDeathTick\":-1,\"surrendered\":false,\"rallyCooldownTicks\":0},\"injury\":{\"byRegion\":{\"head\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"torso\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0}},\"fluidLoss\":0,\"shock\":0,\"consciousness\":10000,\"dead\":false,\"hemolymphLoss\":0},\"grapple\":{\"holdingTargetId\":0,\"heldByIds\":[],\"gripQ\":0,\"position\":\"standing\"},\"ai\":{\"focusTargetId\":0,\"retargetCooldownTicks\":0,\"decisionCooldownTicks\":0}},{\"id\":2,\"teamId\":2,\"attributes\":{\"morphology\":{\"stature_m\":17440,\"mass_kg\":74182,\"actuatorMass_kg\":29420,\"actuatorScale\":9904,\"structureScale\":20000,\"reachScale\":9895},\"performance\":{\"peakForce_N\":154468,\"peakPower_W\":3600,\"continuousPower_W\":129,\"reserveEnergy_J\":14502,\"conversionEfficiency\":8557},\"control\":{\"controlQuality\":7058,\"reactionTime_s\":2091,\"stability\":6517,\"fineControl\":7109},\"resilience\":{\"surfaceIntegrity\":9544,\"bulkIntegrity\":10338,\"structureIntegrity\":9742,\"distressTolerance\":5605,\"shockTolerance\":5235,\"concussionTolerance\":4836,\"heatTolerance\":5353,\"coldTolerance\":4988,\"fatigueRate\":9233,\"recoveryRate\":9064},\"perception\":{\"visionRange_m\":2000000,\"visionArcDeg\":120,\"halfArcCosQ\":5000,\"hearingRange_m\":500000,\"decisionLatency_s\":5000,\"attentionDepth\":4,\"threatHorizon_m\":400000},\"cognition\":{\"linguistic\":6500,\"logicalMathematical\":6000,\"spatial\":6000,\"bodilyKinesthetic\":6000,\"musical\":5000,\"interpersonal\":6000,\"intrapersonal\":5500,\"naturalist\":5000,\"interSpecies\":3500}},\"energy\":{\"reserveEnergy_J\":14502,\"fatigue\":0},\"loadout\":{\"items\":[{\"id\":\"wpn_longsword\",\"kind\":\"weapon\",\"name\":\"Longsword\",\"mass_kg\":1500,\"bulk\":15000,\"reach_m\":9000,\"handedness\":\"twoHand\",\"momentArm_m\":5500,\"handlingMul\":10500,\"strikeEffectiveMassFrac\":1500,\"strikeSpeedMul\":10000,\"readyTime_s\":7500,\"damage\":{\"surfaceFrac\":3500,\"internalFrac\":4500,\"structuralFrac\":2000,\"bleedFactor\":7000,\"penetrationBias\":4000}}]},\"traits\":[],\"position_m\":{\"x\":5000,\"y\":0,\"z\":0},\"velocity_mps\":{\"x\":0,\"y\":0,\"z\":0},\"intent\":{\"move\":{\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"},\"defence\":{\"mode\":\"none\",\"intensity\":0},\"prone\":false},\"action\":{\"attackCooldownTicks\":0,\"defenceCooldownTicks\":0,\"grappleCooldownTicks\":0,\"facingDirQ\":{\"x\":10000,\"y\":0,\"z\":0},\"weaponBindPartnerId\":0,\"weaponBindTicks\":0,\"swingMomentumQ\":0,\"shootCooldownTicks\":0,\"aimTicks\":0,\"aimTargetId\":0},\"condition\":{\"onFire\":0,\"corrosiveExposure\":0,\"radiation\":0,\"electricalOverload\":0,\"suffocation\":0,\"stunned\":0,\"prone\":false,\"pinned\":false,\"standBlockedTicks\":0,\"unconsciousTicks\":0,\"suppressedTicks\":0,\"blindTicks\":0,\"fearQ\":0,\"suppressionFearMul\":10000,\"recentAllyDeaths\":0,\"lastAllyDeathTick\":-1,\"surrendered\":false,\"rallyCooldownTicks\":0},\"injury\":{\"byRegion\":{\"head\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"torso\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightArm\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"leftLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0},\"rightLeg\":{\"surfaceDamage\":0,\"internalDamage\":0,\"structuralDamage\":0,\"bleedingRate\":0,\"fractured\":false,\"infectedTick\":-1,\"bleedDuration_ticks\":0,\"permanentDamage\":0}},\"fluidLoss\":0,\"shock\":0,\"consciousness\":10000,\"dead\":false,\"hemolymphLoss\":0},\"grapple\":{\"holdingTargetId\":0,\"heldByIds\":[],\"gripQ\":0,\"position\":\"standing\"},\"ai\":{\"focusTargetId\":0,\"retargetCooldownTicks\":0,\"decisionCooldownTicks\":0}}]},\"frames\":[{\"tick\":0,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":1,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":2,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":3,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":4,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":5,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":6,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":7,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":8,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]},{\"tick\":9,\"commands\":[[1,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]],[2,[{\"kind\":\"move\",\"dir\":{\"x\":0,\"y\":0,\"z\":0},\"intensity\":0,\"mode\":\"walk\"}]]]}]}",
|
|
14
|
+
"hashTrace": [
|
|
15
|
+
{
|
|
16
|
+
"recordedAtTick": 0,
|
|
17
|
+
"expectedWorldTick": 1,
|
|
18
|
+
"hashHex": "0xfdf533148d08f459"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"recordedAtTick": 1,
|
|
22
|
+
"expectedWorldTick": 2,
|
|
23
|
+
"hashHex": "0xfdf233148d06be82"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"recordedAtTick": 2,
|
|
27
|
+
"expectedWorldTick": 3,
|
|
28
|
+
"hashHex": "0xfdee2f148d02cedf"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"recordedAtTick": 3,
|
|
32
|
+
"expectedWorldTick": 4,
|
|
33
|
+
"hashHex": "0xfe062f148d1760c0"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"recordedAtTick": 4,
|
|
37
|
+
"expectedWorldTick": 5,
|
|
38
|
+
"hashHex": "0xfe0333148d1531b5"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"recordedAtTick": 5,
|
|
42
|
+
"expectedWorldTick": 6,
|
|
43
|
+
"hashHex": "0xfdff33148d1148de"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"recordedAtTick": 6,
|
|
47
|
+
"expectedWorldTick": 7,
|
|
48
|
+
"hashHex": "0xfdfc2f148d0f0c3b"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"recordedAtTick": 7,
|
|
52
|
+
"expectedWorldTick": 8,
|
|
53
|
+
"hashHex": "0xfddd2f148cf45bac"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"recordedAtTick": 8,
|
|
57
|
+
"expectedWorldTick": 9,
|
|
58
|
+
"hashHex": "0xfdda33148cf22ca1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"recordedAtTick": 9,
|
|
62
|
+
"expectedWorldTick": 10,
|
|
63
|
+
"hashHex": "0x8fe5f3eba59e0b75"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
66
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "conformance/v1",
|
|
3
|
+
"id": "state-hash-02",
|
|
4
|
+
"description": "Given a canonical WorldState, hashWorldState must return the specified hex value.",
|
|
5
|
+
"kind": "state-hash",
|
|
6
|
+
"commandSource": "lineInfantry",
|
|
7
|
+
"notes": [
|
|
8
|
+
"seed=42; two humanoid entities with lineInfantry AI commands.",
|
|
9
|
+
"captures a longer deterministic timeline to guard against kernel regressions.",
|
|
10
|
+
"x/y/z positions and injury state changes are included through hashWorldState."
|
|
11
|
+
],
|
|
12
|
+
"cases": [
|
|
13
|
+
{
|
|
14
|
+
"tick": 0,
|
|
15
|
+
"description": "World state at tick 0",
|
|
16
|
+
"hashHex": "0xfdf82f148d0b2364"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"tick": 1,
|
|
20
|
+
"description": "World state at tick 1",
|
|
21
|
+
"hashHex": "0x94e31245fae65074"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"tick": 5,
|
|
25
|
+
"description": "World state at tick 5",
|
|
26
|
+
"hashHex": "0xd5e1600766daf978"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"tick": 10,
|
|
30
|
+
"description": "World state at tick 10",
|
|
31
|
+
"hashHex": "0x49d767bfed374445"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"tick": 20,
|
|
35
|
+
"description": "World state at tick 20",
|
|
36
|
+
"hashHex": "0xacb04548fd68a7fb"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "conformance/v1",
|
|
3
|
-
"id": "state-hash-01",
|
|
4
|
-
"description": "Given a canonical WorldState, hashWorldState must return the specified hex value.",
|
|
5
|
-
"kind": "state-hash",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
{
|
|
2
|
+
"version": "conformance/v1",
|
|
3
|
+
"id": "state-hash-01",
|
|
4
|
+
"description": "Given a canonical WorldState, hashWorldState must return the specified hex value.",
|
|
5
|
+
"kind": "state-hash",
|
|
6
|
+
"commandSource": "idle",
|
|
7
|
+
"notes": [
|
|
8
|
+
"seed=42; two humanoid entities at ±0.5 m with chainmail and longsword.",
|
|
9
|
+
"hash is FNV-64 over canonical JSON (sorted keys, Map→sorted entries).",
|
|
10
|
+
"A conforming implementation must produce identical hashes."
|
|
11
|
+
],
|
|
12
|
+
"cases": [
|
|
13
|
+
{
|
|
14
|
+
"tick": 0,
|
|
15
|
+
"description": "World state at tick 0",
|
|
16
|
+
"hashHex": "0xfdf82f148d0b2364"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"tick": 1,
|
|
20
|
+
"description": "World state at tick 1",
|
|
21
|
+
"hashHex": "0xfdf533148d08f459"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
23
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Discriminated union of all supported fixture kinds. */
|
|
2
|
-
export type FixtureKind = "state-hash" | "replay-parity" | "command-round-trip" | "bridge-snapshot" | "lockstep-sequence";
|
|
2
|
+
export type FixtureKind = "state-hash" | "phase-order" | "replay-parity" | "command-round-trip" | "bridge-snapshot" | "lockstep-sequence";
|
|
3
3
|
/** Version identifier embedded in every fixture file. */
|
|
4
4
|
export declare const CONFORMANCE_VERSION: "conformance/v1";
|
|
5
5
|
/** Common header shared by all conformance fixtures. */
|
|
@@ -18,6 +18,7 @@ export interface ConformanceFixtureHeader {
|
|
|
18
18
|
/** State-hash fixture: given an initial world, hashWorldState must return a known hex. */
|
|
19
19
|
export interface StateHashFixture extends ConformanceFixtureHeader {
|
|
20
20
|
kind: "state-hash";
|
|
21
|
+
commandSource: "idle" | "lineInfantry";
|
|
21
22
|
cases: Array<{
|
|
22
23
|
tick: number;
|
|
23
24
|
description: string;
|
|
@@ -25,6 +26,11 @@ export interface StateHashFixture extends ConformanceFixtureHeader {
|
|
|
25
26
|
hashHex: string;
|
|
26
27
|
}>;
|
|
27
28
|
}
|
|
29
|
+
/** Phase-order fixture: world-step phases must execute in this exact order. */
|
|
30
|
+
export interface PhaseOrderFixture extends ConformanceFixtureHeader {
|
|
31
|
+
kind: "phase-order";
|
|
32
|
+
phases: string[];
|
|
33
|
+
}
|
|
28
34
|
/** Replay-parity fixture: re-simulating a replay must produce the same per-tick hashes. */
|
|
29
35
|
export interface ReplayParityFixture extends ConformanceFixtureHeader {
|
|
30
36
|
kind: "replay-parity";
|
|
@@ -85,7 +91,7 @@ export interface LockstepSequenceFixture extends ConformanceFixtureHeader {
|
|
|
85
91
|
}>;
|
|
86
92
|
}
|
|
87
93
|
/** Union of all fixture types. */
|
|
88
|
-
export type ConformanceFixture = StateHashFixture | ReplayParityFixture | CommandRoundTripFixture | BridgeSnapshotFixture | LockstepSequenceFixture;
|
|
94
|
+
export type ConformanceFixture = StateHashFixture | PhaseOrderFixture | ReplayParityFixture | CommandRoundTripFixture | BridgeSnapshotFixture | LockstepSequenceFixture;
|
|
89
95
|
/** Result of running a single fixture. */
|
|
90
96
|
export interface ConformanceResult {
|
|
91
97
|
id: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WorldState } from "../sim/world.js";
|
|
2
|
+
import type { ContentPack, ContentTerrain } from "./types.js";
|
|
3
|
+
export interface ContentRegistryState {
|
|
4
|
+
packs: Map<string, ContentPack>;
|
|
5
|
+
archetypes: Map<string, unknown>;
|
|
6
|
+
weapons: Map<string, unknown>;
|
|
7
|
+
armour: Map<string, unknown>;
|
|
8
|
+
terrain: Map<string, ContentTerrain>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Apply a content pack into a live world.
|
|
12
|
+
*
|
|
13
|
+
* Example:
|
|
14
|
+
* ```ts
|
|
15
|
+
* const pack = await loadContentPack("./my-hero.json");
|
|
16
|
+
* world = applyContentPack(world, pack);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyContentPack(world: WorldState, pack: ContentPack): WorldState;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { registerArchetype, registerArmour, registerWeapon } from "../catalog.js";
|
|
2
|
+
import { ANANKE_ENGINE_VERSION, semverSatisfies } from "../content-pack.js";
|
|
3
|
+
import { registerWorldArchetype, registerWorldItem } from "../world-factory.js";
|
|
4
|
+
function ensureRegistry(world) {
|
|
5
|
+
const runtime = (world.runtimeState ?? {});
|
|
6
|
+
const existing = runtime.contentRegistry;
|
|
7
|
+
if (existing)
|
|
8
|
+
return existing;
|
|
9
|
+
const created = {
|
|
10
|
+
packs: new Map(),
|
|
11
|
+
archetypes: new Map(),
|
|
12
|
+
weapons: new Map(),
|
|
13
|
+
armour: new Map(),
|
|
14
|
+
terrain: new Map(),
|
|
15
|
+
};
|
|
16
|
+
runtime.contentRegistry = created;
|
|
17
|
+
world.runtimeState = runtime;
|
|
18
|
+
return created;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Apply a content pack into a live world.
|
|
22
|
+
*
|
|
23
|
+
* Example:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const pack = await loadContentPack("./my-hero.json");
|
|
26
|
+
* world = applyContentPack(world, pack);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function applyContentPack(world, pack) {
|
|
30
|
+
const compatRange = pack.registry?.compatRange;
|
|
31
|
+
if (typeof compatRange === "string" && !semverSatisfies(ANANKE_ENGINE_VERSION, compatRange)) {
|
|
32
|
+
throw new Error(`Pack ${pack.name}@${pack.version} is incompatible with engine ${ANANKE_ENGINE_VERSION}; expected ${compatRange}`);
|
|
33
|
+
}
|
|
34
|
+
const nextWorld = {
|
|
35
|
+
...world,
|
|
36
|
+
runtimeState: { ...(world.runtimeState ?? {}) },
|
|
37
|
+
};
|
|
38
|
+
const registry = ensureRegistry(nextWorld);
|
|
39
|
+
const packId = `${pack.name}@${pack.version}`;
|
|
40
|
+
// hot-reload: if pack id already exists, overwrite registry entries.
|
|
41
|
+
registry.packs.set(packId, pack);
|
|
42
|
+
for (const archetype of pack.archetypes ?? []) {
|
|
43
|
+
const parsed = registerArchetype(archetype);
|
|
44
|
+
registerWorldArchetype(archetype.id, parsed);
|
|
45
|
+
registry.archetypes.set(archetype.id, archetype);
|
|
46
|
+
}
|
|
47
|
+
for (const weapon of pack.weapons ?? []) {
|
|
48
|
+
const parsed = registerWeapon(weapon);
|
|
49
|
+
registerWorldItem(weapon.id, parsed);
|
|
50
|
+
registry.weapons.set(weapon.id, weapon);
|
|
51
|
+
}
|
|
52
|
+
for (const armour of pack.armour ?? []) {
|
|
53
|
+
const parsed = registerArmour(armour);
|
|
54
|
+
registerWorldItem(armour.id, parsed);
|
|
55
|
+
registry.armour.set(armour.id, armour);
|
|
56
|
+
}
|
|
57
|
+
for (const terrain of pack.terrain ?? []) {
|
|
58
|
+
registry.terrain.set(terrain.id, terrain);
|
|
59
|
+
}
|
|
60
|
+
return nextWorld;
|
|
61
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
const PACK_CACHE = new Map();
|
|
4
|
+
const SCHEMA_CACHE = new Map();
|
|
5
|
+
function buildJsonParseError(raw, err) {
|
|
6
|
+
const posMatch = /position\s+(\d+)/i.exec(err.message);
|
|
7
|
+
if (!posMatch)
|
|
8
|
+
return err;
|
|
9
|
+
const pos = Number(posMatch[1]);
|
|
10
|
+
const prefix = raw.slice(0, Math.max(0, pos));
|
|
11
|
+
const line = prefix.split("\n").length;
|
|
12
|
+
const col = pos - prefix.lastIndexOf("\n");
|
|
13
|
+
return new Error(`Invalid JSON at line ${line}, column ${col}: ${err.message}`);
|
|
14
|
+
}
|
|
15
|
+
function getByPointer(root, pointer) {
|
|
16
|
+
if (pointer === "" || pointer === "#")
|
|
17
|
+
return root;
|
|
18
|
+
const segments = pointer.replace(/^#\//, "").split("/").map(p => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
19
|
+
let cur = root;
|
|
20
|
+
for (const seg of segments) {
|
|
21
|
+
if (typeof cur !== "object" || cur === null || !(seg in cur)) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
cur = cur[seg];
|
|
25
|
+
}
|
|
26
|
+
return cur;
|
|
27
|
+
}
|
|
28
|
+
async function loadSchemaRef(ref, basePath) {
|
|
29
|
+
if (ref.startsWith("#"))
|
|
30
|
+
return undefined;
|
|
31
|
+
const [docUrlPart] = ref.split("#");
|
|
32
|
+
const docUrl = docUrlPart ?? "";
|
|
33
|
+
if (SCHEMA_CACHE.has(docUrl))
|
|
34
|
+
return SCHEMA_CACHE.get(docUrl);
|
|
35
|
+
let schemaDoc;
|
|
36
|
+
if (docUrl.startsWith("https://")) {
|
|
37
|
+
const response = await fetch(docUrl);
|
|
38
|
+
if (!response.ok)
|
|
39
|
+
throw new Error(`Failed to fetch schema ref ${docUrl}: HTTP ${response.status}`);
|
|
40
|
+
schemaDoc = await response.json();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const schemaPath = isAbsolute(docUrl) ? docUrl : resolve(dirname(basePath), docUrl);
|
|
44
|
+
schemaDoc = JSON.parse(await readFile(schemaPath, "utf8"));
|
|
45
|
+
}
|
|
46
|
+
SCHEMA_CACHE.set(docUrl, schemaDoc);
|
|
47
|
+
return schemaDoc;
|
|
48
|
+
}
|
|
49
|
+
async function resolveRefs(schema, basePath, rootSchema) {
|
|
50
|
+
if (Array.isArray(schema)) {
|
|
51
|
+
return Promise.all(schema.map(item => resolveRefs(item, basePath, rootSchema ?? schema)));
|
|
52
|
+
}
|
|
53
|
+
if (typeof schema !== "object" || schema === null)
|
|
54
|
+
return schema;
|
|
55
|
+
const record = schema;
|
|
56
|
+
if (typeof record["$ref"] === "string") {
|
|
57
|
+
const ref = record["$ref"];
|
|
58
|
+
if (ref.startsWith("#")) {
|
|
59
|
+
const target = getByPointer(rootSchema ?? schema, ref);
|
|
60
|
+
if (target === undefined)
|
|
61
|
+
throw new Error(`Unresolvable local schema ref: ${ref}`);
|
|
62
|
+
return resolveRefs(target, basePath, rootSchema ?? schema);
|
|
63
|
+
}
|
|
64
|
+
const external = await loadSchemaRef(ref, basePath);
|
|
65
|
+
const pointer = ref.includes("#") ? `#${ref.split("#")[1]}` : "#";
|
|
66
|
+
const target = getByPointer(external, pointer);
|
|
67
|
+
if (target === undefined)
|
|
68
|
+
throw new Error(`Unresolvable external schema ref: ${ref}`);
|
|
69
|
+
return resolveRefs(target, basePath, external);
|
|
70
|
+
}
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const [k, v] of Object.entries(record)) {
|
|
73
|
+
out[k] = await resolveRefs(v, basePath, rootSchema ?? schema);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function validateAgainstSchema(data, schema, path = "$") {
|
|
78
|
+
const errors = [];
|
|
79
|
+
if (typeof schema !== "object" || schema === null)
|
|
80
|
+
return errors;
|
|
81
|
+
const s = schema;
|
|
82
|
+
const expectedType = s.type;
|
|
83
|
+
if (expectedType) {
|
|
84
|
+
const typeOkay = (expectedType === "array" && Array.isArray(data)) ||
|
|
85
|
+
(expectedType === "object" && typeof data === "object" && data !== null && !Array.isArray(data)) ||
|
|
86
|
+
(expectedType === "string" && typeof data === "string") ||
|
|
87
|
+
(expectedType === "number" && typeof data === "number") ||
|
|
88
|
+
(expectedType === "integer" && Number.isInteger(data)) ||
|
|
89
|
+
(expectedType === "boolean" && typeof data === "boolean");
|
|
90
|
+
if (!typeOkay) {
|
|
91
|
+
errors.push({ path, message: `expected ${expectedType}` });
|
|
92
|
+
return errors;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (typeof data === "string") {
|
|
96
|
+
if (typeof s.minLength === "number" && data.length < s.minLength)
|
|
97
|
+
errors.push({ path, message: `minLength ${s.minLength}` });
|
|
98
|
+
if (typeof s.pattern === "string" && !(new RegExp(s.pattern).test(data)))
|
|
99
|
+
errors.push({ path, message: `must match pattern ${s.pattern}` });
|
|
100
|
+
}
|
|
101
|
+
if (typeof data === "number") {
|
|
102
|
+
if (typeof s.minimum === "number" && data < s.minimum)
|
|
103
|
+
errors.push({ path, message: `must be >= ${s.minimum}` });
|
|
104
|
+
if (typeof s.maximum === "number" && data > s.maximum)
|
|
105
|
+
errors.push({ path, message: `must be <= ${s.maximum}` });
|
|
106
|
+
if (typeof s.exclusiveMinimum === "number" && data <= s.exclusiveMinimum)
|
|
107
|
+
errors.push({ path, message: `must be > ${s.exclusiveMinimum}` });
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(s.enum) && !s.enum.includes(data)) {
|
|
110
|
+
errors.push({ path, message: `must be one of ${s.enum.join(", ")}` });
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(data)) {
|
|
113
|
+
if (typeof s.minItems === "number" && data.length < s.minItems)
|
|
114
|
+
errors.push({ path, message: `minItems ${s.minItems}` });
|
|
115
|
+
if (s.items !== undefined) {
|
|
116
|
+
data.forEach((item, idx) => {
|
|
117
|
+
errors.push(...validateAgainstSchema(item, s.items, `${path}[${idx}]`));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
122
|
+
const o = data;
|
|
123
|
+
const required = Array.isArray(s.required) ? s.required : [];
|
|
124
|
+
for (const req of required) {
|
|
125
|
+
if (!(req in o))
|
|
126
|
+
errors.push({ path: `${path}.${req}`, message: "is required" });
|
|
127
|
+
}
|
|
128
|
+
const properties = (s.properties ?? {});
|
|
129
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
130
|
+
if (key in o)
|
|
131
|
+
errors.push(...validateAgainstSchema(o[key], propSchema, `${path}.${key}`));
|
|
132
|
+
}
|
|
133
|
+
const additional = s.additionalProperties;
|
|
134
|
+
if (additional && typeof additional === "object") {
|
|
135
|
+
for (const [key, value] of Object.entries(o)) {
|
|
136
|
+
if (!(key in properties))
|
|
137
|
+
errors.push(...validateAgainstSchema(value, additional, `${path}.${key}`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return errors;
|
|
142
|
+
}
|
|
143
|
+
export async function loadContentPack(path) {
|
|
144
|
+
const absPath = resolve(path);
|
|
145
|
+
const cached = PACK_CACHE.get(absPath);
|
|
146
|
+
if (cached)
|
|
147
|
+
return cached;
|
|
148
|
+
const raw = await readFile(absPath, "utf8");
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(raw);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
throw buildJsonParseError(raw, err);
|
|
155
|
+
}
|
|
156
|
+
const schemaPath = resolve(process.cwd(), "schema/content-pack.schema.json");
|
|
157
|
+
const schemaRaw = JSON.parse(await readFile(schemaPath, "utf8"));
|
|
158
|
+
const resolvedSchema = await resolveRefs(schemaRaw, schemaPath, schemaRaw);
|
|
159
|
+
const errors = validateAgainstSchema(parsed, resolvedSchema);
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
const lines = errors.map(e => `${e.path}: ${e.message}`).join("\n");
|
|
162
|
+
throw new Error(`Content pack schema validation failed:\n${lines}`);
|
|
163
|
+
}
|
|
164
|
+
const pack = parsed;
|
|
165
|
+
PACK_CACHE.set(absPath, pack);
|
|
166
|
+
return pack;
|
|
167
|
+
}
|
|
168
|
+
export function clearContentPackCache() {
|
|
169
|
+
PACK_CACHE.clear();
|
|
170
|
+
}
|