@its-not-rocket-science/ananke 0.1.65 → 0.1.67

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,48 @@ Versioning follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [0.1.67] — 2026-04-01
10
+
11
+ ### Added
12
+
13
+ - **PM-8 — Scenario Corpus as Shared Benchmark and Pedagogy Asset (complete):**
14
+ - `corpus/` directory with 5 canonical deterministic scenarios, each carrying a `corpus.json` manifest (`version`, `id`, `title`, `tags`, `stabilityStatus`, `scenario`, `expectedOutputHash`, `performanceClass`, `replayFixture`, `bridgeExpected`).
15
+ - Tags: `tutorial`, `benchmark`, `validation`, `networking`, `bridge`, `content-pack`.
16
+ - Entries: `basic-duel` (tutorial, 30 ticks), `armoured-combat` (validation+content-pack, 50 ticks), `lockstep-replay` (networking, 10 ticks + replay.json fixture), `bridge-snapshot` (bridge, 0 ticks), `ai-benchmark` (benchmark, 20 ticks).
17
+ - `tools/generate-corpus.ts`: regenerates all corpus entries from scenario definitions; computes world-state hashes, replay fixtures, and bridge-frame invariants. Run after any change to `stepWorld`, `hashWorldState`, or equipment constants.
18
+ - `tools/verify-corpus.ts`: verifies all corpus entries against the live engine; checks world-state hash, replay parity (networking), and bridge frame shape (bridge). Supports `--id=<entry>` and `--json` flags.
19
+ - npm scripts: `generate-corpus`, `verify-corpus`.
20
+ - `corpus` added to `package.json` `files` array (shipped with the npm package).
21
+ - Links added to README "Further Reading" table and `docs/cookbook.md` "Further reading" table.
22
+ - 0 new tests (5,593 total). Coverage: 97.11%/88.07%/95.83%/97.11%. Build: clean.
23
+
24
+ ---
25
+
26
+ ## [0.1.66] — 2026-04-01
27
+
28
+ ### Added
29
+
30
+ - **PM-7 — API Deprecation Framework (complete):**
31
+ - `tools/audit-deprecations.ts` (new): scans all `src/` TypeScript files for `@deprecated` JSDoc tags and outputs a structured table of `{ symbol, file, line, since, removeAfter, replacement, overdue }`.
32
+ - `--json` flag: machine-readable JSON output with timestamp, engine version, and full entry list.
33
+ - `--check` flag: exits 1 if any symbol's `removeAfter` version ≤ current engine version (overdue).
34
+ - `prepublishOnly` now includes `npm run audit-deprecations -- --check`: `npm publish` fails if any symbol is overdue for removal.
35
+ - npm script: `audit-deprecations`.
36
+ - Structured `@deprecated` convention defined: `@deprecated since {version} — use {replacement} instead. Removes at {removeAfter}.`
37
+ - All three existing `@deprecated` tags in `src/` updated to the new structured format:
38
+ - `anankeVersion` in `content-pack.ts`: since 0.1.65, removes at 0.3.0
39
+ - `Perception` type alias in `sim/ai/perception.ts`: since 0.1.0, removes at 0.3.0
40
+ - `mkWorld(seed, loadout)` overload in `sim/testing.ts`: since 0.1.0, removes at 0.2.0
41
+ - `docs/versioning.md`: new "Deprecation lifecycle" section documenting the three-phase pattern (mark → migration window → remove), the required tag format, and the audit checklist.
42
+ - `docs/module-index.md`: new "Deprecated exports" table surfacing all known deprecated symbols with since/removeAfter/replacement.
43
+ - 0 new tests (5,593 total). Coverage: 97.11%/88.07%/95.83%/97.11%. Build: clean.
44
+
45
+ ### Deprecated
46
+
47
+ - `AnankePackManifest.anankeVersion` — since 0.1.65, use `registry.compatRange` instead. Removes at 0.3.0.
48
+
49
+ ---
50
+
9
51
  ## [0.1.65] — 2026-04-01
10
52
 
11
53
  ### Added
package/README.md CHANGED
@@ -418,6 +418,7 @@ Ananke's outputs are validated against historical and experimental sources:
418
418
  |---|---|
419
419
  | [`docs/recipes-matrix.md`](docs/recipes-matrix.md) | **Start here** — use case → package → stability → example → performance in one table |
420
420
  | [`docs/cookbook.md`](docs/cookbook.md) | Task-oriented recipes — duel, 500-agent battle, species, renderer, campaign, replay, and more |
421
+ | [`corpus/README.md`](corpus/README.md) | Scenario corpus — 5 canonical deterministic scenarios (tutorial, benchmark, validation, networking, bridge); run `npm run verify-corpus` |
421
422
  | [`docs/module-index.md`](docs/module-index.md) | All 41 entry points — stability tier, use case, key exports, doc links |
422
423
  | [`docs/host-contract.md`](docs/host-contract.md) | Stable integration surface — everything needed to embed Ananke without reading `src/` |
423
424
  | [`docs/integration-primer.md`](docs/integration-primer.md) | Data-flow diagrams, type glossary, gotchas |
@@ -0,0 +1,47 @@
1
+ # Ananke — Scenario Corpus
2
+
3
+ Each subdirectory contains a `corpus.json` manifest describing a canonical
4
+ deterministic scenario. Run `npm run verify-corpus` to verify all entries
5
+ against the reference engine.
6
+
7
+ ## Entries
8
+
9
+ | ID | Tags | Ticks | Description |
10
+ |----|------|-------|-------------|
11
+ | `basic-duel` | `tutorial` | 30 | Basic 1v1 Duel (No AI) |
12
+ | `armoured-combat` | `validation`, `content-pack` | 50 | Armoured 1v1 Combat (Line Infantry AI) |
13
+ | `lockstep-replay` | `networking` | 10 | Lockstep Replay Parity (10 Ticks) |
14
+ | `bridge-snapshot` | `bridge` | 0 | Renderer Bridge Snapshot |
15
+ | `ai-benchmark` | `benchmark` | 20 | AI Skirmish Benchmark (20 Ticks) |
16
+
17
+ ## Tag meanings
18
+
19
+ | Tag | Purpose |
20
+ |-----|---------|
21
+ | `tutorial` | Entry-level; no prior knowledge required |
22
+ | `benchmark` | Stable timing baseline; detect performance regressions |
23
+ | `validation` | Compared against empirical data |
24
+ | `networking` | Exercises replay, hash, lockstep |
25
+ | `bridge` | Exercises the renderer bridge |
26
+ | `content-pack` | Exercises equipment loading and composition |
27
+
28
+ ## Verifying
29
+
30
+ ```bash
31
+ npm run build
32
+ npm run verify-corpus # all entries
33
+ npm run verify-corpus -- --id=basic-duel # single entry
34
+ npm run verify-corpus -- --json # machine-readable
35
+ ```
36
+
37
+ ## Regenerating
38
+
39
+ Re-run after any change to `stepWorld`, `hashWorldState`, or equipment constants:
40
+
41
+ ```bash
42
+ npm run build && npm run generate-corpus
43
+ ```
44
+
45
+ ## Corpus format version
46
+
47
+ All manifests carry `"version": "corpus/v1"`.
@@ -0,0 +1,36 @@
1
+ {
2
+ "version": "corpus/v1",
3
+ "id": "ai-benchmark",
4
+ "title": "AI Skirmish Benchmark (20 Ticks)",
5
+ "description": "Two line-infantry entities engaging over 20 ticks. Benchmark scenario: exercises the full AI decision + physics + injury path. Use the timing result to detect performance regressions between engine versions.",
6
+ "tags": [
7
+ "benchmark"
8
+ ],
9
+ "stabilityStatus": "stable",
10
+ "scenario": {
11
+ "seed": 42,
12
+ "tractionCoeff_Q": 9000,
13
+ "entities": [
14
+ {
15
+ "id": 1,
16
+ "teamId": 1,
17
+ "x_m": -0.5,
18
+ "weapon": "wpn_longsword"
19
+ },
20
+ {
21
+ "id": 2,
22
+ "teamId": 2,
23
+ "x_m": 0.5,
24
+ "weapon": "wpn_longsword"
25
+ }
26
+ ],
27
+ "aiPolicy": "lineInfantry",
28
+ "tickCount": 20
29
+ },
30
+ "expectedOutputHash": "0xacb04548fd68a7fb",
31
+ "performanceClass": {
32
+ "entityCount": 2,
33
+ "expectedTickBudgetMs": 150
34
+ },
35
+ "replayFixture": null
36
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": "corpus/v1",
3
+ "id": "armoured-combat",
4
+ "title": "Armoured 1v1 Combat (Line Infantry AI)",
5
+ "description": "Two entities equipped with chainmail armour and longswords, driven by lineInfantry AI. Validation scenario: armour damage absorption, shock accumulation, and equipment interaction over 50 ticks.",
6
+ "tags": [
7
+ "validation",
8
+ "content-pack"
9
+ ],
10
+ "stabilityStatus": "stable",
11
+ "scenario": {
12
+ "seed": 42,
13
+ "tractionCoeff_Q": 9000,
14
+ "entities": [
15
+ {
16
+ "id": 1,
17
+ "teamId": 1,
18
+ "x_m": -0.5,
19
+ "weapon": "wpn_longsword",
20
+ "armour": "arm_chainmail"
21
+ },
22
+ {
23
+ "id": 2,
24
+ "teamId": 2,
25
+ "x_m": 0.5,
26
+ "weapon": "wpn_longsword",
27
+ "armour": "arm_chainmail"
28
+ }
29
+ ],
30
+ "aiPolicy": "lineInfantry",
31
+ "tickCount": 50
32
+ },
33
+ "expectedOutputHash": "0x8231f53b3211a379",
34
+ "performanceClass": {
35
+ "entityCount": 2,
36
+ "expectedTickBudgetMs": 200
37
+ },
38
+ "replayFixture": null
39
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "version": "corpus/v1",
3
+ "id": "basic-duel",
4
+ "title": "Basic 1v1 Duel (No AI)",
5
+ "description": "Two entities face off with longswords and no AI commands. Entry-level tutorial: demonstrates combat resolution, injury accumulation, and deterministic world-state hashing over 30 ticks.",
6
+ "tags": [
7
+ "tutorial"
8
+ ],
9
+ "stabilityStatus": "stable",
10
+ "scenario": {
11
+ "seed": 42,
12
+ "tractionCoeff_Q": 9000,
13
+ "entities": [
14
+ {
15
+ "id": 1,
16
+ "teamId": 1,
17
+ "x_m": -0.5,
18
+ "weapon": "wpn_longsword"
19
+ },
20
+ {
21
+ "id": 2,
22
+ "teamId": 2,
23
+ "x_m": 0.5,
24
+ "weapon": "wpn_longsword"
25
+ }
26
+ ],
27
+ "aiPolicy": "noMove",
28
+ "tickCount": 30
29
+ },
30
+ "expectedOutputHash": "0x7d82acd3d1966c61",
31
+ "performanceClass": {
32
+ "entityCount": 2,
33
+ "expectedTickBudgetMs": 100
34
+ },
35
+ "replayFixture": null
36
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "version": "corpus/v1",
3
+ "id": "bridge-snapshot",
4
+ "title": "Renderer Bridge Snapshot",
5
+ "description": "Two entities at tick 0 serialized through serializeBridgeFrame. Bridge scenario: verifies the BridgeFrame schema version, tick, entity count, and entity IDs. Baseline for renderer integration testing.",
6
+ "tags": [
7
+ "bridge"
8
+ ],
9
+ "stabilityStatus": "stable",
10
+ "scenario": {
11
+ "seed": 42,
12
+ "tractionCoeff_Q": 9000,
13
+ "entities": [
14
+ {
15
+ "id": 1,
16
+ "teamId": 1,
17
+ "x_m": -0.5,
18
+ "weapon": "wpn_longsword"
19
+ },
20
+ {
21
+ "id": 2,
22
+ "teamId": 2,
23
+ "x_m": 0.5,
24
+ "weapon": "wpn_longsword"
25
+ }
26
+ ],
27
+ "aiPolicy": "noMove",
28
+ "tickCount": 0
29
+ },
30
+ "expectedOutputHash": "0xec578e005fcbef34",
31
+ "performanceClass": {
32
+ "entityCount": 2,
33
+ "expectedTickBudgetMs": 10
34
+ },
35
+ "replayFixture": null,
36
+ "bridgeExpected": {
37
+ "schema": "ananke.bridge.frame.v1",
38
+ "tick": 0,
39
+ "entityCount": 2,
40
+ "scenarioId": "corpus-bridge"
41
+ }
42
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "version": "corpus/v1",
3
+ "id": "lockstep-replay",
4
+ "title": "Lockstep Replay Parity (10 Ticks)",
5
+ "description": "Two entities with noMove commands over 10 ticks. Networking scenario: records a reference replay and verifies that replayTo() reproduces identical world-state hashes at each tick. Use as a correctness check when porting to a new host environment.",
6
+ "tags": [
7
+ "networking"
8
+ ],
9
+ "stabilityStatus": "stable",
10
+ "scenario": {
11
+ "seed": 42,
12
+ "tractionCoeff_Q": 9000,
13
+ "entities": [
14
+ {
15
+ "id": 1,
16
+ "teamId": 1,
17
+ "x_m": -0.5,
18
+ "weapon": "wpn_longsword"
19
+ },
20
+ {
21
+ "id": 2,
22
+ "teamId": 2,
23
+ "x_m": 0.5,
24
+ "weapon": "wpn_longsword"
25
+ }
26
+ ],
27
+ "aiPolicy": "noMove",
28
+ "tickCount": 10
29
+ },
30
+ "expectedOutputHash": "0x8fe5f3eba59e0b75",
31
+ "performanceClass": {
32
+ "entityCount": 2,
33
+ "expectedTickBudgetMs": 200
34
+ },
35
+ "replayFixture": "replay.json"
36
+ }
@@ -0,0 +1 @@
1
+ {"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"}]]]}]}
@@ -1,6 +1,6 @@
1
1
  import type { WorldState } from "./sim/world.js";
2
2
  /** Current Ananke engine version — used to evaluate pack compatRange at runtime. */
3
- export declare const ANANKE_ENGINE_VERSION = "0.1.65";
3
+ export declare const ANANKE_ENGINE_VERSION = "0.1.67";
4
4
  /** A single actionable validation failure from `validatePack`. */
5
5
  export interface PackValidationError {
6
6
  /** JSONPath-style location, e.g. `"$.weapons[2].mass_kg"`. */
@@ -81,7 +81,7 @@ export interface AnankePackManifest {
81
81
  description?: string;
82
82
  /**
83
83
  * Minimum Ananke version required, as a semver range string.
84
- * @deprecated Use `registry.compatRange` instead this field is informational only.
84
+ * @deprecated since 0.1.65 — use `registry.compatRange` instead. Removes at 0.3.0.
85
85
  */
86
86
  anankeVersion?: string;
87
87
  /**
@@ -16,7 +16,7 @@ import { registerWorldArchetype, registerWorldItem } from "./world-factory.js";
16
16
  // ── Version constant ──────────────────────────────────────────────────────────
17
17
  // Must be kept in sync with package.json "version" field.
18
18
  /** Current Ananke engine version — used to evaluate pack compatRange at runtime. */
19
- export const ANANKE_ENGINE_VERSION = "0.1.65";
19
+ export const ANANKE_ENGINE_VERSION = "0.1.67";
20
20
  // ── Semver utilities ──────────────────────────────────────────────────────────
21
21
  // Lightweight range evaluator — no external dependencies.
22
22
  // Supports: >=X.Y.Z >X.Y.Z <=X.Y.Z <X.Y.Z =X.Y.Z ^X.Y.Z ~X.Y.Z
@@ -7,6 +7,6 @@ export interface LocalPerception {
7
7
  enemies: Entity[];
8
8
  allies: Entity[];
9
9
  }
10
- /** @deprecated Use LocalPerception */
10
+ /** @deprecated since 0.1.0 — use `LocalPerception` instead. Removes at 0.3.0. */
11
11
  export type Perception = LocalPerception;
12
12
  export declare function perceiveLocal(world: WorldState | undefined, self: Entity, index: WorldIndex, spatial: SpatialIndex, radius_m: number, maxCount?: number, env?: SensoryEnvironment): LocalPerception;
@@ -8,6 +8,6 @@ import { ImpactEvent } from "./events.js";
8
8
  */
9
9
  export declare function mkHumanoidEntity(id: number, teamId: number, x_m: number, y_m: number, z_m?: number): Entity;
10
10
  export declare function mkWorld(seed: number, entities: Entity[]): WorldState;
11
- /** @deprecated Pass an explicit entity array instead: mkWorld(seed, [a, b]) */
11
+ /** @deprecated since 0.1.0 use `mkWorld(seed, entities[])` instead. Removes at 0.2.0. */
12
12
  export declare function mkWorld(seed: number, loadoutA: Loadout): WorldState;
13
13
  export declare function mkImpactEvent(attackerId: number, targetId: number, region?: string, energy_J?: number, protectedByArmour?: boolean, blocked?: boolean, parried?: boolean, weaponId?: string, wpn?: Weapon, hitQuality?: number, shieldBlocked?: boolean): ImpactEvent;
@@ -179,3 +179,67 @@ body plan hooks):
179
179
  3. Keep an `UPSTREAM.md` at your fork root noting your base version and a diff summary
180
180
  4. Periodically rebase onto upstream Tier 3 commits to collect non-breaking improvements;
181
181
  treat Tier 1/2 commits as explicit migration tasks to schedule
182
+
183
+ ---
184
+
185
+ ## Deprecation lifecycle
186
+
187
+ Symbols are deprecated rather than removed immediately so downstream projects have time
188
+ to migrate. The lifecycle follows a three-phase pattern:
189
+
190
+ ### 1 — Mark deprecated (current version)
191
+
192
+ Add a structured JSDoc tag to the symbol:
193
+
194
+ ```typescript
195
+ /**
196
+ * @deprecated since 0.1.50 — use `newFunction` instead. Removes at 0.3.0.
197
+ */
198
+ export function oldFunction() { … }
199
+ ```
200
+
201
+ The **required format** is:
202
+
203
+ ```
204
+ @deprecated since {version} — use {replacement} instead. Removes at {removeAfter}.
205
+ ```
206
+
207
+ | Field | Meaning |
208
+ |-------|---------|
209
+ | `since` | Version in which the deprecation was introduced |
210
+ | `replacement` | Short description or code reference of what to use instead |
211
+ | `removeAfter` | Version at which the symbol will be deleted — must be a future version |
212
+
213
+ `removeAfter` must satisfy `> current` at publish time; `npm publish` runs
214
+ `audit-deprecations --check` and fails if any symbol is overdue.
215
+
216
+ ### 2 — Migration window
217
+
218
+ During the migration window the symbol still works but emits a TypeScript deprecation
219
+ warning in IDEs (the `@deprecated` tag triggers the strikethrough).
220
+
221
+ The migration window is at least one **minor** version for Tier 2 symbols
222
+ and at least one **major** version for Tier 1 (Stable) symbols.
223
+
224
+ ### 3 — Remove at removeAfter
225
+
226
+ When the engine reaches `removeAfter`, the symbol is deleted and a CHANGELOG entry is
227
+ added under a `### Removed` heading. The `since` version and the replacement are
228
+ included in the removal note so the changelog is self-contained.
229
+
230
+ ### Auditing
231
+
232
+ ```bash
233
+ npm run audit-deprecations # human-readable table
234
+ npm run audit-deprecations -- --json # machine-readable JSON
235
+ npm run audit-deprecations -- --check # exit 1 if any overdue
236
+ ```
237
+
238
+ The `--check` flag is run automatically by `npm run prepublishOnly`.
239
+
240
+ ### Adding a new deprecation (checklist)
241
+
242
+ - [ ] Add the structured `@deprecated` JSDoc tag with `since`, replacement, and `removeAfter`.
243
+ - [ ] Run `npm run audit-deprecations` to confirm the tag is detected and not overdue.
244
+ - [ ] Add a CHANGELOG entry under `### Deprecated`.
245
+ - [ ] Surface the replacement in the relevant `docs/` file or `STABLE_API.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@its-not-rocket-science/ananke",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "type": "module",
5
5
  "description": "Deterministic lockstep-friendly SI-units RPG/physics core (fixed-point TS)",
6
6
  "license": "MIT",
@@ -214,6 +214,7 @@
214
214
  "dist/src",
215
215
  "dist/as",
216
216
  "conformance",
217
+ "corpus",
217
218
  "docs/project-overview.md",
218
219
  "docs/host-contract.md",
219
220
  "docs/integration-primer.md",
@@ -247,7 +248,7 @@
247
248
  "game-engine"
248
249
  ],
249
250
  "scripts": {
250
- "prepublishOnly": "npm run build && npm run test:coverage",
251
+ "prepublishOnly": "npm run build && npm run test:coverage && npm run audit-deprecations -- --check",
251
252
  "build": "tsc -p tsconfig.build.json",
252
253
  "test": "vitest run",
253
254
  "test:watch": "vitest",
@@ -286,6 +287,8 @@
286
287
  "generate-recipes-matrix": "node dist/tools/generate-recipes-matrix.js",
287
288
  "generate-conformance-fixtures": "node dist/tools/generate-conformance-fixtures.js",
288
289
  "conformance-runner": "node dist/tools/conformance-runner.js",
290
+ "generate-corpus": "node dist/tools/generate-corpus.js",
291
+ "verify-corpus": "node dist/tools/verify-corpus.js",
289
292
  "pack": "node dist/tools/pack-cli.js pack",
290
293
  "generate-fixtures": "node dist/tools/generate-fixtures.js",
291
294
  "generate-zoo": "node dist/tools/generate-zoo.js",
@@ -298,6 +301,7 @@
298
301
  "benchmark-check": "node dist/tools/benchmark-check.js",
299
302
  "benchmark-check:strict": "node dist/tools/benchmark-check.js --threshold=0.10",
300
303
  "benchmark-check:update": "node dist/tools/benchmark-check.js --update-baseline",
304
+ "audit-deprecations": "node dist/tools/audit-deprecations.js",
301
305
  "benchmark:guide": "node dist/tools/benchmark-guide.js",
302
306
  "benchmark:parallel": "node dist/tools/benchmark-parallel.js",
303
307
  "run:renderer-bridge": "node dist/tools/renderer-bridge.js",