@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.
Files changed (165) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/README.md +150 -432
  3. package/STABLE_API.md +113 -490
  4. package/conformance/README.md +46 -44
  5. package/conformance/lockstep-sequence.json +395 -395
  6. package/conformance/phase-order.json +21 -0
  7. package/conformance/replay-parity.json +65 -65
  8. package/conformance/state-hash-regression.json +39 -0
  9. package/conformance/state-hash.json +23 -22
  10. package/dist/src/conformance.d.ts +8 -2
  11. package/dist/src/content/index.d.ts +3 -0
  12. package/dist/src/content/index.js +2 -0
  13. package/dist/src/content/injector.d.ts +19 -0
  14. package/dist/src/content/injector.js +61 -0
  15. package/dist/src/content/loader.d.ts +3 -0
  16. package/dist/src/content/loader.js +170 -0
  17. package/dist/src/content/types.d.ts +56 -0
  18. package/dist/src/content/types.js +1 -0
  19. package/dist/src/content/validator.d.ts +6 -0
  20. package/dist/src/content/validator.js +14 -0
  21. package/dist/src/content-pack.d.ts +3 -2
  22. package/dist/src/content-pack.js +205 -10
  23. package/dist/src/data-governance.d.ts +73 -0
  24. package/dist/src/data-governance.js +188 -0
  25. package/dist/src/debug.d.ts +6 -0
  26. package/dist/src/debug.js +9 -0
  27. package/dist/src/determinism.d.ts +12 -0
  28. package/dist/src/determinism.js +93 -0
  29. package/dist/src/dialogue.d.ts +41 -0
  30. package/dist/src/dialogue.js +113 -2
  31. package/dist/src/economy.d.ts +97 -3
  32. package/dist/src/economy.js +191 -37
  33. package/dist/src/history/autosave.d.ts +19 -0
  34. package/dist/src/history/autosave.js +98 -0
  35. package/dist/src/history/timetravel.d.ts +28 -0
  36. package/dist/src/history/timetravel.js +61 -0
  37. package/dist/src/index.d.ts +19 -31
  38. package/dist/src/index.js +20 -38
  39. package/dist/src/model3d.js +63 -2
  40. package/dist/src/narrative/combat-logger.d.ts +25 -0
  41. package/dist/src/narrative/combat-logger.js +97 -0
  42. package/dist/src/narrative/plausibility.d.ts +30 -0
  43. package/dist/src/narrative/plausibility.js +70 -0
  44. package/dist/src/narrative-layer.d.ts +2 -0
  45. package/dist/src/narrative-layer.js +2 -0
  46. package/dist/src/navigation/causal-chain.d.ts +26 -0
  47. package/dist/src/navigation/causal-chain.js +74 -0
  48. package/dist/src/netcode.d.ts +1 -1
  49. package/dist/src/netcode.js +1 -1
  50. package/dist/src/performance/adaptive-tick.d.ts +23 -0
  51. package/dist/src/performance/adaptive-tick.js +50 -0
  52. package/dist/src/plugins/loader.d.ts +23 -0
  53. package/dist/src/plugins/loader.js +86 -0
  54. package/dist/src/plugins/registry.d.ts +11 -0
  55. package/dist/src/plugins/registry.js +28 -0
  56. package/dist/src/plugins/types.d.ts +31 -0
  57. package/dist/src/plugins/types.js +7 -0
  58. package/dist/src/replay.js +5 -2
  59. package/dist/src/scenario.js +2 -1
  60. package/dist/src/serialization/binary.d.ts +5 -0
  61. package/dist/src/serialization/binary.js +193 -0
  62. package/dist/src/session.d.ts +115 -0
  63. package/dist/src/session.js +228 -0
  64. package/dist/src/sim/ai/decide.js +1 -1
  65. package/dist/src/sim/ai/personality.js +2 -2
  66. package/dist/src/sim/bodyplan.d.ts +30 -0
  67. package/dist/src/sim/context.d.ts +2 -0
  68. package/dist/src/sim/kernel.js +80 -1294
  69. package/dist/src/sim/normalization.d.ts +12 -0
  70. package/dist/src/sim/normalization.js +92 -0
  71. package/dist/src/sim/resolvers/attack-resolver.d.ts +30 -0
  72. package/dist/src/sim/resolvers/attack-resolver.js +310 -0
  73. package/dist/src/sim/resolvers/capability-resolver.d.ts +15 -0
  74. package/dist/src/sim/resolvers/capability-resolver.js +270 -0
  75. package/dist/src/sim/resolvers/grapple-resolver.d.ts +28 -0
  76. package/dist/src/sim/resolvers/grapple-resolver.js +70 -0
  77. package/dist/src/sim/resolvers/shoot-resolver.d.ts +27 -0
  78. package/dist/src/sim/resolvers/shoot-resolver.js +212 -0
  79. package/dist/src/sim/resolvers/treat-resolver.d.ts +16 -0
  80. package/dist/src/sim/resolvers/treat-resolver.js +77 -0
  81. package/dist/src/sim/step/apply/intents.d.ts +6 -0
  82. package/dist/src/sim/step/apply/intents.js +83 -0
  83. package/dist/src/sim/step/kernel-pipeline.d.ts +9 -0
  84. package/dist/src/sim/step/kernel-pipeline.js +15 -0
  85. package/dist/src/sim/step/phases/capability-phase.d.ts +8 -0
  86. package/dist/src/sim/step/phases/capability-phase.js +65 -0
  87. package/dist/src/sim/step/phases/cooldowns-phase.d.ts +3 -0
  88. package/dist/src/sim/step/phases/cooldowns-phase.js +37 -0
  89. package/dist/src/sim/step/phases/prepare-phase.d.ts +10 -0
  90. package/dist/src/sim/step/phases/prepare-phase.js +33 -0
  91. package/dist/src/sim/step/pipeline.d.ts +2 -0
  92. package/dist/src/sim/step/pipeline.js +15 -0
  93. package/dist/src/sim/step/resolvers/impact-resolver.d.ts +9 -0
  94. package/dist/src/sim/step/resolvers/impact-resolver.js +55 -0
  95. package/dist/src/sim/step/world-phases.d.ts +7 -0
  96. package/dist/src/sim/step/world-phases.js +15 -0
  97. package/dist/src/sim/step/world-step-context.d.ts +23 -0
  98. package/dist/src/sim/step/world-step-context.js +1 -0
  99. package/dist/src/sim/systemic-toxicology.js +1 -1
  100. package/dist/src/sim/team.js +2 -2
  101. package/dist/src/sim/testing.js +4 -3
  102. package/dist/src/sim/toxicology.d.ts +1 -1
  103. package/dist/src/sim/toxicology.js +1 -1
  104. package/dist/src/sim/world.d.ts +17 -24
  105. package/dist/src/snapshot.js +3 -6
  106. package/dist/src/tier2.d.ts +19 -0
  107. package/dist/src/tier2.js +21 -0
  108. package/dist/src/tier3.d.ts +10 -0
  109. package/dist/src/tier3.js +12 -0
  110. package/dist/src/version.d.ts +6 -0
  111. package/dist/src/version.js +8 -0
  112. package/dist/src/wasm/bridge.d.ts +14 -0
  113. package/dist/src/wasm/bridge.js +84 -0
  114. package/dist/src/wasm-kernel.d.ts +2 -0
  115. package/dist/src/wasm-kernel.js +1 -0
  116. package/dist/src/world-evolution-backend/engine.d.ts +4 -0
  117. package/dist/src/world-evolution-backend/engine.js +347 -0
  118. package/dist/src/world-evolution-backend/host-schema.d.ts +145 -0
  119. package/dist/src/world-evolution-backend/host-schema.js +375 -0
  120. package/dist/src/world-evolution-backend/index.d.ts +1 -0
  121. package/dist/src/world-evolution-backend/index.js +1 -0
  122. package/dist/src/world-evolution-backend/open-world-host-adapter.d.ts +96 -0
  123. package/dist/src/world-evolution-backend/open-world-host-adapter.js +208 -0
  124. package/dist/src/world-evolution-backend/profiles.d.ts +4 -0
  125. package/dist/src/world-evolution-backend/profiles.js +136 -0
  126. package/dist/src/world-evolution-backend/public.d.ts +11 -0
  127. package/dist/src/world-evolution-backend/public.js +7 -0
  128. package/dist/src/world-evolution-backend/reproducibility.d.ts +8 -0
  129. package/dist/src/world-evolution-backend/reproducibility.js +35 -0
  130. package/dist/src/world-evolution-backend/timeline.d.ts +6 -0
  131. package/dist/src/world-evolution-backend/timeline.js +337 -0
  132. package/dist/src/world-evolution-backend/types.d.ts +138 -0
  133. package/dist/src/world-evolution-backend/types.js +1 -0
  134. package/dist/src/world-evolution-host-backend.d.ts +51 -0
  135. package/dist/src/world-evolution-host-backend.js +97 -0
  136. package/dist/src/world-evolution.d.ts +201 -0
  137. package/dist/src/world-evolution.js +543 -0
  138. package/dist/src/world-factory.js +2 -1
  139. package/dist/tools/pack-cli.js +55 -2
  140. package/docs/bridge-contract.md +140 -332
  141. package/docs/completion-evidence-dashboard.md +63 -0
  142. package/docs/host-contract.md +86 -310
  143. package/docs/host-world-evolution-schema.md +155 -0
  144. package/docs/integration-primer.md +29 -22
  145. package/docs/migration-monolith-to-modular.md +102 -120
  146. package/docs/package-architecture.md +116 -323
  147. package/docs/plugins/README.md +42 -0
  148. package/docs/plugins/hooks.md +22 -0
  149. package/docs/plugins/publishing.md +29 -0
  150. package/docs/plugins/security-model.md +273 -0
  151. package/docs/project-overview.md +30 -30
  152. package/docs/release-dashboard.md +47 -0
  153. package/docs/session-api.md +127 -0
  154. package/docs/stable-api-manifest.json +52 -0
  155. package/docs/trust-dashboard.md +37 -0
  156. package/docs/versioning.md +256 -245
  157. package/docs/wire-protocol.md +140 -209
  158. package/docs/world-evolution-backend.md +200 -0
  159. package/docs/world-evolution-engine.md +150 -0
  160. package/docs/world-evolution-host-backend.md +70 -0
  161. package/docs/world-evolution-orchestration.md +248 -0
  162. package/package.json +125 -6
  163. package/schema/content-pack.schema.json +85 -0
  164. package/schema/plugin.schema.json +57 -0
  165. package/schema/world-evolution-input.schema.json +196 -0
package/README.md CHANGED
@@ -1,432 +1,150 @@
1
- # Ananke — Programmer's Guide
2
-
3
- ![CI](../../actions/workflows/ci.yml/badge.svg)
4
-
5
- > **Package:** `@its-not-rocket-science/ananke`
6
- > **Full project overview:** [`docs/project-overview.md`](docs/project-overview.md)
7
-
8
- ---
9
-
10
- ## What is Ananke?
11
-
12
- Ananke is a **deterministic, physics-grounded simulation engine** for characters, combat,
13
- and survivability. It models entities using real physical quantities — mass in kg, force
14
- in newtons, energy in joules — rather than abstract hit points or dice rolls.
15
-
16
- Same seed + same inputs → identical results, every time. No floating-point drift.
17
- Suitable for lockstep multiplayer, reproducible research, and offline AI training.
18
-
19
- ---
20
-
21
- ## Installation
22
-
23
- ```bash
24
- npm install @its-not-rocket-science/ananke
25
- ```
26
-
27
- Requires Node 18. ESM-only. TypeScript declarations included — no `@types/` package
28
- needed. Zero runtime dependencies.
29
-
30
- > **Versioning:** pin to a specific version in production. The `0.x` series may include
31
- > minor-version breaking changes to Tier 2 (experimental) APIs; Tier 1 (Stable) APIs follow
32
- > full semver. See [`STABLE_API.md`](STABLE_API.md) for the tier breakdown and
33
- > [`docs/versioning.md`](docs/versioning.md) for the upgrade policy.
34
-
35
- ---
36
-
37
- ## Core concepts
38
-
39
- ### Fixed-point arithmetic
40
-
41
- All simulation values use `Q` a fixed-point integer where `SCALE.Q = 16384` represents
42
- `1.0`. Never use raw `number` for simulation values; always use `q()` to construct them.
43
-
44
- ```typescript
45
- import { q, SCALE } from "@its-not-rocket-science/ananke";
46
-
47
- const half = q(0.50); // 8192 — 50%
48
- const full = q(1.00); // 16384 — 100%
49
- const eighty = q(0.80); // 13107 — 80%
50
-
51
- // SI unit scales
52
- SCALE.m; // 1000 — 1 metre in fixed-point units
53
- SCALE.kg; // 1000 — 1 kilogram
54
- SCALE.mps; // 1000 — 1 m/s
55
- SCALE.J; // 1 — 1 joule (energy is stored at 1:1)
56
- ```
57
-
58
- You will see values like `position_m: { x: 3000, y: 0, z: 0 }` — that is 3 metres on the
59
- x-axis (`3000 / SCALE.m = 3`). The `_m`, `_kg`, `_J`, `_s` suffixes on field names tell
60
- you the unit.
61
-
62
- ### The Entity
63
-
64
- An `Entity` is any simulated object. Required fields at creation:
65
-
66
- ```typescript
67
- import type { Entity } from "@its-not-rocket-science/ananke";
68
- ```
69
-
70
- | Field | Type | Meaning |
71
- |---|---|---|
72
- | `id` | `number` | Unique integer; used as RNG salt |
73
- | `teamId` | `number` | Entities attack those on different teams |
74
- | `position_m` | `Vec3` | World-space position in fixed-point metres |
75
- | `attributes` | `IndividualAttributes` | Physical stats (force, power, mass…) |
76
- | `energy` | `{ current_J, max_J }` | Stamina pool in joules |
77
- | `injury` | `InjuryState` | Per-region damage accumulation |
78
- | `condition` | `ConditionSnapshot` | Shock, fear, fatigue |
79
- | `loadout` | `{ items: Item[] }` | Equipped weapons and armour |
80
-
81
- Use a factory instead of constructing these manually — see **Quick starts** below.
82
-
83
- ### The simulation loop
84
-
85
- ```typescript
86
- import { mkWorld, stepWorld } from "@its-not-rocket-science/ananke";
87
-
88
- const world = mkWorld(seed, entities); // create world with deterministic seed
89
-
90
- for (let tick = 0; tick < 2000; tick++) {
91
- const commands = buildCommands(world); // your AI / player input
92
- stepWorld(world, commands, ctx); // mutates world in-place
93
- }
94
- ```
95
-
96
- `stepWorld` is the only function that mutates state. Everything else is pure computation.
97
- Call it at 20 Hz for real-time simulation; 1 Hz or lower for campaign-scale time.
98
-
99
- Not sure which entry point to use? See the **[Recipes Matrix](docs/recipes-matrix.md)** — use case → package → stability → runnable example → performance in one table.
100
-
101
- For task-oriented walkthroughs, see the **[Simulation Cookbook](docs/cookbook.md)** — 12 recipes
102
- from "Simulate a duel" to "Load a content pack", each with step-by-step code and expected output.
103
-
104
- ---
105
-
106
- ## Quick start A — Melee combat
107
-
108
- Two fighters, one fight, three seeds:
109
-
110
- ```typescript
111
- import {
112
- mkWorld, stepWorld, generateIndividual, q,
113
- SCALE, STARTER_WEAPONS, STARTER_ARMOUR,
114
- buildAICommands, buildWorldIndex, buildSpatialIndex,
115
- AI_PRESETS,
116
- } from "@its-not-rocket-science/ananke";
117
- import type { Q } from "@its-not-rocket-science/ananke";
118
-
119
- const LONGSWORD = STARTER_WEAPONS[2]!;
120
- const LEATHER = STARTER_ARMOUR[0]!;
121
-
122
- function makeEntity(id: number, teamId: number, x_m: number) {
123
- const e = generateIndividual("KNIGHT_INFANTRY", id, teamId);
124
- e.position_m = { x: x_m * SCALE.m, y: 0, z: 0 };
125
- e.loadout = { items: [LONGSWORD, LEATHER] };
126
- return e;
127
- }
128
-
129
- const policy = AI_PRESETS["lineInfantry"]!;
130
-
131
- for (const seed of [1, 42, 99]) {
132
- const a = makeEntity(1, 1, -2);
133
- const b = makeEntity(2, 2, +2);
134
- const world = mkWorld(seed, [a, b]);
135
- const ctx = { tractionCoeff: q(0.85) as Q };
136
-
137
- let tick = 0;
138
- while (tick < 2000 && !a.injury.dead && !b.injury.dead) {
139
- tick++;
140
- const idx = buildWorldIndex(world);
141
- const spat = buildSpatialIndex(world, 40_000);
142
- const cmds = buildAICommands(world, idx, spat, () => policy);
143
- stepWorld(world, cmds, ctx);
144
- }
145
-
146
- const winner = a.injury.dead ? "B" : b.injury.dead ? "A" : "draw";
147
- console.log(`seed=${seed} winner=${winner} ticks=${tick}`);
148
- }
149
- ```
150
-
151
- ### Reading injury state
152
-
153
- ```typescript
154
- for (const [region, inj] of Object.entries(entity.injury.regions)) {
155
- const pct = (inj.surfaceDamage / SCALE.Q * 100).toFixed(0);
156
- if (inj.surfaceDamage > 0)
157
- console.log(` ${region}: ${pct}% surface damage${inj.infected ? " [infected]" : ""}`);
158
- }
159
- console.log(` dead: ${entity.injury.dead}`);
160
- console.log(` shock: ${(entity.condition.shockQ / SCALE.Q * 100).toFixed(0)}%`);
161
- ```
162
-
163
- ### Using the narrative layer
164
-
165
- ```typescript
166
- import {
167
- CollectingTrace, renderChronicle,
168
- } from "@its-not-rocket-science/ananke";
169
-
170
- const trace = new CollectingTrace();
171
- stepWorld(world, commands, { ...ctx, trace });
172
-
173
- const log = renderChronicle(trace.events, world.entities, { verbosity: "normal" });
174
- console.log(log);
175
- // → "Knight strikes Brawler in the torso for 340 J. Brawler staggers."
176
- ```
177
-
178
- ---
179
-
180
- ## Quick start B — Campaign and world simulation
181
-
182
- Advance two polities through 90 days with tech diffusion and emotional contagion:
183
-
184
- ```typescript
185
- import {
186
- createPolityRegistry, stepPolityDay,
187
- applyEmotionalContagion, stepTechDiffusion,
188
- createEmotionalWave, FEAR_WAVE, q, SCALE,
189
- } from "@its-not-rocket-science/ananke";
190
-
191
- const WORLD_SEED = 1;
192
-
193
- const registry = createPolityRegistry([
194
- { id: 1, name: "Ironhold", population: 50_000, techEra: 2, moraleQ: q(0.70) /* ... */ },
195
- { id: 2, name: "Ashfeld", population: 30_000, techEra: 1, moraleQ: q(0.55) /* ... */ },
196
- ]);
197
-
198
- const pairs = [{ polityA: 1, polityB: 2, routeQuality_Q: q(0.60), atWar: false /* ... */ }];
199
-
200
- for (let day = 1; day <= 90; day++) {
201
- stepPolityDay(registry, WORLD_SEED, day);
202
- stepTechDiffusion(registry, pairs, WORLD_SEED, day);
203
- applyEmotionalContagion(registry, [createEmotionalWave(FEAR_WAVE, 1)], pairs);
204
- }
205
-
206
- for (const p of registry.polities) {
207
- console.log(`${p.name}: pop=${p.population} era=${p.techEra} morale=${(p.moraleQ / SCALE.Q).toFixed(2)}`);
208
- }
209
- ```
210
-
211
- ---
212
-
213
- ## Quick start C — Species and character generation
214
-
215
- Generate individuals from a body-plan archetype, apply aging, and describe them:
216
-
217
- ```typescript
218
- import {
219
- generateIndividual, applyAgingToAttributes,
220
- describeCharacter, formatCharacterSheet,
221
- } from "@its-not-rocket-science/ananke";
222
-
223
- // Generate a 45-year-old knight
224
- const base = generateIndividual("KNIGHT_INFANTRY", 1, 1);
225
- const aged = applyAgingToAttributes(base.attributes, 45);
226
-
227
- console.log(formatCharacterSheet({ ...base, attributes: aged }));
228
- // → Strength: 1840 N [above average]
229
- // Reaction: 0.21 s [average]
230
- // ...
231
-
232
- // Fantasy species
233
- const elf = generateIndividual("ELF_ARCHER", 2, 2);
234
- console.log(describeCharacter(elf));
235
- ```
236
-
237
- Available built-in archetypes: `KNIGHT_INFANTRY`, `PRO_BOXER`, `GRECO_WRESTLER`,
238
- `AMATEUR_BOXER`, `LARGE_PACIFIC_OCTOPUS`, and all species defined in
239
- [`src/species.ts`](src/species.ts) — humans, elves, dwarves, orcs, dragons,
240
- Vulcans, Klingons, and more.
241
-
242
- ---
243
-
244
- ## The command system
245
-
246
- `stepWorld` takes a `CommandMap` — a `Map<entityId, EntityCommand>`. You build it
247
- manually, from your AI layer, or from the built-in AI system:
248
-
249
- ```typescript
250
- import type { EntityCommand } from "@its-not-rocket-science/ananke";
251
-
252
- // Attack
253
- const commands = new Map<number, EntityCommand>([
254
- [entityId, { kind: "attack", targetId: opponentId, weapon: LONGSWORD }],
255
- ]);
256
-
257
- // Move to a position
258
- commands.set(entityId, {
259
- kind: "move",
260
- destination: { x: 5 * SCALE.m, y: 0, z: 0 },
261
- });
262
-
263
- // Treat a wounded ally
264
- commands.set(medicId, {
265
- kind: "treat",
266
- targetId: woundedId,
267
- schedule: { care: "field_surgery", equipmentTier: 2 },
268
- });
269
- ```
270
-
271
- Valid `kind` values: `"attack"`, `"move"`, `"grapple"`, `"treat"`, `"use_capability"`,
272
- `"signal"`, `"idle"`.
273
-
274
- ---
275
-
276
- ## Determinism
277
-
278
- Ananke guarantees that `mkWorld(seed, entities)` followed by identical commands produces
279
- identical `WorldState` at every tick, regardless of platform, JS engine, or execution time.
280
-
281
- **Rules to preserve determinism in your host:**
282
-
283
- 1. Never use `Math.random()` — use `makeRng(eventSeed(...))` from the package instead
284
- 2. Iterate `world.entities` in insertion order (it is a stable array, not a Map)
285
- 3. Keep entity `id` values stable across ticks — IDs are used as RNG salts
286
- 4. Do not rely on wall-clock time inside the simulation loop
287
-
288
- ```typescript
289
- import { makeRng, eventSeed } from "@its-not-rocket-science/ananke";
290
-
291
- // Deterministic RNG inside your AI or event code:
292
- const rng = makeRng(eventSeed(world.seed, world.tick, entityId, 0, 42));
293
- const roll = rng(); // float in [0, 1) — deterministic from inputs
294
- ```
295
-
296
- ---
297
-
298
- ## Replay and serialisation
299
-
300
- ```typescript
301
- import {
302
- ReplayRecorder, serializeReplay, deserializeReplay, replayTo,
303
- } from "@its-not-rocket-science/ananke";
304
-
305
- // Record
306
- const recorder = new ReplayRecorder();
307
- for (let tick = 0; tick < N; tick++) {
308
- const cmds = buildCommands(world);
309
- recorder.record(tick, cmds);
310
- stepWorld(world, cmds, ctx);
311
- }
312
- const json = serializeReplay(recorder.replay); // stable JSON string
313
-
314
- // Replay to any tick
315
- const replay = deserializeReplay(json);
316
- const state = replayTo(replay, initialWorld, targetTick, ctx);
317
- ```
318
-
319
- ---
320
-
321
- ## 3D renderer bridge
322
-
323
- Extract per-segment pose data for driving a humanoid rig at renderer frame rate:
324
-
325
- ```typescript
326
- import {
327
- extractRigSnapshots, deriveAnimationHints, BridgeEngine,
328
- } from "@its-not-rocket-science/ananke";
329
-
330
- // Per-tick: get bone transforms
331
- const snapshots = extractRigSnapshots(world.entities, bodyPlan);
332
- // snapshots[entityId] → RigSnapshot { segments: Map<segmentId, { position_m, rotation }> }
333
-
334
- // Per-tick: get animation state machine hints
335
- const hints = deriveAnimationHints(entity);
336
- // hints → { idle, walk, run, attacking, prone, unconscious, dead, shockQ, fearQ, ... }
337
-
338
- // Or use BridgeEngine for double-buffered interpolation at renderer frame rate:
339
- const bridge = new BridgeEngine(config);
340
- bridge.writeSimFrame(world.tick, world.entities);
341
- const interp = bridge.readInterpolated(rendererTimestamp);
342
- ```
343
-
344
- See [`docs/bridge-contract.md`](docs/bridge-contract.md) for the full double-buffer
345
- protocol and `AnimationHints` field-by-field contract.
346
-
347
- ---
348
-
349
- ## API stability tiers
350
-
351
- | Tier | Guarantee | Examples |
352
- |------|-----------|---------|
353
- | **Tier 1 — Stable** | Breaking changes require major semver bump + migration guide | `stepWorld`, `generateIndividual`, `Entity`, `q`, `SCALE`, bridge module |
354
- | **Tier 2 — Experimental** | May change in minor versions; CHANGELOG will note it | Campaign, polity, dialogue, faction, quest subsystems |
355
- | **Tier 3 — Internal** | No stability guarantee; may change at any time | `makeRng`, `eventSeed`, kernel tuning constants, `mkHumanoidEntity` |
356
-
357
- Full tier table: [`STABLE_API.md`](STABLE_API.md)
358
-
359
- ---
360
-
361
- ## TypeScript
362
-
363
- The package ships full `.d.ts` declarations. Key types to know:
364
-
365
- ```typescript
366
- import type {
367
- Entity, // the simulated object
368
- WorldState, // world.entities + world.tick + world.seed
369
- KernelContext, // tractionCoeff, weather, etc. — passed to stepWorld
370
- EntityCommand, // what an entity does this tick
371
- IndividualAttributes, // physical stats (SI units)
372
- InjuryState, // per-region damage
373
- ConditionSnapshot, // shock, fear, fatigue
374
- Q, // fixed-point number alias (just `number` at runtime)
375
- Vec3, // { x, y, z } in fixed-point metres
376
- } from "@its-not-rocket-science/ananke";
377
- ```
378
-
379
- `Q` is a nominal alias for `number` — it carries no runtime overhead, but the `q()`
380
- constructor and `SCALE` constants make the intent clear in every formula.
381
-
382
- ---
383
-
384
- ## Performance guidance
385
-
386
- | Scenario | Recommended tick rate | Practical entity cap |
387
- |---|---|---|
388
- | Duel / 1v1 | 20 Hz | Unlimited |
389
- | Skirmish (squads) | 20 Hz | ~300 |
390
- | Battle (formations) | 10 Hz | ~500 |
391
- | Siege / campaign | 1 Hz | ~1 000 |
392
- | World simulation | 0.01 Hz (once/day) | ~10 000 |
393
-
394
- Enable `buildSpatialIndex` when entities exceed ~50 and distances matter. Disable
395
- expensive subsystems (disease O(n²) spread, thermoregulation) at high entity counts
396
- unless required.
397
-
398
- Full benchmark methodology and operational guide: [`docs/performance.md`](docs/performance.md)
399
-
400
- ---
401
-
402
- ## Validation and trust
403
-
404
- Ananke's outputs are validated against historical and experimental sources:
405
-
406
- - **Isolated sub-system validation** — compares physical constants against sport-science
407
- and biomechanics datasets: `npm run run:validation`
408
- - **Emergent validation** — four historical combat scenarios (du Picq, Keegan, Lanchester,
409
- Raudzens) across 100 seeds each: `npm run run:emergent-validation`
410
- - **Pinned baseline** — committed result summaries that CI guards against regression:
411
- [`docs/emergent-validation-report.md`](docs/emergent-validation-report.md)
412
-
413
- ---
414
-
415
- ## Further reading
416
-
417
- | Document | What's in it |
418
- |---|---|
419
- | [`docs/recipes-matrix.md`](docs/recipes-matrix.md) | **Start here** — use case → package → stability → example → performance in one table |
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` |
422
- | [`docs/module-index.md`](docs/module-index.md) | All 41 entry points — stability tier, use case, key exports, doc links |
423
- | [`docs/core-guarantees.md`](docs/core-guarantees.md) | Core guarantees — determinism contract, API stability, validation philosophy, known limits |
424
- | [`docs/maintenance-policy.md`](docs/maintenance-policy.md) | Maintenance commitments — docs reconciliation, issue triage, SDK parity, deprecation budget |
425
- | [`docs/host-contract.md`](docs/host-contract.md) | Stable integration surface — everything needed to embed Ananke without reading `src/` |
426
- | [`docs/integration-primer.md`](docs/integration-primer.md) | Data-flow diagrams, type glossary, gotchas |
427
- | [`docs/bridge-contract.md`](docs/bridge-contract.md) | 3D renderer bridge protocol (AnimationHints, GrapplePoseConstraint) |
428
- | [`STABLE_API.md`](STABLE_API.md) | Full tier table for every export |
429
- | [`docs/versioning.md`](docs/versioning.md) | Semver policy, breaking-change tiers, upgrade cadence |
430
- | [`docs/performance.md`](docs/performance.md) | Benchmark results, operational guide, entity caps |
431
- | [`docs/emergent-validation-report.md`](docs/emergent-validation-report.md) | Historical scenario validation report |
432
- | [`docs/project-overview.md`](docs/project-overview.md) | Full project overview — implementation status, entity model reference, design principles, architecture |
1
+ # Ananke
2
+
3
+ ![CI](../../actions/workflows/ci.yml/badge.svg)
4
+ ![Determinism](https://img.shields.io/badge/Determinism-%E2%9C%85%2010%2C000%2F10%2C000%20seeds%20passed%20(last%20run%3A%202026--04--03)-brightgreen)
5
+
6
+ > Package: `@its-not-rocket-science/ananke`
7
+ > Stable API contract: [`STABLE_API.md`](STABLE_API.md)
8
+ > Status: active engineering project; stable root API, exploratory submodules
9
+
10
+ Ananke is a deterministic simulation kernel for host applications.
11
+
12
+ It is designed for projects that need a world state to evolve reproducibly while leaving rendering, persistence, networking, content authoring, and deployment under the control of the host application.
13
+
14
+ ## Core contract
15
+
16
+ Given the same:
17
+
18
+ - initial world state,
19
+ - command stream,
20
+ - tick count, and
21
+ - engine version,
22
+
23
+ Ananke should produce the same outcome.
24
+
25
+ That makes it useful for replay, debugging, deterministic tests, lockstep-style simulation, audit trails, and host-controlled narrative or game systems.
26
+
27
+ ## Use Ananke when
28
+
29
+ - you need a reproducible simulation loop;
30
+ - you want host-owned rendering and persistence;
31
+ - you need deterministic fixtures for tests or replays;
32
+ - you want a stable root import surface for long-lived integrations;
33
+ - you are comfortable treating advanced subpath modules as version-pinned or experimental.
34
+
35
+ ## Do not use Ananke if
36
+
37
+ - you want a complete game engine;
38
+ - you need a visual editor;
39
+ - you want turnkey networking;
40
+ - you need a no-code simulation builder;
41
+ - you expect every exported internal module to be semver-stable.
42
+
43
+ ## 10-minute success path
44
+
45
+ ```bash
46
+ npm install
47
+ npm run build
48
+ npm run example:first-hour
49
+ npm run test:first-hour-smoke
50
+ ```
51
+
52
+ Then follow the first-hour adopter path:
53
+
54
+ ```text
55
+ docs/first-hour-adopter-path.md
56
+ ```
57
+
58
+ ## Minimal deterministic loop
59
+
60
+ ```ts
61
+ import {
62
+ createWorld,
63
+ q,
64
+ stepWorld,
65
+ type CommandMap,
66
+ } from "@its-not-rocket-science/ananke";
67
+
68
+ const world = createWorld(1337, [
69
+ {
70
+ id: 1,
71
+ teamId: 1,
72
+ seed: 10,
73
+ archetype: "KNIGHT_INFANTRY",
74
+ weaponId: "wpn_longsword",
75
+ armourId: "arm_mail",
76
+ x_m: -1.2,
77
+ },
78
+ {
79
+ id: 2,
80
+ teamId: 2,
81
+ seed: 11,
82
+ archetype: "HUMAN_BASE",
83
+ weaponId: "wpn_club",
84
+ x_m: 1.2,
85
+ },
86
+ ]);
87
+
88
+ const commands: CommandMap = new Map([
89
+ [1, [{ kind: "attackNearest", mode: "strike", intensity: q(1.0) }]],
90
+ [2, [{ kind: "attackNearest", mode: "strike", intensity: q(1.0) }]],
91
+ ]);
92
+
93
+ stepWorld(world, commands, { tractionCoeff: q(0.9) });
94
+ ```
95
+
96
+ ## Stable API promise
97
+
98
+ For semver stability, import from the package root only:
99
+
100
+ ```ts
101
+ import {
102
+ createWorld,
103
+ stepWorld,
104
+ q,
105
+ type CommandMap,
106
+ } from "@its-not-rocket-science/ananke";
107
+ ```
108
+
109
+ Tier-1 root exports are documented in:
110
+
111
+ - [`STABLE_API.md`](STABLE_API.md)
112
+ - [`docs/public-contract.md`](docs/public-contract.md)
113
+ - [`docs/stable-api-manifest.json`](docs/stable-api-manifest.json)
114
+
115
+ Subpath modules are shipped and supported, but are not part of the Tier-1 semver contract unless explicitly called out as stable.
116
+
117
+ ## What is stable today
118
+
119
+ Stable today means Tier-1 root exports from `@its-not-rocket-science/ananke`:
120
+
121
+ - fixed-point primitives and helpers (`q`, `SCALE`, related conversion and maths utilities);
122
+ - host-facing types (`Entity`, `WorldState`, `Command`, `CommandMap`, `KernelContext`);
123
+ - deterministic world and scenario entry points (`createWorld`, `loadScenario`, `validateScenario`);
124
+ - deterministic stepping (`stepWorld`);
125
+ - replay helpers (`ReplayRecorder`, `replayTo`, `serializeReplay`, `deserializeReplay`);
126
+ - bridge snapshot extraction (`extractRigSnapshots`, `deriveAnimationHints`).
127
+
128
+ If you need long-term compatibility, keep production integrations on this root Tier-1 surface.
129
+
130
+ ## What is shipped but not semver-stable
131
+
132
+ These surfaces are available, but outside the Tier-1 stability promise unless separately documented:
133
+
134
+ - most subpath modules in `package.json#exports`, including `./combat`, `./character`, `./tier2`, `./tier3`, `./netcode`, and `./host-loop`;
135
+ - emerging or advanced modules that may change shape between minor releases;
136
+ - exploratory integration helpers and higher-order systems.
137
+
138
+ Treat these areas as adopt-with-version-pinning.
139
+
140
+ ## Useful next files
141
+
142
+ - [`docs/PROJECT_STATUS.md`](docs/PROJECT_STATUS.md) plain-English status for visitors
143
+ - [`docs/host-contract.md`](docs/host-contract.md) host integration contract
144
+ - [`docs/bridge-contract.md`](docs/bridge-contract.md) — renderer bridge contract
145
+ - [`docs/support-boundaries.md`](docs/support-boundaries.md) — maintainer commitments and support boundaries
146
+ - [`docs/engineering-guarantees.md`](docs/engineering-guarantees.md) explicit engineering guarantees
147
+
148
+ ## Boundaries note
149
+
150
+ First-hour checks verify setup and basic deterministic behaviour. They do not validate your production integration. Host applications should keep their own regression suite and pin versions where deterministic behaviour is part of their product contract.