@its-not-rocket-science/ananke 0.1.62 → 0.1.64

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 CHANGED
@@ -6,6 +6,46 @@ Versioning follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [0.1.64] — 2026-04-01
10
+
11
+ ### Added
12
+
13
+ - **PM-5 — Deterministic Conformance Suite (complete):**
14
+ - `conformance/` directory (published in package): 5 self-contained JSON fixture files that any third-party host SDK can use to verify deterministic compatibility with the reference TypeScript engine.
15
+ - `state-hash.json`: given a canonical `WorldState`, `hashWorldState` must return specific hex values at tick 0 and tick 1.
16
+ - `replay-parity.json`: re-simulating a recorded 10-tick replay with `replayTo` must reproduce the per-tick hash trace exactly. Uses `noMove` commands to avoid AI-state side-effect divergence.
17
+ - `command-round-trip.json`: verifies `SCALE` constants and `CommandMap` JSON serialisation round-trips without loss for all 4 command kinds.
18
+ - `bridge-snapshot.json`: `serializeBridgeFrame` must produce a `BridgeFrame` with the correct schema, tick, entity count, and entity IDs.
19
+ - `lockstep-sequence.json`: stepping the simulation 20 ticks with `lineInfantry` AI must produce matching entity positions, dead flags, shock values, and world-state hashes at each tick.
20
+ - `tools/generate-conformance-fixtures.ts`: reference fixture generator; run `npm run generate-conformance-fixtures` to regenerate after changes to the hash algorithm, `stepWorld`, or `serializeBridgeFrame`.
21
+ - `tools/conformance-runner.ts`: standalone runner with `--json` (machine-readable) and `--fixture=<kind>` (single fixture) flags; exits 0 on all-pass, 1 on any failure.
22
+ - `src/conformance.ts` (new subpath export `"./conformance"`): public TypeScript types for third-party runners — `FixtureKind`, `ConformanceFixtureHeader`, `StateHashFixture`, `ReplayParityFixture`, `CommandRoundTripFixture`, `BridgeSnapshotFixture`, `LockstepSequenceFixture`, `ConformanceResult`, `ConformanceSummary`, and `CONFORMANCE_VERSION`.
23
+ - npm scripts: `generate-conformance-fixtures`, `conformance-runner`.
24
+ - 0 new tests (5,569 total). Coverage: 97.11%/88.08%/95.82%/97.11%. Build: clean.
25
+
26
+ ---
27
+
28
+ ## [0.1.63] — 2026-04-01
29
+
30
+ ### Added
31
+
32
+ - **PM-4 — Release Discipline Dashboard (complete):**
33
+ - `tools/release-check.ts` (new): pre-release gate runner that executes 6 gates in sequence and produces two output artefacts:
34
+ 1. **Schema migration pass** — runs `test/schema-migration.test.ts` + `test/anatomy_schema.test.ts` via Vitest
35
+ 2. **Golden replay / fixture round-trip** — skips gracefully if `test/fixtures/` does not exist; runs fixture tests when present
36
+ 3. **Bridge contract type-check** — `tsc --noEmit -p tsconfig.build.json`; counts TypeScript errors
37
+ 4. **Benchmark regression** — delegates to `dist/tools/benchmark-check.js`; exit-code based
38
+ 5. **Emergent behaviour validation** — runs emergent-validation and parses PASS / PARTIAL PASS / FAIL from stdout
39
+ 6. **Module-index freshness** — re-generates `docs/module-index.md` and diffs against the committed version; warns if stale
40
+ - `docs/release-report.json` (auto-generated): structured JSON with timestamp, version, per-gate status, duration, summary, and detail strings.
41
+ - `docs/release-dashboard.md` (auto-generated): human-readable Markdown audit trail rendered from the JSON report. Includes verdict, gate table, and per-gate detail blocks.
42
+ - `--quick` flag: skips slow gates (benchmark, emergent validation) for fast local checks.
43
+ - Exit code: 0 = releasable (no failures, no warnings); 1 = not releasable.
44
+ - npm scripts: `release-check`, `release-check:quick`.
45
+ - 0 new tests (5,569 total). Coverage unchanged. Build: clean.
46
+
47
+ ---
48
+
9
49
  ## [0.1.62] — 2026-04-01
10
50
 
11
51
  ### Added
@@ -0,0 +1,44 @@
1
+ # Ananke — Conformance Fixtures
2
+
3
+ Test fixtures for verifying host-SDK determinism. Any implementation that
4
+ passes all fixtures is guaranteed to produce the same simulation state as the
5
+ reference TypeScript engine.
6
+
7
+ ## Fixture files
8
+
9
+ | File | Kind | What it tests |
10
+ |------|------|---------------|
11
+ | `state-hash.json` | `state-hash` | `hashWorldState` output for a known WorldState |
12
+ | `replay-parity.json` | `replay-parity` | Per-tick hash trace when re-simulating a recorded replay |
13
+ | `command-round-trip.json` | `command-round-trip` | CommandMap wire encoding and field semantics |
14
+ | `bridge-snapshot.json` | `bridge-snapshot` | `serializeBridgeFrame` output shape and invariants |
15
+ | `lockstep-sequence.json` | `lockstep-sequence` | Entity positions and shock at each tick of a 20-tick run |
16
+
17
+ ## Running the suite
18
+
19
+ ```bash
20
+ npm run build
21
+ npm run conformance-runner # TypeScript reference implementation
22
+ npm run conformance-runner -- --json # machine-readable output
23
+ ```
24
+
25
+ ## Integrating from a non-TypeScript host
26
+
27
+ 1. Load the fixture JSON.
28
+ 2. Reconstruct the initial `WorldState` from the fixture's `input` section.
29
+ 3. Step the simulation exactly as described.
30
+ 4. Compare your output against the `expected` / `snapshots` / `hashTrace` fields.
31
+ 5. A mismatch means your fixed-point arithmetic or RNG seeding diverges.
32
+
33
+ ## Fixture format version
34
+
35
+ All fixtures carry `"version": "conformance/v1"`. A breaking change in the
36
+ hash algorithm or wire format will bump to `v2` with a migration note.
37
+
38
+ ## Regenerating
39
+
40
+ ```bash
41
+ npm run build && npm run generate-conformance-fixtures
42
+ ```
43
+
44
+ Re-run after any change to `hashWorldState`, `stepWorld`, or `serializeBridgeFrame`.
@@ -0,0 +1,40 @@
1
+ {
2
+ "version": "conformance/v1",
3
+ "id": "bridge-snapshot-01",
4
+ "description": "serializeBridgeFrame must produce a BridgeFrame with these invariants.",
5
+ "kind": "bridge-snapshot",
6
+ "notes": [
7
+ "Check: frame.schema === 'ananke-bridge/v1'.",
8
+ "Check: frame.tick === 0.",
9
+ "Check: frame.entities.length === 2.",
10
+ "Check: entity positions match input WorldState positions.",
11
+ "The generatedAt timestamp is non-deterministic — do not compare it."
12
+ ],
13
+ "input": {
14
+ "seed": 42,
15
+ "tick": 0,
16
+ "entityCount": 2,
17
+ "entityPositions": [
18
+ {
19
+ "id": 1,
20
+ "x_m": -0.5,
21
+ "y_m": 0
22
+ },
23
+ {
24
+ "id": 2,
25
+ "x_m": 0.5,
26
+ "y_m": 0
27
+ }
28
+ ]
29
+ },
30
+ "expected": {
31
+ "schema": "ananke.bridge.frame.v1",
32
+ "tick": 0,
33
+ "entityCount": 2,
34
+ "scenarioId": "conformance-test",
35
+ "entityIds": [
36
+ 1,
37
+ 2
38
+ ]
39
+ }
40
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "version": "conformance/v1",
3
+ "id": "command-round-trip-01",
4
+ "description": "CommandMap entries must serialise to and from JSON without loss.",
5
+ "kind": "command-round-trip",
6
+ "notes": [
7
+ "CommandMap is Map<entityId, Command[]>.",
8
+ "For wire transport, serialise as [[entityId, commands[]], ...].",
9
+ "All numeric fields are fixed-point integers (SCALE.Q = 10000)."
10
+ ],
11
+ "scale": {
12
+ "Q": 10000,
13
+ "kg": 1000,
14
+ "m": 10000,
15
+ "mps": 10000
16
+ },
17
+ "commands": [
18
+ {
19
+ "entityId": 1,
20
+ "kind": "attack",
21
+ "mode": "strike",
22
+ "targetId": 2,
23
+ "intensity_Q": 10000,
24
+ "description": "Full-intensity strike at entity 2"
25
+ },
26
+ {
27
+ "entityId": 2,
28
+ "kind": "move",
29
+ "direction_x": 5000,
30
+ "direction_y": 0,
31
+ "direction_z": 0,
32
+ "mode": "walk",
33
+ "description": "Walk toward +x at 0.5 m/s"
34
+ },
35
+ {
36
+ "entityId": 1,
37
+ "kind": "defend",
38
+ "mode": "active",
39
+ "description": "Active defence stance"
40
+ },
41
+ {
42
+ "entityId": 2,
43
+ "kind": "idle",
44
+ "description": "No action this tick"
45
+ }
46
+ ]
47
+ }
@@ -0,0 +1,396 @@
1
+ {
2
+ "version": "conformance/v1",
3
+ "id": "lockstep-sequence-01",
4
+ "description": "Stepping the simulation must produce identical entity state at each tick.",
5
+ "kind": "lockstep-sequence",
6
+ "notes": [
7
+ "seed=42; 2 entities (lineInfantry AI); 20 ticks.",
8
+ "tractionCoeff = q(0.90) = 9000.",
9
+ "At each tick: verify hashWorldState === hashHex and entity fields match.",
10
+ "x_m values are rounded to 3 decimal places for cross-language comparison.",
11
+ "shock_Q is raw fixed-point (SCALE.Q = 10000); 10000 = 100% shock."
12
+ ],
13
+ "context": {
14
+ "tractionCoeff_Q": 9000
15
+ },
16
+ "snapshots": [
17
+ {
18
+ "tick": 0,
19
+ "hashHex": "0xec578e005fcbef34",
20
+ "entities": [
21
+ {
22
+ "id": 1,
23
+ "x_m": -0.5,
24
+ "dead": false,
25
+ "shock_Q": 0
26
+ },
27
+ {
28
+ "id": 2,
29
+ "x_m": 0.5,
30
+ "dead": false,
31
+ "shock_Q": 0
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "tick": 1,
37
+ "hashHex": "0x94e31245fae65074",
38
+ "entities": [
39
+ {
40
+ "id": 1,
41
+ "x_m": -0.478,
42
+ "dead": false,
43
+ "shock_Q": 0
44
+ },
45
+ {
46
+ "id": 2,
47
+ "x_m": 0.479,
48
+ "dead": false,
49
+ "shock_Q": 0
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "tick": 2,
55
+ "hashHex": "0x5f433b5815b27103",
56
+ "entities": [
57
+ {
58
+ "id": 1,
59
+ "x_m": -0.433,
60
+ "dead": false,
61
+ "shock_Q": 0
62
+ },
63
+ {
64
+ "id": 2,
65
+ "x_m": 0.437,
66
+ "dead": false,
67
+ "shock_Q": 0
68
+ }
69
+ ]
70
+ },
71
+ {
72
+ "tick": 3,
73
+ "hashHex": "0xacb3ed02702144c4",
74
+ "entities": [
75
+ {
76
+ "id": 1,
77
+ "x_m": -0.366,
78
+ "dead": false,
79
+ "shock_Q": 0
80
+ },
81
+ {
82
+ "id": 2,
83
+ "x_m": 0.373,
84
+ "dead": false,
85
+ "shock_Q": 0
86
+ }
87
+ ]
88
+ },
89
+ {
90
+ "tick": 4,
91
+ "hashHex": "0xd8cd8894e32b5d24",
92
+ "entities": [
93
+ {
94
+ "id": 1,
95
+ "x_m": -0.276,
96
+ "dead": false,
97
+ "shock_Q": 0
98
+ },
99
+ {
100
+ "id": 2,
101
+ "x_m": 0.289,
102
+ "dead": false,
103
+ "shock_Q": 0
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "tick": 5,
109
+ "hashHex": "0xd5e1600766daf978",
110
+ "entities": [
111
+ {
112
+ "id": 1,
113
+ "x_m": -0.164,
114
+ "dead": false,
115
+ "shock_Q": 0
116
+ },
117
+ {
118
+ "id": 2,
119
+ "x_m": 0.183,
120
+ "dead": false,
121
+ "shock_Q": 0
122
+ }
123
+ ]
124
+ },
125
+ {
126
+ "tick": 6,
127
+ "hashHex": "0xf775238c6b6c2339",
128
+ "entities": [
129
+ {
130
+ "id": 1,
131
+ "x_m": -0.049,
132
+ "dead": false,
133
+ "shock_Q": 0
134
+ },
135
+ {
136
+ "id": 2,
137
+ "x_m": 0.076,
138
+ "dead": false,
139
+ "shock_Q": 0
140
+ }
141
+ ]
142
+ },
143
+ {
144
+ "tick": 7,
145
+ "hashHex": "0xa4adf7d41c67f090",
146
+ "entities": [
147
+ {
148
+ "id": 1,
149
+ "x_m": 0.031,
150
+ "dead": false,
151
+ "shock_Q": 0
152
+ },
153
+ {
154
+ "id": 2,
155
+ "x_m": 0.004,
156
+ "dead": false,
157
+ "shock_Q": 0
158
+ }
159
+ ]
160
+ },
161
+ {
162
+ "tick": 8,
163
+ "hashHex": "0x5f39532dfa0cf053",
164
+ "entities": [
165
+ {
166
+ "id": 1,
167
+ "x_m": 0.168,
168
+ "dead": false,
169
+ "shock_Q": 0
170
+ },
171
+ {
172
+ "id": 2,
173
+ "x_m": -0.131,
174
+ "dead": false,
175
+ "shock_Q": 0
176
+ }
177
+ ]
178
+ },
179
+ {
180
+ "tick": 9,
181
+ "hashHex": "0xa87941a6b840c6d7",
182
+ "entities": [
183
+ {
184
+ "id": 1,
185
+ "x_m": 0.309,
186
+ "dead": false,
187
+ "shock_Q": 0
188
+ },
189
+ {
190
+ "id": 2,
191
+ "x_m": -0.272,
192
+ "dead": false,
193
+ "shock_Q": 0
194
+ }
195
+ ]
196
+ },
197
+ {
198
+ "tick": 10,
199
+ "hashHex": "0x49d767bfed374445",
200
+ "entities": [
201
+ {
202
+ "id": 1,
203
+ "x_m": 0.429,
204
+ "dead": false,
205
+ "shock_Q": 0
206
+ },
207
+ {
208
+ "id": 2,
209
+ "x_m": -0.391,
210
+ "dead": false,
211
+ "shock_Q": 0
212
+ }
213
+ ]
214
+ },
215
+ {
216
+ "tick": 11,
217
+ "hashHex": "0xce74d9688b813796",
218
+ "entities": [
219
+ {
220
+ "id": 1,
221
+ "x_m": 0.526,
222
+ "dead": false,
223
+ "shock_Q": 0
224
+ },
225
+ {
226
+ "id": 2,
227
+ "x_m": -0.49,
228
+ "dead": false,
229
+ "shock_Q": 0
230
+ }
231
+ ]
232
+ },
233
+ {
234
+ "tick": 12,
235
+ "hashHex": "0x1efbc95cd539db56",
236
+ "entities": [
237
+ {
238
+ "id": 1,
239
+ "x_m": 0.6,
240
+ "dead": false,
241
+ "shock_Q": 0
242
+ },
243
+ {
244
+ "id": 2,
245
+ "x_m": -0.567,
246
+ "dead": false,
247
+ "shock_Q": 0
248
+ }
249
+ ]
250
+ },
251
+ {
252
+ "tick": 13,
253
+ "hashHex": "0xd4f1d44a8c11b01e",
254
+ "entities": [
255
+ {
256
+ "id": 1,
257
+ "x_m": 0.652,
258
+ "dead": false,
259
+ "shock_Q": 0
260
+ },
261
+ {
262
+ "id": 2,
263
+ "x_m": -0.623,
264
+ "dead": false,
265
+ "shock_Q": 0
266
+ }
267
+ ]
268
+ },
269
+ {
270
+ "tick": 14,
271
+ "hashHex": "0xc9fd237324abe444",
272
+ "entities": [
273
+ {
274
+ "id": 1,
275
+ "x_m": 0.682,
276
+ "dead": false,
277
+ "shock_Q": 0
278
+ },
279
+ {
280
+ "id": 2,
281
+ "x_m": -0.659,
282
+ "dead": false,
283
+ "shock_Q": 0
284
+ }
285
+ ]
286
+ },
287
+ {
288
+ "tick": 15,
289
+ "hashHex": "0xaf66c06fb25eac2d",
290
+ "entities": [
291
+ {
292
+ "id": 1,
293
+ "x_m": 0.689,
294
+ "dead": false,
295
+ "shock_Q": 0
296
+ },
297
+ {
298
+ "id": 2,
299
+ "x_m": -0.672,
300
+ "dead": false,
301
+ "shock_Q": 0
302
+ }
303
+ ]
304
+ },
305
+ {
306
+ "tick": 16,
307
+ "hashHex": "0xc6a0cb965ff544d1",
308
+ "entities": [
309
+ {
310
+ "id": 1,
311
+ "x_m": 0.689,
312
+ "dead": false,
313
+ "shock_Q": 0
314
+ },
315
+ {
316
+ "id": 2,
317
+ "x_m": -0.672,
318
+ "dead": false,
319
+ "shock_Q": 0
320
+ }
321
+ ]
322
+ },
323
+ {
324
+ "tick": 17,
325
+ "hashHex": "0xc067905d49583c8a",
326
+ "entities": [
327
+ {
328
+ "id": 1,
329
+ "x_m": 0.689,
330
+ "dead": false,
331
+ "shock_Q": 0
332
+ },
333
+ {
334
+ "id": 2,
335
+ "x_m": -0.672,
336
+ "dead": false,
337
+ "shock_Q": 0
338
+ }
339
+ ]
340
+ },
341
+ {
342
+ "tick": 18,
343
+ "hashHex": "0x8e104127ec08a2c7",
344
+ "entities": [
345
+ {
346
+ "id": 1,
347
+ "x_m": 0.689,
348
+ "dead": false,
349
+ "shock_Q": 0
350
+ },
351
+ {
352
+ "id": 2,
353
+ "x_m": -0.672,
354
+ "dead": false,
355
+ "shock_Q": 0
356
+ }
357
+ ]
358
+ },
359
+ {
360
+ "tick": 19,
361
+ "hashHex": "0xb3468a0cce37d1ea",
362
+ "entities": [
363
+ {
364
+ "id": 1,
365
+ "x_m": 0.667,
366
+ "dead": false,
367
+ "shock_Q": 0
368
+ },
369
+ {
370
+ "id": 2,
371
+ "x_m": -0.651,
372
+ "dead": false,
373
+ "shock_Q": 0
374
+ }
375
+ ]
376
+ },
377
+ {
378
+ "tick": 20,
379
+ "hashHex": "0xacb04548fd68a7fb",
380
+ "entities": [
381
+ {
382
+ "id": 1,
383
+ "x_m": 0.622,
384
+ "dead": false,
385
+ "shock_Q": 0
386
+ },
387
+ {
388
+ "id": 2,
389
+ "x_m": -0.609,
390
+ "dead": false,
391
+ "shock_Q": 0
392
+ }
393
+ ]
394
+ }
395
+ ]
396
+ }
@@ -0,0 +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
+ ]
66
+ }
@@ -0,0 +1,23 @@
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
+ "notes": [
7
+ "seed=42; two humanoid entities at ±0.5 m with chainmail and longsword.",
8
+ "hash is FNV-64 over canonical JSON (sorted keys, Map→sorted entries).",
9
+ "A conforming implementation must produce identical hashes."
10
+ ],
11
+ "cases": [
12
+ {
13
+ "tick": 0,
14
+ "description": "Initial world state before any tick",
15
+ "hashHex": "0xec578e005fcbef34"
16
+ },
17
+ {
18
+ "tick": 1,
19
+ "description": "World state after one idle tick (noMove commands)",
20
+ "hashHex": "0xfdf533148d08f459"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,108 @@
1
+ /** Discriminated union of all supported fixture kinds. */
2
+ export type FixtureKind = "state-hash" | "replay-parity" | "command-round-trip" | "bridge-snapshot" | "lockstep-sequence";
3
+ /** Version identifier embedded in every fixture file. */
4
+ export declare const CONFORMANCE_VERSION: "conformance/v1";
5
+ /** Common header shared by all conformance fixtures. */
6
+ export interface ConformanceFixtureHeader {
7
+ /** Fixture format version — check this before reading. */
8
+ version: typeof CONFORMANCE_VERSION;
9
+ /** Unique fixture identifier. */
10
+ id: string;
11
+ /** Human-readable description of what this fixture tests. */
12
+ description: string;
13
+ /** Fixture kind — determines which runner handles it. */
14
+ kind: FixtureKind;
15
+ /** Implementation notes — read before writing a custom runner. */
16
+ notes: string[];
17
+ }
18
+ /** State-hash fixture: given an initial world, hashWorldState must return a known hex. */
19
+ export interface StateHashFixture extends ConformanceFixtureHeader {
20
+ kind: "state-hash";
21
+ cases: Array<{
22
+ tick: number;
23
+ description: string;
24
+ /** FNV-64 hash as a 0x-prefixed hex string. */
25
+ hashHex: string;
26
+ }>;
27
+ }
28
+ /** Replay-parity fixture: re-simulating a replay must produce the same per-tick hashes. */
29
+ export interface ReplayParityFixture extends ConformanceFixtureHeader {
30
+ kind: "replay-parity";
31
+ /** Serialised Replay JSON produced by ReplayRecorder. */
32
+ replayJson: string;
33
+ hashTrace: Array<{
34
+ tick: number;
35
+ hashHex: string;
36
+ }>;
37
+ }
38
+ /** Command round-trip fixture: verifies scale constants and JSON round-trips. */
39
+ export interface CommandRoundTripFixture extends ConformanceFixtureHeader {
40
+ kind: "command-round-trip";
41
+ scale: {
42
+ Q: number;
43
+ kg: number;
44
+ m: number;
45
+ mps: number;
46
+ };
47
+ commands: Array<Record<string, unknown>>;
48
+ }
49
+ /** Bridge-snapshot fixture: serializeBridgeFrame must produce this shape. */
50
+ export interface BridgeSnapshotFixture extends ConformanceFixtureHeader {
51
+ kind: "bridge-snapshot";
52
+ input: {
53
+ seed: number;
54
+ tick: number;
55
+ entityCount: number;
56
+ entityPositions: Array<{
57
+ id: number;
58
+ x_m: number;
59
+ y_m: number;
60
+ }>;
61
+ };
62
+ expected: {
63
+ schema: string;
64
+ tick: number;
65
+ entityCount: number;
66
+ scenarioId: string;
67
+ entityIds: number[];
68
+ };
69
+ }
70
+ /** Lockstep-sequence fixture: entity state at each tick of a deterministic run. */
71
+ export interface LockstepSequenceFixture extends ConformanceFixtureHeader {
72
+ kind: "lockstep-sequence";
73
+ context: {
74
+ tractionCoeff_Q: number;
75
+ };
76
+ snapshots: Array<{
77
+ tick: number;
78
+ hashHex: string;
79
+ entities: Array<{
80
+ id: number;
81
+ x_m: number;
82
+ dead: boolean;
83
+ shock_Q: number;
84
+ }>;
85
+ }>;
86
+ }
87
+ /** Union of all fixture types. */
88
+ export type ConformanceFixture = StateHashFixture | ReplayParityFixture | CommandRoundTripFixture | BridgeSnapshotFixture | LockstepSequenceFixture;
89
+ /** Result of running a single fixture. */
90
+ export interface ConformanceResult {
91
+ id: string;
92
+ kind: FixtureKind;
93
+ status: "pass" | "fail" | "skip" | "error";
94
+ checks: number;
95
+ failures: string[];
96
+ durationMs: number;
97
+ }
98
+ /** Summary of a full conformance run. */
99
+ export interface ConformanceSummary {
100
+ _generated: string;
101
+ passed: number;
102
+ failed: number;
103
+ errored: number;
104
+ skipped: number;
105
+ total: number;
106
+ conformant: boolean;
107
+ results: ConformanceResult[];
108
+ }
@@ -0,0 +1,13 @@
1
+ // src/conformance.ts
2
+ // PM-5: Conformance suite public API
3
+ //
4
+ // Exports types and helpers used by third-party host SDKs to verify
5
+ // deterministic compatibility with the reference Ananke engine.
6
+ //
7
+ // Usage:
8
+ // import type { ConformanceFixture, FixtureKind } from "@its-not-rocket-science/ananke/conformance";
9
+ //
10
+ // The full conformance fixtures live in conformance/*.json (published in the package).
11
+ // Run the suite with: npx ananke conformance (via pack-cli)
12
+ /** Version identifier embedded in every fixture file. */
13
+ export const CONFORMANCE_VERSION = "conformance/v1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@its-not-rocket-science/ananke",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "description": "Deterministic lockstep-friendly SI-units RPG/physics core (fixed-point TS)",
6
6
  "license": "MIT",
@@ -201,6 +201,10 @@
201
201
  "./netcode": {
202
202
  "import": "./dist/src/netcode.js",
203
203
  "types": "./dist/src/netcode.d.ts"
204
+ },
205
+ "./conformance": {
206
+ "import": "./dist/src/conformance.js",
207
+ "types": "./dist/src/conformance.d.ts"
204
208
  }
205
209
  },
206
210
  "workspaces": [
@@ -209,6 +213,7 @@
209
213
  "files": [
210
214
  "dist/src",
211
215
  "dist/as",
216
+ "conformance",
212
217
  "docs/project-overview.md",
213
218
  "docs/host-contract.md",
214
219
  "docs/integration-primer.md",
@@ -248,6 +253,8 @@
248
253
  "test:watch": "vitest",
249
254
  "test:coverage": "vitest run --coverage",
250
255
  "ci": "npm run build && npm run test:coverage && npm run check-boundaries",
256
+ "release-check": "node dist/tools/release-check.js",
257
+ "release-check:quick": "node dist/tools/release-check.js --quick",
251
258
  "check-boundaries": "node dist/tools/check-package-boundaries.js",
252
259
  "check-boundaries:strict": "node dist/tools/check-package-boundaries.js --strict",
253
260
  "extract-api": "node dist/tools/extract-api.js",
@@ -277,6 +284,8 @@
277
284
  "ref:species-lab:quick": "node dist/examples/reference/species-lab/index.js --quick",
278
285
  "generate-module-index": "node dist/tools/generate-module-index.js",
279
286
  "generate-recipes-matrix": "node dist/tools/generate-recipes-matrix.js",
287
+ "generate-conformance-fixtures": "node dist/tools/generate-conformance-fixtures.js",
288
+ "conformance-runner": "node dist/tools/conformance-runner.js",
280
289
  "pack": "node dist/tools/pack-cli.js pack",
281
290
  "generate-fixtures": "node dist/tools/generate-fixtures.js",
282
291
  "generate-zoo": "node dist/tools/generate-zoo.js",