@its-not-rocket-science/ananke 0.1.0

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 (311) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/LICENSE +21 -0
  3. package/README.md +2199 -0
  4. package/STABLE_API.md +266 -0
  5. package/dist/src/anatomy/anatomy-compiler.d.ts +14 -0
  6. package/dist/src/anatomy/anatomy-compiler.js +277 -0
  7. package/dist/src/anatomy/anatomy-contracts.d.ts +94 -0
  8. package/dist/src/anatomy/anatomy-contracts.js +1 -0
  9. package/dist/src/anatomy/anatomy-helpers.d.ts +82 -0
  10. package/dist/src/anatomy/anatomy-helpers.js +233 -0
  11. package/dist/src/anatomy/anatomy-schema.d.ts +28 -0
  12. package/dist/src/anatomy/anatomy-schema.js +388 -0
  13. package/dist/src/anatomy/index.d.ts +4 -0
  14. package/dist/src/anatomy/index.js +4 -0
  15. package/dist/src/archetypes.d.ts +87 -0
  16. package/dist/src/archetypes.js +285 -0
  17. package/dist/src/arena.d.ts +173 -0
  18. package/dist/src/arena.js +695 -0
  19. package/dist/src/bridge/bridge-engine.d.ts +46 -0
  20. package/dist/src/bridge/bridge-engine.js +252 -0
  21. package/dist/src/bridge/index.d.ts +4 -0
  22. package/dist/src/bridge/index.js +5 -0
  23. package/dist/src/bridge/interpolation.d.ts +64 -0
  24. package/dist/src/bridge/interpolation.js +130 -0
  25. package/dist/src/bridge/mapping.d.ts +33 -0
  26. package/dist/src/bridge/mapping.js +54 -0
  27. package/dist/src/bridge/types.d.ts +94 -0
  28. package/dist/src/bridge/types.js +2 -0
  29. package/dist/src/campaign.d.ts +141 -0
  30. package/dist/src/campaign.js +235 -0
  31. package/dist/src/channels.d.ts +15 -0
  32. package/dist/src/channels.js +20 -0
  33. package/dist/src/chronicle.d.ts +124 -0
  34. package/dist/src/chronicle.js +232 -0
  35. package/dist/src/collective-activities.d.ts +154 -0
  36. package/dist/src/collective-activities.js +247 -0
  37. package/dist/src/competence/acoustic.d.ts +101 -0
  38. package/dist/src/competence/acoustic.js +242 -0
  39. package/dist/src/competence/catalogue.d.ts +30 -0
  40. package/dist/src/competence/catalogue.js +241 -0
  41. package/dist/src/competence/crafting.d.ts +35 -0
  42. package/dist/src/competence/crafting.js +88 -0
  43. package/dist/src/competence/engineering.d.ts +53 -0
  44. package/dist/src/competence/engineering.js +108 -0
  45. package/dist/src/competence/framework.d.ts +68 -0
  46. package/dist/src/competence/framework.js +694 -0
  47. package/dist/src/competence/index.d.ts +12 -0
  48. package/dist/src/competence/index.js +13 -0
  49. package/dist/src/competence/interspecies.d.ts +81 -0
  50. package/dist/src/competence/interspecies.js +108 -0
  51. package/dist/src/competence/language.d.ts +79 -0
  52. package/dist/src/competence/language.js +115 -0
  53. package/dist/src/competence/naturalist.d.ts +97 -0
  54. package/dist/src/competence/naturalist.js +187 -0
  55. package/dist/src/competence/navigation.d.ts +24 -0
  56. package/dist/src/competence/navigation.js +48 -0
  57. package/dist/src/competence/performance.d.ts +125 -0
  58. package/dist/src/competence/performance.js +210 -0
  59. package/dist/src/competence/teaching.d.ts +64 -0
  60. package/dist/src/competence/teaching.js +121 -0
  61. package/dist/src/competence/willpower.d.ts +74 -0
  62. package/dist/src/competence/willpower.js +114 -0
  63. package/dist/src/crafting/index.d.ts +55 -0
  64. package/dist/src/crafting/index.js +229 -0
  65. package/dist/src/crafting/manufacturing.d.ts +83 -0
  66. package/dist/src/crafting/manufacturing.js +165 -0
  67. package/dist/src/crafting/materials.d.ts +53 -0
  68. package/dist/src/crafting/materials.js +120 -0
  69. package/dist/src/crafting/recipes.d.ts +75 -0
  70. package/dist/src/crafting/recipes.js +233 -0
  71. package/dist/src/crafting/workshops.d.ts +61 -0
  72. package/dist/src/crafting/workshops.js +170 -0
  73. package/dist/src/debug.d.ts +86 -0
  74. package/dist/src/debug.js +76 -0
  75. package/dist/src/derive.d.ts +21 -0
  76. package/dist/src/derive.js +88 -0
  77. package/dist/src/describe.d.ts +29 -0
  78. package/dist/src/describe.js +276 -0
  79. package/dist/src/dialogue.d.ts +122 -0
  80. package/dist/src/dialogue.js +266 -0
  81. package/dist/src/dist.d.ts +20 -0
  82. package/dist/src/dist.js +39 -0
  83. package/dist/src/downtime.d.ts +89 -0
  84. package/dist/src/downtime.js +391 -0
  85. package/dist/src/economy.d.ts +116 -0
  86. package/dist/src/economy.js +182 -0
  87. package/dist/src/emotional-contagion.d.ts +142 -0
  88. package/dist/src/emotional-contagion.js +274 -0
  89. package/dist/src/equipment.d.ts +206 -0
  90. package/dist/src/equipment.js +598 -0
  91. package/dist/src/faction.d.ts +102 -0
  92. package/dist/src/faction.js +237 -0
  93. package/dist/src/generate.d.ts +35 -0
  94. package/dist/src/generate.js +166 -0
  95. package/dist/src/index.d.ts +42 -0
  96. package/dist/src/index.js +54 -0
  97. package/dist/src/inheritance.d.ts +69 -0
  98. package/dist/src/inheritance.js +136 -0
  99. package/dist/src/inventory.d.ts +194 -0
  100. package/dist/src/inventory.js +637 -0
  101. package/dist/src/item-durability.d.ts +69 -0
  102. package/dist/src/item-durability.js +308 -0
  103. package/dist/src/legend.d.ts +97 -0
  104. package/dist/src/legend.js +269 -0
  105. package/dist/src/lod.d.ts +9 -0
  106. package/dist/src/lod.js +84 -0
  107. package/dist/src/metrics.d.ts +51 -0
  108. package/dist/src/metrics.js +91 -0
  109. package/dist/src/model3d.d.ts +138 -0
  110. package/dist/src/model3d.js +214 -0
  111. package/dist/src/mythology.d.ts +101 -0
  112. package/dist/src/mythology.js +308 -0
  113. package/dist/src/narrative-render.d.ts +42 -0
  114. package/dist/src/narrative-render.js +194 -0
  115. package/dist/src/narrative-stress.d.ts +123 -0
  116. package/dist/src/narrative-stress.js +183 -0
  117. package/dist/src/narrative.d.ts +44 -0
  118. package/dist/src/narrative.js +257 -0
  119. package/dist/src/party.d.ts +70 -0
  120. package/dist/src/party.js +226 -0
  121. package/dist/src/polity.d.ts +262 -0
  122. package/dist/src/polity.js +398 -0
  123. package/dist/src/presets.d.ts +42 -0
  124. package/dist/src/presets.js +170 -0
  125. package/dist/src/progression.d.ts +170 -0
  126. package/dist/src/progression.js +256 -0
  127. package/dist/src/quest-generators.d.ts +76 -0
  128. package/dist/src/quest-generators.js +534 -0
  129. package/dist/src/quest.d.ts +239 -0
  130. package/dist/src/quest.js +520 -0
  131. package/dist/src/relationships-effects.d.ts +75 -0
  132. package/dist/src/relationships-effects.js +219 -0
  133. package/dist/src/relationships.d.ts +104 -0
  134. package/dist/src/relationships.js +347 -0
  135. package/dist/src/replay.d.ts +47 -0
  136. package/dist/src/replay.js +82 -0
  137. package/dist/src/rng.d.ts +9 -0
  138. package/dist/src/rng.js +37 -0
  139. package/dist/src/settlement-services.d.ts +67 -0
  140. package/dist/src/settlement-services.js +267 -0
  141. package/dist/src/settlement.d.ts +143 -0
  142. package/dist/src/settlement.js +419 -0
  143. package/dist/src/sim/action.d.ts +28 -0
  144. package/dist/src/sim/action.js +12 -0
  145. package/dist/src/sim/aging.d.ts +95 -0
  146. package/dist/src/sim/aging.js +243 -0
  147. package/dist/src/sim/ai/decide.d.ts +10 -0
  148. package/dist/src/sim/ai/decide.js +267 -0
  149. package/dist/src/sim/ai/perception.d.ts +12 -0
  150. package/dist/src/sim/ai/perception.js +54 -0
  151. package/dist/src/sim/ai/personality.d.ts +54 -0
  152. package/dist/src/sim/ai/personality.js +202 -0
  153. package/dist/src/sim/ai/presets.d.ts +2 -0
  154. package/dist/src/sim/ai/presets.js +28 -0
  155. package/dist/src/sim/ai/system.d.ts +6 -0
  156. package/dist/src/sim/ai/system.js +13 -0
  157. package/dist/src/sim/ai/targeting.d.ts +8 -0
  158. package/dist/src/sim/ai/targeting.js +42 -0
  159. package/dist/src/sim/ai/types.d.ts +14 -0
  160. package/dist/src/sim/ai/types.js +1 -0
  161. package/dist/src/sim/body.d.ts +9 -0
  162. package/dist/src/sim/body.js +32 -0
  163. package/dist/src/sim/bodyplan.d.ts +161 -0
  164. package/dist/src/sim/bodyplan.js +677 -0
  165. package/dist/src/sim/capability.d.ts +135 -0
  166. package/dist/src/sim/capability.js +8 -0
  167. package/dist/src/sim/combat.d.ts +21 -0
  168. package/dist/src/sim/combat.js +77 -0
  169. package/dist/src/sim/commandBuilders.d.ts +11 -0
  170. package/dist/src/sim/commandBuilders.js +39 -0
  171. package/dist/src/sim/commands.d.ts +71 -0
  172. package/dist/src/sim/commands.js +8 -0
  173. package/dist/src/sim/condition.d.ts +35 -0
  174. package/dist/src/sim/condition.js +21 -0
  175. package/dist/src/sim/cone.d.ts +40 -0
  176. package/dist/src/sim/cone.js +44 -0
  177. package/dist/src/sim/context.d.ts +68 -0
  178. package/dist/src/sim/context.js +1 -0
  179. package/dist/src/sim/density.d.ts +14 -0
  180. package/dist/src/sim/density.js +33 -0
  181. package/dist/src/sim/disease.d.ts +141 -0
  182. package/dist/src/sim/disease.js +353 -0
  183. package/dist/src/sim/entity.d.ts +251 -0
  184. package/dist/src/sim/entity.js +19 -0
  185. package/dist/src/sim/events.d.ts +25 -0
  186. package/dist/src/sim/events.js +5 -0
  187. package/dist/src/sim/explosion.d.ts +40 -0
  188. package/dist/src/sim/explosion.js +40 -0
  189. package/dist/src/sim/formation-unit.d.ts +138 -0
  190. package/dist/src/sim/formation-unit.js +197 -0
  191. package/dist/src/sim/formation.d.ts +12 -0
  192. package/dist/src/sim/formation.js +54 -0
  193. package/dist/src/sim/frontage.d.ts +30 -0
  194. package/dist/src/sim/frontage.js +84 -0
  195. package/dist/src/sim/grapple.d.ts +100 -0
  196. package/dist/src/sim/grapple.js +480 -0
  197. package/dist/src/sim/hazard.d.ts +104 -0
  198. package/dist/src/sim/hazard.js +201 -0
  199. package/dist/src/sim/hydrostatic.d.ts +58 -0
  200. package/dist/src/sim/hydrostatic.js +117 -0
  201. package/dist/src/sim/impairment.d.ts +20 -0
  202. package/dist/src/sim/impairment.js +162 -0
  203. package/dist/src/sim/indexing.d.ts +7 -0
  204. package/dist/src/sim/indexing.js +7 -0
  205. package/dist/src/sim/injury.d.ts +54 -0
  206. package/dist/src/sim/injury.js +66 -0
  207. package/dist/src/sim/intent.d.ts +26 -0
  208. package/dist/src/sim/intent.js +7 -0
  209. package/dist/src/sim/kernel.d.ts +45 -0
  210. package/dist/src/sim/kernel.js +1992 -0
  211. package/dist/src/sim/kinds.d.ts +64 -0
  212. package/dist/src/sim/kinds.js +56 -0
  213. package/dist/src/sim/knockback.d.ts +50 -0
  214. package/dist/src/sim/knockback.js +82 -0
  215. package/dist/src/sim/limb.d.ts +48 -0
  216. package/dist/src/sim/limb.js +78 -0
  217. package/dist/src/sim/medical.d.ts +32 -0
  218. package/dist/src/sim/medical.js +33 -0
  219. package/dist/src/sim/morale.d.ts +69 -0
  220. package/dist/src/sim/morale.js +92 -0
  221. package/dist/src/sim/mount.d.ts +150 -0
  222. package/dist/src/sim/mount.js +225 -0
  223. package/dist/src/sim/nutrition.d.ts +74 -0
  224. package/dist/src/sim/nutrition.js +168 -0
  225. package/dist/src/sim/occlusion.d.ts +8 -0
  226. package/dist/src/sim/occlusion.js +71 -0
  227. package/dist/src/sim/push.d.ts +11 -0
  228. package/dist/src/sim/push.js +79 -0
  229. package/dist/src/sim/ranged.d.ts +44 -0
  230. package/dist/src/sim/ranged.js +69 -0
  231. package/dist/src/sim/seeds.d.ts +3 -0
  232. package/dist/src/sim/seeds.js +16 -0
  233. package/dist/src/sim/sensory-extended.d.ts +103 -0
  234. package/dist/src/sim/sensory-extended.js +181 -0
  235. package/dist/src/sim/sensory.d.ts +38 -0
  236. package/dist/src/sim/sensory.js +109 -0
  237. package/dist/src/sim/skills.d.ts +70 -0
  238. package/dist/src/sim/skills.js +69 -0
  239. package/dist/src/sim/sleep.d.ts +107 -0
  240. package/dist/src/sim/sleep.js +215 -0
  241. package/dist/src/sim/spatial.d.ts +8 -0
  242. package/dist/src/sim/spatial.js +59 -0
  243. package/dist/src/sim/step/capability.d.ts +8 -0
  244. package/dist/src/sim/step/capability.js +77 -0
  245. package/dist/src/sim/step/concentration.d.ts +9 -0
  246. package/dist/src/sim/step/concentration.js +25 -0
  247. package/dist/src/sim/step/effects.d.ts +17 -0
  248. package/dist/src/sim/step/effects.js +96 -0
  249. package/dist/src/sim/step/energy.d.ts +3 -0
  250. package/dist/src/sim/step/energy.js +31 -0
  251. package/dist/src/sim/step/hazards.d.ts +4 -0
  252. package/dist/src/sim/step/hazards.js +19 -0
  253. package/dist/src/sim/step/injury.d.ts +10 -0
  254. package/dist/src/sim/step/injury.js +353 -0
  255. package/dist/src/sim/step/morale.d.ts +11 -0
  256. package/dist/src/sim/step/morale.js +130 -0
  257. package/dist/src/sim/step/movement.d.ts +5 -0
  258. package/dist/src/sim/step/movement.js +172 -0
  259. package/dist/src/sim/step/push.d.ts +11 -0
  260. package/dist/src/sim/step/push.js +79 -0
  261. package/dist/src/sim/step/substances.d.ts +3 -0
  262. package/dist/src/sim/step/substances.js +75 -0
  263. package/dist/src/sim/substance.d.ts +38 -0
  264. package/dist/src/sim/substance.js +57 -0
  265. package/dist/src/sim/systemic-toxicology.d.ts +109 -0
  266. package/dist/src/sim/systemic-toxicology.js +263 -0
  267. package/dist/src/sim/team.d.ts +9 -0
  268. package/dist/src/sim/team.js +37 -0
  269. package/dist/src/sim/tech.d.ts +36 -0
  270. package/dist/src/sim/tech.js +46 -0
  271. package/dist/src/sim/terrain.d.ts +121 -0
  272. package/dist/src/sim/terrain.js +141 -0
  273. package/dist/src/sim/testing.d.ts +13 -0
  274. package/dist/src/sim/testing.js +100 -0
  275. package/dist/src/sim/thermoregulation.d.ts +77 -0
  276. package/dist/src/sim/thermoregulation.js +161 -0
  277. package/dist/src/sim/tick.d.ts +3 -0
  278. package/dist/src/sim/tick.js +3 -0
  279. package/dist/src/sim/toxicology.d.ts +52 -0
  280. package/dist/src/sim/toxicology.js +104 -0
  281. package/dist/src/sim/trace.d.ts +141 -0
  282. package/dist/src/sim/trace.js +1 -0
  283. package/dist/src/sim/tuning.d.ts +16 -0
  284. package/dist/src/sim/tuning.js +42 -0
  285. package/dist/src/sim/vec3.d.ts +14 -0
  286. package/dist/src/sim/vec3.js +31 -0
  287. package/dist/src/sim/weapon_dynamics.d.ts +102 -0
  288. package/dist/src/sim/weapon_dynamics.js +142 -0
  289. package/dist/src/sim/weather.d.ts +95 -0
  290. package/dist/src/sim/weather.js +105 -0
  291. package/dist/src/sim/world.d.ts +52 -0
  292. package/dist/src/sim/world.js +1 -0
  293. package/dist/src/sim/wound-aging.d.ts +120 -0
  294. package/dist/src/sim/wound-aging.js +223 -0
  295. package/dist/src/species.d.ts +106 -0
  296. package/dist/src/species.js +664 -0
  297. package/dist/src/story-arcs.d.ts +17 -0
  298. package/dist/src/story-arcs.js +276 -0
  299. package/dist/src/tech-diffusion.d.ts +80 -0
  300. package/dist/src/tech-diffusion.js +185 -0
  301. package/dist/src/traits.d.ts +25 -0
  302. package/dist/src/traits.js +178 -0
  303. package/dist/src/types.d.ts +117 -0
  304. package/dist/src/types.js +1 -0
  305. package/dist/src/units.d.ts +41 -0
  306. package/dist/src/units.js +64 -0
  307. package/dist/src/weapons.d.ts +20 -0
  308. package/dist/src/weapons.js +824 -0
  309. package/dist/src/world-generation.d.ts +52 -0
  310. package/dist/src/world-generation.js +301 -0
  311. package/package.json +74 -0
package/README.md ADDED
@@ -0,0 +1,2199 @@
1
+ # Ananke
2
+ ![CI](../../actions/workflows/ci.yml/badge.svg)
3
+
4
+ **Ananke** is a deterministic, physics-grounded simulation engine for characters, combat,
5
+ and survivability — from a single duel to a campaign-scale world.
6
+
7
+ It models entities using **real physical quantities** rather than abstract hit points,
8
+ using **SI units stored as fixed-point integers** for full determinism.
9
+ Same seed + same inputs → identical results, at every scale.
10
+
11
+ The core promise is narrow and hard: deterministic, physics-grounded simulation for
12
+ characters, combat, and survivability across tactical-to-campaign scales.
13
+ Everything else — world simulation, mythology, emotional contagion, tech diffusion — is
14
+ built on top of that foundation and can be adopted independently.
15
+
16
+ ---
17
+
18
+ ## Start here: three adoption paths
19
+
20
+ Choose the path that matches your immediate need. Ignore the rest until you need it.
21
+
22
+ ### Path A — "I want a deterministic combat kernel"
23
+
24
+ **Minimal modules:** `src/units.ts`, `src/sim/kernel.ts`, `src/presets.ts`, `src/weapons.ts`
25
+
26
+ **30-minute quickstart:** Run `tools/vertical-slice.ts` (`npm run run:vertical-slice`).
27
+ A Knight fights a Brawler across three seeds, producing a physics-grounded combat log.
28
+ Read `docs/host-contract.md` for the stable integration surface, then
29
+ `docs/integration-primer.md` for data-flow diagrams and type glossary.
30
+
31
+ **Key entry points:** `stepWorld()`, `resolveHit()`, `generateIndividual()`, `mkKnight()`
32
+
33
+ **What to ignore for now:** Phases 56–67 (campaign-scale systems), all `tools/` except `vertical-slice.ts`
34
+
35
+ ---
36
+
37
+ ### Path B — "I want a campaign / world simulation"
38
+
39
+ **Minimal modules:** Path A + `src/campaign.ts`, `src/polity.ts`, `src/tech-diffusion.ts`
40
+
41
+ **30-minute quickstart:** Run `npm run run:what-if` to see alternate-history polity scenarios
42
+ (plague, charismatic leader, sudden war) with probability-weighted outcome distributions.
43
+
44
+ **Key entry points:** `stepPolityDay()`, `stepTechDiffusion()`, `applyEmotionalContagion()`,
45
+ `compressMythsFromHistory()`
46
+
47
+ **What to ignore for now:** `src/sim/model3d.ts`, `src/bridge/`, `docs/editors/`
48
+
49
+ ---
50
+
51
+ ### Path C — "I want physiology / species modelling"
52
+
53
+ **Minimal modules:** `src/units.ts`, `src/sim/bodyplan.ts`, `src/archetypes.ts`,
54
+ `src/describe.ts`
55
+
56
+ **30-minute quickstart:** Open `docs/editors/species-forge.html` in a browser.
57
+ Design a body plan, set archetype sliders, and copy the generated TypeScript trio
58
+ (`BodyPlan` + `Archetype` + `NarrativeBias`) into your project.
59
+
60
+ **Key entry points:** `generateIndividual()`, `applyAgingToAttributes()`,
61
+ `applySleepToAttributes()`, `extractRigSnapshots()`
62
+
63
+ **What to ignore for now:** `src/polity.ts`, `src/tech-diffusion.ts`, all `tools/`
64
+
65
+ ---
66
+
67
+ ### Where Ananke sits in a larger system
68
+
69
+ Ananke is the physics and biology layer — Layer 2 in the stack below. Layers above it
70
+ are partially or fully implemented; Layer 7 is long-term vision.
71
+
72
+ | Layer | Purpose | Status |
73
+ |-------|---------|--------|
74
+ | **7 — The Universe** | Cosmological scale: planets, interstellar travel, multiple worlds | Long-term vision |
75
+ | **6 — The World** | Geopolitical scale: nations, empires, diplomacy, trade, war, tech diffusion | **Complete** (Phases 61, 67) |
76
+ | **5 — The Society** | Cultural scale: cities, factions, myths, legends, mass psychology | **Complete** (Phases 36, 45, 50, 66) |
77
+ | **4 — The Group** | Social scale: parties, armies, organisations, emotional contagion | **Complete** (Phases 22, 48, 51, 65) |
78
+ | **3 — The Individual** | Character scale: generation, narrative shaping, aging, sleep, skill progression | **Complete** (Phases 21, 33–39, 57–58, 62) |
79
+ | **2 — The Simulation** | Physics and biology kernel | **Complete** (Phases 1–60) |
80
+ | **1 — The Interface** | Visual editors, Species Forge, Culture Forge, Simulation Zoo, Generative Cartography, Persistent World Server, "What If?" engine, validation dashboard, Narrative Stress Test | **Complete** (ROADMAP items 7–11, Long-Term Vision all four; companion renderer READMEs in `docs/companion-projects/`) |
81
+
82
+ The kernel's deterministic, physics-first foundation makes it suited to the full stack. A
83
+ scenario that would require hand-waving in a narrative RPG — "does the hero survive this
84
+ battle?" — becomes a distribution of outcomes across thousands of seeded runs, each
85
+ physically grounded.
86
+
87
+ ---
88
+
89
+ ## Core design principles
90
+
91
+ ### Deterministic by default
92
+
93
+ - Lockstep-safe simulation
94
+ - No floating-point drift
95
+ - Stable ordering and RNG consumption
96
+ - Same seed + inputs → identical results
97
+ - Entity iteration and pair resolution are stable and ordered
98
+
99
+ ### Physics-first
100
+
101
+ - Impact energy → injury (not dice roll → damage)
102
+ - Mass, velocity, leverage, penetration physics
103
+ - Encumbrance affects movement and fatigue
104
+
105
+ ### Fixed-point arithmetic
106
+
107
+ - All simulation values use Q fixed-point integers
108
+ - No runtime floats in simulation path
109
+ - Units centralised in `src/units.ts`
110
+
111
+ ### No arbitrary scales
112
+
113
+ Individual variation is expressed in absolute physical terms, not 1–20 ranges.
114
+
115
+ | What varies | Engine representation | Unit |
116
+ |---|---|---|
117
+ | Strength | `peakForce_N` | N (newtons) |
118
+ | Explosive power | `peakPower_W` | W (watts) |
119
+ | Stamina | `continuousPower_W` | W (watts) |
120
+ | Energy reserves | `reserveEnergy_J` | J (joules) |
121
+ | Reaction speed | `reactionTime_s` | s (seconds) |
122
+ | Motor coordination | `controlQuality`, `fineControl` | dimensionless 0–1 |
123
+ | Body size | `stature_m`, `mass_kg` | m, kg |
124
+
125
+ An "average human" has `peakForce_N = 1840`, `peakPower_W = 1200`, `continuousPower_W = 200`,
126
+ `reserveEnergy_J = 20000`, `reactionTime_s = 0.20`. These are real sport-science values.
127
+ Variation between individuals is generated by `generateIndividual()` using per-archetype
128
+ variance distributions, producing a unique entity with realistic physical spread.
129
+
130
+ ### Biology-agnostic
131
+
132
+ - Supports humans, robots, aliens, and abstract entities
133
+ - Genericised actuation, structure, and control systems
134
+ - Region-based anatomy adaptable to any morphology (Phase 8)
135
+
136
+ ### Scalable
137
+
138
+ - Single entity to squad to formation to army
139
+ - Spatial partitioning and density modelling
140
+ - Deterministic large-battle simulation
141
+
142
+ ---
143
+
144
+ ## Current implementation status
145
+
146
+ **Phases 1–60 complete** (including Phase 6 Formation System, 2ext, 3ext, 8B, 8C, 10B, 10C, 11C, 12B, 31–60). Melee combat,
147
+ grappling, stamina and exhaustion, weapon dynamics (including swing momentum carry), ranged
148
+ and projectile combat (including aiming time, moving target penalty, suppression→AI behaviour,
149
+ and ammo type overrides), injury, entity environmental hazards, movement physics, formation
150
+ basics, deterministic AI scaffolding,
151
+ perception/cognition (sensory model, decision latency, surprise mechanics), morale and
152
+ psychological state (fear accumulation, routing, panic variety, leader/banner auras, rally
153
+ mechanic), terrain systems (surface friction, obstacle/cover grids, elevation, slope direction,
154
+ dynamic hazard cells, AI cover-seeking, cover morale bonus, elevation melee advantage), a
155
+ physics-grounded skill system, a universal data-driven body plan system (humanoid, quadruped,
156
+ theropod, sauropod, avian, vermiform, centaur, octopoid — adding a new species requires only a
157
+ BodyPlan data file and an Archetype baseline, no kernel changes), a full injury and medical
158
+ simulation layer (fractures, infection, permanent damage, natural clotting, fatal fluid loss,
159
+ and a `TreatCommand` system with tiered medical equipment and skill-scaled treatment rates),
160
+ environmental physics including blast and fragmentation explosions, fall damage,
161
+ pharmacokinetics with substance interactions (stimulant/haemostatic/anaesthetic/poison
162
+ cross-effects, temperature-dependent metabolism), flash blindness from explosions, ambient
163
+ temperature stress, a technology spectrum system (`TechContext`, `TechEra`,
164
+ `TechCapability`, `validateLoadout`) with powered exoskeleton items, energy weapons,
165
+ reflective and ablative armour, sensor items, and era-gated starter items, a **capability
166
+ sources and effects** system (Clarke's Third Law — magic and advanced technology are the
167
+ same abstraction; only the tags differ), a **deterministic replay and analytics** system
168
+ (`ReplayRecorder`, `replayTo`, `serializeReplay`/`deserializeReplay`, `CollectingTrace`,
169
+ `collectMetrics`, `survivalRate`, `meanTimeToIncapacitation`), a **3D model integration
170
+ layer** (`deriveMassDistribution`, `deriveInertiaTensor`, `deriveAnimationHints`,
171
+ `derivePoseModifiers`, `deriveGrappleConstraint`, `extractRigSnapshots`) for driving
172
+ host renderer rigs from simulation state, a **named archetype and scenario library**
173
+ (`AMATEUR_BOXER`, `PRO_BOXER`, `GRECO_WRESTLER`, `KNIGHT_INFANTRY`, `LARGE_PACIFIC_OCTOPUS`
174
+ with corresponding `mkBoxer`/`mkWrestler`/`mkKnight`/`mkOctopus`/`mkScubaDiver` factory
175
+ functions in `src/presets.ts`) validated against real-world biomechanics data by a
176
+ statistical scenario test suite, a **character description layer** (`describeCharacter`,
177
+ `formatCharacterSheet`, `formatOneLine` in `src/describe.ts`) that translates SI fixed-point
178
+ attributes into human-readable summaries with tier ratings, labelled comparisons, and plain
179
+ English descriptions grounded in real-world benchmarks, a **historical weapons database**
180
+ (`src/weapons.ts`) covering ~70 weapons across six eras (Prehistoric through Contemporary)
181
+ with two combat extensions: flexible/chain weapon **shield bypass** (`shieldBypassQ` reduces
182
+ effective blocking coverage for flails and morning stars) and **magazine tracking**
183
+ (`magCapacity` + `shotInterval_s` give magazine firearms per-shot and reload cooldowns), a
184
+ **combat narrative layer** (`src/narrative.ts`) that converts trace event streams into
185
+ human-readable combat logs with configurable verbosity, physics-grounded verb selection, and
186
+ injury/outcome summaries, a **downtime and recovery simulation** (`src/downtime.ts`) that
187
+ bridges combat-resolution time (20 Hz) and campaign time (hours to weeks) via a compressed
188
+ 1 Hz loop with tiered care levels, resource tracking, and recovery projection, an **arena
189
+ simulation framework** (`src/arena.ts`) providing a declarative scenario DSL, batch trial
190
+ runner, expectation system, and six physics-calibrated built-in scenarios for validating
191
+ simulation realism, and a **character progression system** (`src/progression.ts`) that adds
192
+ the temporal axis to entity attributes: XP/milestone-driven skill advancement using geometric
193
+ thresholds, physical training drift bounded by genetic ceiling, physiologically-grounded
194
+ ageing curves, and permanent injury sequelae, and a **campaign and world state layer**
195
+ (`src/campaign.ts`) that persists entity state, location, and inventory between sessions:
196
+ world clock advancement with integrated downtime healing, a location registry with
197
+ travelCost routing, campaign-level item stockpiles, and Map-aware JSON serialisation so
198
+ full campaign state round-trips cleanly, and a **dialogue and negotiation layer**
199
+ (`src/dialogue.ts`) that resolves non-combat social actions — intimidation, persuasion,
200
+ deception, surrender, and trade negotiation — using the same physical and psychological
201
+ attributes as the combat engine: intimidation strength derives from `peakForce_N`, deception
202
+ is defeated by `attentionDepth`, and escalation triggers when a fearless target interprets
203
+ an intimidation attempt as an insult, and a **faction and reputation system**
204
+ (`src/faction.ts`) that tracks faction membership, inter-faction standing, entity reputation,
205
+ and a witness system that propagates reputation deltas from combat events: kills reduce the
206
+ actor's standing with the victim's faction, aid increases it, and the AI decision layer
207
+ suppresses attacks on entities with standing above `STANDING_FRIENDLY_THRESHOLD`, with a
208
+ self-defence override when the attacker has already taken damage, and a **loot and economy
209
+ layer** (`src/economy.ts`) that adds item valuation, equipment degradation, drop resolution,
210
+ and trade mechanics: `computeItemValue` derives cost-unit prices from physical properties
211
+ (mass, reach, armour resist), `applyWear` tracks cumulative use-wear on melee weapons
212
+ (penalty at q(0.30), fumble at q(0.70), breaks at q(1.0)), `resolveDrops` produces
213
+ deterministic loot from dead or incapacitated entities, and `evaluateTradeOffer` evaluates
214
+ trades from the accepting party's perspective, a **momentum transfer and knockback**
215
+ layer (`src/sim/knockback.ts`) that converts impulse-momentum physics into stagger and
216
+ prone transitions, a **hydrostatic shock and cavitation** layer (`src/sim/hydrostatic.ts`)
217
+ that amplifies internal damage for high-velocity projectiles based on per-tissue compliance
218
+ (temporary cavity ×1.0–×3.0 above 600 m/s; cavitation bleed boost in fluid-saturated organs
219
+ above 900 m/s), and a **cone AoE** system (`src/sim/cone.ts`) that adds directional
220
+ geometry to the capability system for breath weapons, flamethrowers, gas dispersal, and
221
+ sonic blasts: `entityInCone` geometry with configurable half-angle and range, `"facing"` and
222
+ `"fixed"` direction modes, sustained emission with per-tick cost deduction and shock interrupt,
223
+ a `weaponImpact` payload variant for custom per-effect damage profiles, and a full
224
+ dragon-vs-knight scenario demonstration, a **staged thermoregulation model**
225
+ (`src/sim/thermoregulation.ts`) that tracks core body temperature using a heat-balance
226
+ equation (metabolic heat vs. conductive loss through armour and skin), deriving performance
227
+ modifiers across seven stages from mild hyperthermia to cardiac-arrest hypothermia — each
228
+ with calibrated powerMul, fineControlPen, and latencyMul penalties, and a **nutrition and
229
+ starvation model** (`src/sim/nutrition.ts`) that adds the longest survivability axis:
230
+ caloric balance tracked via Kleiber's-law BMR, four hunger states (sated → hungry →
231
+ starving → critical) with staged fat catabolism (300 g/day), muscle catabolism (0.5 N/hour
232
+ reduction in peakForce_N), and a six-item food catalogue from ration bars to water flasks,
233
+ a **species and race system** (`src/species.ts`) providing a data-driven species registry
234
+ where `SpeciesDefinition` bundles archetype, body plan, innate traits, capabilities, natural
235
+ weapons, and physiological overrides into one record: 14 species across fantasy humanoids
236
+ (elf, dwarf, halfling, orc, ogre, goblin, troll), sci-fi humanoids (Vulcan, Klingon, Romulan),
237
+ mythological (dragon, centaur, satyr), and fictional (Heechee); `SpeciesPhysiology` adds
238
+ `coldBlooded` (skips thermoregulation), `bmrMultiplier` (Vulcan q(0.72) starves slower;
239
+ orc q(1.15) starves faster), and `naturalInsulation_m2KW` (scales heat loss), and a
240
+ **multiple intelligences layer** (`src/types.ts`, wired through targeting, morale, decide,
241
+ and dialogue) implementing Howard Gardner's nine cognitive domains plus inter-species empathy
242
+ as Q-coded attributes on every archetype and species: `spatial` scales the threat-horizon
243
+ perception radius (human baseline q(0.60) → ×1.0; octopus q(0.90) → ×1.30),
244
+ `logicalMathematical` reduces decision latency (Vulcan q(0.95) → ×0.82; ogre q(0.25) →
245
+ ×1.10), `interpersonal` scales the effective leader-aura reception radius, `intrapersonal`
246
+ boosts effective distress tolerance in morale computations, `linguistic` sets the per-entity
247
+ persuasion base probability in dialogue (elf q(0.80) → q(0.44); ogre q(0.25) → q(0.275)),
248
+ and `bodilyKinesthetic` sets a floor on fine-motor precision (`fineControl ≥ bk × q(0.80)`).
249
+
250
+ **Phase 34** extends `bodilyKinesthetic` and `spatial` into non-combat competence resolvers
251
+ (`src/competence/`): `resolveCrafting(entity, spec, seed)` computes craft quality
252
+ (materialQ × BK × fineControl-bonus × toolBonus ± RNG) and time (baseTime_s × q(0.50) / BK);
253
+ `computeSurgicalPrecision(entity)` returns lerp(q(0.70), q(1.30), BK) for use as
254
+ `TreatmentSchedule.surgicalPrecisionMul` in downtime surgery; `resolveNavigation(entity, spec, seed)`
255
+ gives routeEfficiency = clamp(spatial × mapBonus × terrainQ × visibilityQ, q(0.50), q(1.0)) and
256
+ the resulting timeLost_s.
257
+
258
+ **Phase 35** adds the `naturalist` intelligence competence resolver (`src/competence/naturalist.ts`):
259
+ `resolveTracking(entity, spec, seed)` computes track confidence from naturalist skill, track age,
260
+ terrain preservation (ideal/rain/urban/deep_water/forest), and species-specific difficulty;
261
+ `resolveForaging(entity, spec, seed)` yields itemsFound per hour by biome (forest/plains/desert/
262
+ swamp/mountain) and season with misidentification risk (P_misidentify = max(0, 0.30 − naturalist × 0.40));
263
+ `resolveTaming(entity, spec, seed)` computes trust_Q = naturalist × interSpecies × effort − fear × 0.50,
264
+ with attack probability when fear exceeds trust.
265
+
266
+ **Phase 36** adds inter-species intelligence (`src/competence/interspecies.ts`): the `signal` dialogue
267
+ action for cross-species communication (`{ kind: "signal"; targetSpecies; intent: "calm" | "submit" | "ally" | "territory" }`)
268
+ with success probability `empathy_Q × vocab × (1 − fear × 0.60)` and potential aggravation when low
269
+ empathy meets fearful targets; `computeUnfamiliarSpeciesLatencyPenalty(entity, species)` returns
270
+ up to +80 ms decision latency when facing unfamiliar species (those not in `speciesAffinity`).
271
+
272
+ **Phase 37** extends linguistic and interpersonal intelligence into practical applications:
273
+ `LanguageCapacity` (languageId + fluency_Q) added to `Entity.languages`; `resolveCommandTransmission`
274
+ computes formation reception rate (`linguistic × formationBonus`) and transmission delay;
275
+ `resolveTeaching` computes XP transfer (`hours × BASE_XP_RATE × interpersonalTeacher × interpersonalLearner`)
276
+ with teacher fatigue; `computeDeceptionDetectionProbability` factors interpersonal intuition into
277
+ detecting lies (`attentionDepth × 0.50 + interpersonal × 0.50 − plausibility`).
278
+
279
+ **Phase 38** implements logical-mathematical and intrapersonal intelligence: `WillpowerState`
280
+ (`current_J/max_J`) added to `Entity.willpower`; `computeMaxWillpower(entity)` calculates capacity
281
+ from intrapersonal intelligence (max 50kJ); `deductWillpower()`, `replenishWillpower()`, and
282
+ `stepConcentrationWillpower()` manage cognitive stamina for sustained concentration (Phase 12B);
283
+ `resolveEngineering(entity, spec, seed)` computes project quality (`qualityMul = logicalMath ×
284
+ complexityMul × timeFactor`) with latent flaw probability (`P_flaw = max(0, complexity − logicalMath) × 0.40`);
285
+ descriptor bands: exceptional/good/adequate/poor/failure.
286
+
287
+ **Phase 39** adds musical intelligence and acoustic systems: `deriveAcousticSignature(entity)`
288
+ computes noise level from movement velocity, equipment (metal armour +15, heavy weapons +10),
289
+ and stealth skill; `detectAcousticSignature(listener, source, dist_m)` returns detection confidence
290
+ (`sourceNoise / dist_m × listener.musical × SCALE_ACOUSTIC`); `resolveFormationSignal(signaller,
291
+ signal, listener, dist_m)` computes command clarity (`musical(signaller) × musical(listener) ×
292
+ rangeFactor`) for military coordination (drums, horns); `resolvePerformance(performer, spec)`
293
+ generates morale auras (`fearDecayBonus = musical × q(0.020)`) draining willpower per second;
294
+ performance types: march/rally/dirge/celebration/lament.
295
+
296
+ **Phase 47** adds individual AI personalities beyond policy presets (`src/sim/ai/personality.ts`):
297
+ `PersonalityTraits` — four Q-coded axes: `aggression` (shifts retreat range, overrides hesitation
298
+ above q(0.70)), `caution` (±q(0.20) defence intensity boost), `loyalty` (switches focus target to
299
+ protect a distressed ally), `opportunism` (switches to the most-wounded enemy). Five named presets:
300
+ `berserker`, `coward`, `guardian`, `schemer`, `soldier`. `derivePersonalityFromCognition()` maps
301
+ distressTolerance → aggression, intrapersonal → caution, interpersonal → loyalty,
302
+ logicalMathematical → opportunism. `entity.personality?: PersonalityTraits` is optional and
303
+ backward-compatible (absent = neutral = q(0.50) on all axes, no behaviour change).
304
+
305
+ **Phase 48** adds multi-party dynamics (`src/party.ts`): `Party` and `PartyMember` types with
306
+ per-member `loyaltyQ`, `stepPartyLoyalty()` (leader interpersonal aura + resource/injury/hunger
307
+ modifiers), `checkDefection()` (probabilistic when loyalty < q(0.20)), `resolvePartyConflict()`
308
+ (combat-power ratio determines outcome), `recordMissionSuccess/Failure()`.
309
+
310
+ **Phase 49** adds legacy and inheritance (`src/inheritance.ts`): character death does not end the
311
+ campaign — `transferEquipment()` moves loadout items to the heir, `transferRelationships()` partially
312
+ copies the relationship graph (trust/affinity scaled by `relationshipTransferRate_Q`, default q(0.50)),
313
+ `transferInventory()` merges campaign inventories, and `applyInheritance()` orchestrates all three
314
+ steps while updating the campaign registry and location tracking.
315
+
316
+ **Phase 50** adds mythology and legend (`src/legend.ts`): high-significance chronicle entries crystallise
317
+ into `Legend` objects with a `LegendReputation` (`heroic`, `notorious`, `legendary`, `forgotten`),
318
+ a `fame_Q` (how widely known), and thematic `tags` derived from source events and story arcs.
319
+ `createLegendFromChronicle(chronicle, subjectId, subjectName)` promotes qualifying entries (default
320
+ significance ≥ 60) into a legend; `fame_Q` scales with total significance (7 × 100-pt entries →
321
+ q(1.0)). `getLegendEffect(legend)` returns `LegendEffect` — persuasion, intimidation, fear, and
322
+ morale bonuses that scale with fame. `npcKnowsLegend(legend, npcId, worldSeed, tick)` is a
323
+ deterministic fame roll (same inputs = same result). `applyLegendToDialogueContext(initiatorId,
324
+ targetId, registry, worldSeed, tick)` aggregates effects for NPCs who know the legend. `stepLegendFame`
325
+ decays fame over time (legendary legends have a q(0.50) floor). Full serialisation support.
326
+
327
+ **Phase 51** adds weather and atmospheric environment (`src/sim/weather.ts`): `WeatherState` bundles
328
+ `WindField`, `PrecipitationType` (`none` / `rain` / `heavy_rain` / `snow` / `blizzard` / `hail`),
329
+ and `fogDensity_Q`. `deriveWeatherModifiers(weather)` returns `WeatherModifiers` — traction,
330
+ vision, and thermal deltas. Per-tick kernel integration: rain/snow reduce `ctx.tractionCoeff`
331
+ (blizzard → q(0.40)), fog and precipitation reduce `SensoryEnvironment.lightMul` and `.smokeMul`,
332
+ precipitation cools `ctx.thermalAmbient_Q` (snow −5 °C, blizzard −12 °C). `computeWindAimError`
333
+ adds crosswind drift to ranged-combat grouping radius (drift = v_wind_perp × range / v_proj).
334
+ `adjustConeRange` modulates breath-weapon range ±20 % for head/tailwind. All effects are additive
335
+ with existing sensory and thermal modifiers; `weather` absent = backward-compatible.
336
+
337
+ **Phase 52** adds extended sensory modalities (`src/sim/sensory-extended.ts`): `ExtendedSenses` on
338
+ `Entity` enables echolocation (range-checked, darkness-independent, degraded by noiseMul — dead
339
+ targets still detected via physical echo), electroreception (bioelectric field detection, dead
340
+ targets not detected), and olfaction (wind-direction-aware, precipitation-dispersed scent tracking).
341
+ `computeDaylightMul(hourOfDay)` returns a Q multiplier (q(0.10) at midnight → q(1.0) at noon) for
342
+ integration with `SensoryEnvironment.lightMul`. `canDetectExtended` wraps Phase 4 `canDetect` and
343
+ additionally checks each extended modality in quality order: electroreception q(0.80),
344
+ echolocation q(0.70), olfaction q(0.20–0.40).
345
+
346
+ **Phase 53** adds systemic toxicology for ingested substances (`src/sim/systemic-toxicology.ts`):
347
+ five toxin profiles — `alcohol` (15 min onset, motor impairment + disinhibition, addictive),
348
+ `sedative_plant` (30 min onset, consciousness erosion, addictive), `alkaloid_poison` (20 min,
349
+ internal damage + fear), `heavy_lead` and `radiation_dose` (cumulative irreversible dose
350
+ accumulation via `CumulativeExposureRecord`). Exponential concentration decay via metabolic
351
+ half-life (fixed-point minimum-1/s guarantee ensures clearance). Withdrawal states
352
+ (`WithdrawalState`) are triggered after sustained addictive use, applying fatigue and fear
353
+ penalties for the withdrawal duration. `deriveCumulativeToxicity(entity)` → Q for chronic
354
+ exposure queries. Stepped at 1 Hz alongside nutrition and Phase 32C venom. Backward-compatible:
355
+ absent fields = no change.
356
+
357
+ **Phase 54** adds wound aging and long-term sequelae (`src/sim/wound-aging.ts`):
358
+ `stepWoundAging(entity, elapsedSeconds)` models four time-scale processes — uninfected-region
359
+ healing (surface 1%/day, internal 0.5%/day, clamped to permanentDamage floor), infection
360
+ worsening (1.5%/day internalDamage, sepsis threshold at q(0.85)), chronic fatigue from
361
+ sustained permanent damage, and phantom pain shock from fractured+permanent regions.
362
+ `recordTraumaEvent` / `deriveFearThresholdMul` implement PTSD-like trauma accumulation
363
+ with natural q(0.002)/day decay; max trauma halves the effective fear threshold (q(0.50)).
364
+ `deriveSepsisRisk(entity)` → Q aggregates infected-region severity for medical triage AI.
365
+ `TraumaState` added to `Entity`. All healing and worsening rates are deterministic from
366
+ elapsed seconds — no RNG required. Backward-compatible: absent `traumaState` = no effect.
367
+
368
+ **Phase 55** adds collective non-combat activities (`src/collective-activities.ts`):
369
+ three self-contained group systems for downtime and logistics. **Siege engineering**:
370
+ `createCollectiveProject` / `contributeToCollectiveProject` maintain a shared `progress_Q`
371
+ pool; each entity's contribution = `deriveEngineeringCompetence(entity) × hoursWorked /
372
+ requiredWorkHours`, where competence is the average of `logicalMathematical + bodilyKinesthetic`;
373
+ `isProjectComplete` and `completedAtTick` track completion. **Ritual & ceremony**:
374
+ `stepRitual(participants, elapsedSeconds)` produces a `moraleBonus_Q` (cap q(0.30)) and
375
+ `fearReduction_Q` (60 % of morale bonus) by pooling each participant's average `(intrapersonal
376
+ + musical) / 2` with sqrt(N) group scaling and a linear time ramp over one hour. **Trade
377
+ caravan logistics**: `planCaravanRoute(waypoints, participants, inventory)` derives
378
+ `routeQuality_Q` from the best navigator's `logicalMathematical`, a `negotiationBonus_Q` from
379
+ the best `interpersonal` score, shortens travel time via a speed factor ∈ [q(0.80), q(1.00)],
380
+ and computes `supplySufficiency_Q` from available rations vs. travel-day demand.
381
+ No kernel integration — all three systems are host-application-driven downtime APIs.
382
+
383
+ **Phase 56** adds entity-to-entity disease transmission (`src/sim/disease.ts`):
384
+ six disease profiles (common_fever, wound_fever, plague_pneumonic, dysentery, marsh_fever,
385
+ wasting_sickness) each with `transmissionRoute`, daily fatigue drain, incubation/symptomatic
386
+ phase timers, a mortality roll via deterministic `eventSeed`, and graduated immunity.
387
+ `exposeToDisease(entity, id)` adds an incubating state (skips immune/already-infected
388
+ entities); `stepDiseaseForEntity(entity, delta_s, worldSeed, tick)` advances phases, drains
389
+ fatigue, rolls mortality on recovery, grants immunity; `computeTransmissionRisk` returns Q
390
+ risk — airborne decays linearly to zero at `airborneRange_Sm`, contact/vector/waterborne are
391
+ flat within 2 m; `spreadDisease(entityMap, pairs, worldSeed, tick)` performs a deterministic
392
+ batch exposure from host-supplied spatial pairs. `DiseaseState` and `ImmunityRecord` added
393
+ to `Entity`. Backward-compatible: absent `activeDiseases` = no effect.
394
+
395
+ **Phase 57** adds aging and lifespan simulation (`src/sim/aging.ts`):
396
+ species-agnostic attribute curves parameterized by normalized age fraction
397
+ (`ageFrac = ageYears / lifespanYears`). Seven piecewise-linear Q dimensions —
398
+ muscular strength (peaks at ageFrac ≈ 0.28), reaction time (faster at peak,
399
+ slowest in infancy and antiquity), motor control, stature (stable adult, slight
400
+ compression in ancient), fluid cognition (logical/spatial/musical — peaks young),
401
+ crystallized cognition (linguistic/interpersonal/intrapersonal — peaks mid-life,
402
+ wisdom outlasts speed), and distress tolerance (peaks middle age). A 25-year-old
403
+ human and a 187-year-old elf (lifespan 600) share the same ageFrac and therefore
404
+ the same developmental multipliers. `applyAgingToAttributes(base, ageYears)` returns
405
+ a new `IndividualAttributes` without mutating the peak baseline; `stepAging(entity,
406
+ elapsedSeconds)` accumulates `entity.age.ageSeconds` for campaign-scale time tracking.
407
+
408
+ **Phase 58** adds sleep and circadian rhythm (`src/sim/sleep.ts`): a two-factor
409
+ deprivation model tracking `awakeSeconds` (continuous wake duration) and `sleepDebt_s`
410
+ (cumulative nightly shortfall, capped at 72 h). `circadianAlertness(hourOfDay)` → Q
411
+ piecewise-linear alertness curve (peak 17:00, nadir 03:00). Impairment begins after 17 h
412
+ awake and degrades four attributes linearly to max at 72 h: fluid cognition −45%, reaction
413
+ time +45% slower, stability −25%, distress tolerance −35%. Sleep-phase cycling follows the
414
+ 90-minute NREM/REM cycle (light 45 min → deep 25 min → rem 20 min → repeat). Prior-night
415
+ debt persists even after short sleep: effective driver = max(awakeSeconds, sleepDebt_s).
416
+ `applySleepToAttributes(base, state)` returns a new `IndividualAttributes` (immutable,
417
+ same pattern as Phase 57); `stepSleep(entity, elapsedSeconds, isSleeping)` mutates
418
+ `entity.sleep`; `entitySleepDebt_h(entity)` convenience helper.
419
+
420
+ **Phase 59** adds mounted combat (`src/sim/mount.ts`): five mount profiles (pony, horse,
421
+ warhorse, camel, war_elephant) with physics-based charge energy (`bonusEnergy_J = ½mv²`),
422
+ rider height advantage (aim bonus proportional to seat elevation, q(0.12)/m, max q(0.30)),
423
+ mount-to-rider fear contagion (40% of excess shock beyond fearThreshold), and
424
+ forced-dismount detection (rider over-shock, mount death, mount bolt). `checkMountStep`
425
+ returns a pure `MountStepResult` with no entity mutation. `MountState { mountId, riderId,
426
+ gait }` added to `Entity`.
427
+
428
+ **Phase 60** adds environmental hazard zones (`src/sim/hazard.ts`): persistent 2-D circular areas
429
+ with linear exposure falloff that inflict per-second effects (fatigue, thermal shift, radiation dose,
430
+ surface damage, disease exposure). Five types: fire, radiation, toxic_gas, acid, extreme_cold.
431
+ `computeHazardExposure(dist_Sm, hazard)` → Q; `deriveHazardEffect(hazard, exposureQ)` → `HazardEffect`;
432
+ `stepHazardZone` ticks duration (permanent zones are untouched); `isHazardExpired` signals removal.
433
+ No entity field needed — hazards are world-level objects the host iterates each tick.
434
+
435
+ **Phase 63** adds a **narrative stress test** framework (`src/narrative-stress.ts`): given a
436
+ scene described as a sequence of `NarrativeBeat` predicates — each with a tick window in which
437
+ it must become true — `runNarrativeStressTest` runs the simulation across hundreds of seeds
438
+ and measures what fraction of runs spontaneously produce that sequence. The complement is the
439
+ **narrative push**: `0.00` = plausible (no authorial pressure needed); `1.00` = "extreme — plot
440
+ armour" (the story never happens without explicit intervention). Beat predicate helpers
441
+ (`beatEntityDefeated`, `beatEntitySurvives`, `beatTeamDefeated`, `beatEntityShockExceeds`,
442
+ `beatEntityFatigued`) cover the most common story requirements. `formatStressTestReport`
443
+ produces a human-readable breakdown labelled "none — plausible" / "light" / "moderate" /
444
+ "heavy" / "extreme — plot armour". Demo: `npm run run:narrative-stress-test`.
445
+
446
+ **Artificial Life Validation ("Blade Runner" Test)** (`tools/blade-runner.ts`, `npm run run:blade-runner`) —
447
+ the ultimate integration test: 198 named NPCs, 3 polities, and 365 simulated days wiring every
448
+ major phase simultaneously (disease, polity economics, war, sleep debt, skill progression).
449
+ Validates 4 emergent-behaviour claims: social hierarchy, disease mortality spikes, morale–economy
450
+ correlation, and skill accumulation hierarchy. All 4/4 claims pass on seed 1.
451
+
452
+ **Species Forge** (`docs/editors/species-forge.html`) — four-tab standalone HTML/JS species designer
453
+ extending the Body Plan Editor. Tab 1: segment body plan with live validation. Tab 2: 24 archetype
454
+ sliders (stature, mass, peak force/power, reaction time, control quality, resilience, perception).
455
+ Tab 3: five Narrative Bias sliders (strength/speed/resilience/agility/size, −1 to +1) with six
456
+ preset profiles (Warrior/Scholar/Rogue/Tank/Feral Beast/Clear). Tab 4: live-generated TypeScript
457
+ trio (`BodyPlan` + `Archetype` + `NarrativeBias`). Four templates: Humanoid, Large Beast, War
458
+ Machine, Mind Swarm. Linked from `docs/editors/index.html`.
459
+
460
+ **Phase 67 — Technology Diffusion at Polity Scale** (`src/tech-diffusion.ts`) — tech eras
461
+ spread from advanced polities to lagging neighbours via trade routes and cultural contact.
462
+ `computeDiffusionPressure(source, target, pair, warActive)` computes per-day advance probability
463
+ scaling with era gap (×1–3×), route quality, shared locations, and stability threshold.
464
+ `stepTechDiffusion(registry, pairs, worldSeed, tick)` rolls for advances each tick, mutates
465
+ `techEra`, and refreshes military strength; one advance per polity per tick maximum.
466
+ `totalInboundPressure` provides a signed-Q AI query for "how likely is this polity to advance?"
467
+ Long-run convergence test: era-1 polity reaches era-3 within 2 000 daily ticks (~5.5 years).
468
+ 34 tests; 100% coverage.
469
+
470
+ **Phase 66 — Generative Mythology** (`src/mythology.ts`) — narrative compression of the Legend/
471
+ Chronicle log into in-world cultural beliefs. `compressMythsFromHistory(legendRegistry, entries,
472
+ factionIds)` detects six archetypal patterns (hero, monster, great_plague, divine_wrath, golden_age,
473
+ trickster) from accumulated legends and chronicle events; each myth carries a `MythEffect` (fear
474
+ threshold, diplomacy, morale, tech modifiers) scaled by its `belief_Q`. `stepMythologyYear`
475
+ decays belief 12%/year to a floor of q(0.10). `aggregateFactionMythEffect` gives the net cultural
476
+ modifier for polity-day AI use. 39 tests; 100% statement/branch/function/line coverage.
477
+
478
+ **Phase 65 — Emotional Contagion at Polity Scale** (`src/emotional-contagion.ts`) — fear and hope
479
+ propagate between polities using the Phase 56 disease transmission model with `fear_Q`/`hope_Q` as
480
+ the "pathogen". Four built-in profiles: `military_rout` (fast-spreading panic), `plague_panic`
481
+ (slow-decaying dread), `victory_rally` (hope wave from a battle win), `charismatic_address`
482
+ (leader-amplified; Phase 39 `leaderPerformance_Q` scales initial intensity).
483
+ `applyEmotionalContagion(registry, pairs, waves, profiles, worldSeed, tick)` drives spread +
484
+ moraleQ updates each polity day-tick. `netEmotionalPressure` gives a signed Q for AI queries.
485
+ 46 tests; 100% statement/function/line coverage.
486
+
487
+ **"What If?" / Alternate History Engine** (`tools/what-if.ts`, `npm run run:what-if`, Phase 64) —
488
+ polity-scale alternate-history simulator that runs a `WhatIfScenario` across N seeds and reports
489
+ probability-weighted outcome distributions. Three built-in scenarios: plague devastating a capital
490
+ (−92.5% population; density-floor mechanics), a charismatic leader's morale surge (+22% military
491
+ at day 90), and a sudden war between equal polities (−100% aggressor stability; −40% treasury for
492
+ both; war persists all 180 days). Demonstrates geopolitical consequence modelling built on Phase 61.
493
+
494
+ **Emergent Behaviour Validation Suite** (`tools/emergent-validation.ts`, `npm run run:emergent-validation`) —
495
+ four historical combat scenarios validated across 100 seeds each: 10v10 open-ground skirmish
496
+ (Ardant du Picq), environmental friction in rain + fog (Keegan), Lanchester's numerical
497
+ superiority law (5 vs. 10), and siege attrition via disease (Raudzens). All 4/4 scenarios pass.
498
+
499
+ **Performance & Scalability Benchmarks** (`tools/benchmark.ts`, `npm run run:benchmark`) —
500
+ reproducible throughput figures across four entity-count scenarios (10 / 100 / 500 / 1 000).
501
+ Includes AI-decision-budget breakdown, spatial-index comparison vs. naïve O(n²), and a tuning
502
+ guide. Full report in `docs/performance.md`.
503
+
504
+ **Dataset Contribution Pipeline** (`docs/dataset-contribution.md`) — step-by-step guide for
505
+ adding empirical datasets and `DirectValidationScenario` entries to the validation runner.
506
+ Includes CSV format spec, four code templates, tolerance-selection table, and a live example
507
+ (`datasets/example-sprint-speed.csv` — human peak anaerobic power, ✓ PASS at 9.5% error).
508
+
509
+ **Public Validation Dashboard** (`docs/dashboard/`, `npm run run:validation-dashboard`) —
510
+ self-contained HTML dashboard showing all 43 validation scenarios with pass/fail status,
511
+ simulated vs. empirical bars with ±tolerance bands, and filter controls. JSON data is
512
+ regenerated automatically on push via `.github/workflows/validation-dashboard.yml`.
513
+ Serve locally with `python -m http.server` inside `docs/dashboard/`.
514
+
515
+ **Visual Editors for Non-Developers** (`docs/editors/`) — two standalone HTML/JS tools
516
+ requiring no build step or TypeScript knowledge. **Body Plan Editor**: define species segments
517
+ with mass-share sliders, locomotion/manipulation/CNS roles, and live validation; generates a
518
+ `BodyPlan` TypeScript literal. **Validation Scenario Builder**: configure entities, simulation
519
+ parameters, weather, and empirical reference data; generates a `DirectValidationScenario`
520
+ block. Both tools serve via GitHub Pages or `python -m http.server`.
521
+
522
+ **2904 tests.** All coverage thresholds met (statements 93.75%+, branches 84.69%+, functions 92%+, lines 93.75%+).
523
+
524
+ See `ROADMAP.md` for the full development plan.
525
+
526
+ ---
527
+
528
+ ## Validation against real-world data
529
+
530
+ > **Emergent validation report:** [`docs/emergent-validation-report.md`](docs/emergent-validation-report.md)
531
+ > — four historical combat scenarios, 100 seeds each, all passing. This is the flagship trust
532
+ > artifact: it validates *distributions of outcomes* across multi-system interactions, not just
533
+ > individual formula outputs. CI runs a 20-seed fast subset on every push.
534
+
535
+ Ananke's physics-based approach is systematically validated against external real-world datasets and literature sources. The validation framework (`tools/validation.ts`) compares simulation outputs with empirical measurements across multiple sub‑systems, ensuring the simulation's predictions remain grounded in reality.
536
+
537
+ **Key validated sub‑systems:**
538
+ - Movement energy cost (AddBiomechanics walking metabolic dataset)
539
+ - Projectile drag (BVR Air Combat dataset)
540
+ - Jump height and sprint speed (sports‑science literature)
541
+ - Fracture thresholds (Yamada 1970, McElhaney 1970)
542
+ - Thoracic and pelvic impact tolerance (AFRL Biodynamics Data Bank)
543
+ - Damage energy constants (NATO STANAG 4526)
544
+ - Sleep deprivation cognitive impairment (Van Dongen et al. 2003 meta‑analysis)
545
+ - Disease mortality rates (historical epidemiology)
546
+ - Calibration scenarios: armed vs. unarmed, untreated knife wound, first‑aid saves lives, fracture recovery, untreated infection, plate armour effectiveness
547
+
548
+ Each validation scenario runs a deterministic simulation that replicates the experimental conditions of the external dataset, then compares the simulated mean against the empirical mean with a ±20 % tolerance. A constant‑suggestion system provides actionable recommendations for tuning constants when deviations exceed tolerance.
549
+
550
+ **Validation inventory:** A comprehensive catalogue of all validated and potential future datasets is maintained in [`docs/external-dataset-validation-inventory.md`](docs/external-dataset-validation-inventory.md). The inventory includes 19 currently validated sources and identifies 11 high‑value external datasets for future validation work across muscle mechanics, ground‑reaction forces, blast physics, cognitive state, and melee combat.
551
+
552
+ **Run validation:** `npm run run:validation <subsystem>` generates a validation report for a specific sub‑system.
553
+
554
+ ## Physics realism summary (post-Phase 30)
555
+
556
+ Ananke now models the complete survivability stack. Five independent threat vectors can kill
557
+ an entity, each operating on a different time scale and requiring different intervention:
558
+
559
+ | Threat | Time scale | Mechanism | Primary phases |
560
+ |--------|-----------|-----------|----------------|
561
+ | Injury / blood loss | Seconds | Fluid loss → shock → death | 1, 9 |
562
+ | Infection | Hours–days | Internal damage accumulation | 9 |
563
+ | Thermal stress | Minutes–hours | Core temperature extremes | 29 |
564
+ | Dehydration | Hours–days | Hydration balance collapse | 30 |
565
+ | Starvation | Days–weeks | Caloric deficit → catabolism | 30 |
566
+
567
+ ### Physical phenomena modelled
568
+
569
+ **Newtonian mechanics**
570
+ - Kinetic energy → injury via impact physics, penetration, and leverage (Phases 1–3)
571
+ - Impulse-momentum → knockback velocity and stagger/prone transitions (Phase 26)
572
+ - Angular momentum → miss recovery time and weapon bind probability (Phase 2)
573
+
574
+ **Wound physiology**
575
+ - Per-region surface / internal / structural damage; penetration bias by tissue type
576
+ - Bleeding rate proportional to internal damage; natural clotting proportional to tissue integrity
577
+ - Fluid loss → haemodynamic shock → consciousness erosion → death (`fluidLoss ≥ 0.80` fatal)
578
+ - Fracture (structural ≥ 0.70), permanent damage (≥ 0.90), infection onset after 100 ticks of bleeding
579
+
580
+ **High-velocity ballistics** (Phase 27)
581
+ - Temporary cavity multiplier ×1.0–×3.0 above 600 m/s; scales by tissue compliance
582
+ - Cavitation bleed boost in fluid-saturated organs (torso, liver, spleen, lung, legs) above 900 m/s
583
+
584
+ **Thermodynamics** (Phase 29)
585
+ - Core temperature balance: metabolic heat (1.06–5.50 W/kg by activity) vs. conductive loss through skin + armour insulation
586
+ - Thermal resistance: `R = 0.09 + Σ(armour.insulation_m2KW)` °C/W; thermal mass = `3500 × mass_real` J/°C
587
+ - Seven-stage temperature model: critical hyperthermia → heat stroke → heat exhaustion → normal → mild → moderate → severe hypothermia → cardiac arrest
588
+
589
+ **Metabolism** (Phase 30)
590
+ - Basal metabolic rate: `80 × (mass_kg / 75)^0.75` W (Kleiber's law)
591
+ - Active metabolic rate: `BMR + peakPower_W × activityFrac × 0.15`
592
+ - Four hunger states: sated (< 12 h deficit), hungry (12–24 h), starving (24–72 h), critical (≥ 72 h)
593
+ - Fat catabolism: 300 g/day during starvation; muscle catabolism: 0.5 N/hour reduction in `peakForce_N` during critical state
594
+
595
+ **Pharmacokinetics** (Phase 10)
596
+ - One-compartment absorption/elimination model per active substance
597
+ - Cross-substance interactions: stimulant, haemostatic, anaesthetic, poison
598
+
599
+ **Neuromuscular and cognitive**
600
+ - Reaction time → attack/defence timing; decision latency → AI replanning interval
601
+ - Fine motor control impairment from arm damage; manipulation penalty from fractures
602
+ - Fatigue accumulation (joules) → exhaustion threshold → prone collapse
603
+
604
+ **Environmental**
605
+ - Fire, corrosive, electrical, radiation, suffocation — tick-based accumulation with channel-specific armour
606
+ - Blast wave (quadratic falloff) and fragmentation (stochastic count, random region)
607
+ - Ambient temperature stress (Phase 10); terrain friction, elevation, cover, hazard cells (Phase 6)
608
+
609
+ **Psychology**
610
+ - Fear accumulation from near-miss fire, ally deaths, calibre size (Phase 5)
611
+ - Routing, panic varieties (surrender / freeze / flee), leader auras, rally mechanics
612
+ - Suppression from near-miss ranged fire → AI cover-seeking
613
+
614
+ ### Time scales covered
615
+
616
+ | Scale | System |
617
+ |-------|--------|
618
+ | 50 ms / tick (20 Hz) | Combat resolution, movement, stamina |
619
+ | Seconds | Bleeding, pharmacokinetics, thermal tick |
620
+ | Minutes | Clotting, hypothermia onset |
621
+ | Hours | Infection onset, hunger onset, thermal equilibrium |
622
+ | Days | Hunger state transitions, mass loss from starvation |
623
+ | Weeks | Wound healing, infection mortality |
624
+ | Months / years | Training drift, ageing decline, injury sequelae (Phase 21) |
625
+
626
+ ---
627
+
628
+ ## Quick start
629
+
630
+ ```typescript
631
+ import { stepWorld, TICK_HZ } from "./src/sim/kernel.js";
632
+ import { STARTER_WEAPONS } from "./src/equipment.js";
633
+ import { HUMAN_BASE } from "./src/archetypes.js";
634
+ import { generateIndividual } from "./src/generate.js";
635
+ import { q, SCALE } from "./src/units.js";
636
+ import { v3 } from "./src/sim/vec3.js";
637
+ import { defaultIntent } from "./src/sim/intent.js";
638
+ import { defaultAction } from "./src/sim/action.js";
639
+ import { defaultCondition } from "./src/sim/condition.js";
640
+ import { defaultInjury } from "./src/sim/injury.js";
641
+
642
+ // Two fighters, procedurally generated from the human archetype.
643
+ // Each will have unique physical attributes drawn from realistic distributions.
644
+ const attrsA = generateIndividual(1, HUMAN_BASE);
645
+ const attrsB = generateIndividual(2, HUMAN_BASE);
646
+
647
+ const world = {
648
+ tick: 0,
649
+ seed: 12345,
650
+ entities: [
651
+ {
652
+ id: 1, teamId: 1,
653
+ attributes: attrsA,
654
+ energy: { reserveEnergy_J: attrsA.performance.reserveEnergy_J, fatigue: q(0) },
655
+ loadout: { items: [STARTER_WEAPONS[0]] },
656
+ traits: [],
657
+ position_m: v3(0, 0, 0),
658
+ velocity_mps: v3(0, 0, 0),
659
+ intent: defaultIntent(), action: defaultAction(),
660
+ condition: defaultCondition(), injury: defaultInjury(),
661
+ grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0) },
662
+ },
663
+ {
664
+ id: 2, teamId: 2,
665
+ attributes: attrsB,
666
+ energy: { reserveEnergy_J: attrsB.performance.reserveEnergy_J, fatigue: q(0) },
667
+ loadout: { items: [] },
668
+ traits: [],
669
+ position_m: v3(Math.trunc(0.8 * SCALE.m), 0, 0),
670
+ velocity_mps: v3(0, 0, 0),
671
+ intent: defaultIntent(), action: defaultAction(),
672
+ condition: defaultCondition(), injury: defaultInjury(),
673
+ grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0) },
674
+ },
675
+ ],
676
+ };
677
+
678
+ const cmds = new Map([
679
+ [1, [{ kind: "attack", targetId: 2, weaponId: "wpn_club", intensity: q(1.0), mode: "strike" }]],
680
+ ]);
681
+
682
+ for (let i = 0; i < 5 * TICK_HZ; i++) {
683
+ stepWorld(world, cmds, { tractionCoeff: q(0.9) });
684
+ }
685
+
686
+ const target = world.entities.find(e => e.id === 2)!;
687
+ console.log("Torso surface damage:", target.injury.byRegion.torso.surfaceDamage / SCALE.Q);
688
+ console.log("Consciousness:", target.injury.consciousness / SCALE.Q);
689
+ console.log("Dead:", target.injury.dead);
690
+ ```
691
+
692
+ ---
693
+
694
+ ## Fixed-point unit system
695
+
696
+ All simulation values are scaled integers. Never use `Math.random()` or floating-point
697
+ arithmetic in simulation code.
698
+
699
+ | Unit | SCALE constant | Meaning |
700
+ |---|---|---|
701
+ | metres | `SCALE.m = 10000` | 1 m = 10 000 units |
702
+ | seconds | `SCALE.s = 10000` | 1 s = 10 000 units |
703
+ | kilograms | `SCALE.kg = 1000` | 1 kg = 1 000 units |
704
+ | newtons | `SCALE.N = 1000` | 1 N = 1 000 units |
705
+ | joules | `SCALE.J = 1000` | 1 J = 1 000 units |
706
+ | watts | `SCALE.W = 1000` | 1 W = 1 000 units |
707
+ | dimensionless | `SCALE.Q = 10000` | 1.0 = 10 000 units |
708
+
709
+ Convert to fixed-point: `q(0.5)` → `5000`, `to.m(1.8)` → `18000`.
710
+ Read back: `value / SCALE.m` → metres, `value / SCALE.Q` → dimensionless fraction.
711
+
712
+ ---
713
+
714
+ ## Entity model
715
+
716
+ Every entity is built from four attribute groups, all in SI units.
717
+
718
+ ### Morphology
719
+
720
+ | Field | Unit | Meaning |
721
+ |---|---|---|
722
+ | `stature_m` | m | Height |
723
+ | `mass_kg` | kg | Total mass |
724
+ | `actuatorMass_kg` | kg | Muscle or actuator mass |
725
+ | `actuatorScale` | Q | Actuator strength multiplier |
726
+ | `structureScale` | Q | Structural toughness multiplier |
727
+ | `reachScale` | Q | Limb reach multiplier |
728
+
729
+ ### Performance
730
+
731
+ | Field | Unit | Meaning |
732
+ |---|---|---|
733
+ | `peakForce_N` | N | Maximum output force |
734
+ | `peakPower_W` | W | Maximum instantaneous power |
735
+ | `continuousPower_W` | W | Sustainable aerobic power |
736
+ | `reserveEnergy_J` | J | Total metabolic reserve (stamina pool) |
737
+ | `conversionEfficiency` | Q | Fraction of metabolic energy delivered as mechanical work |
738
+
739
+ ### Control
740
+
741
+ | Field | Unit | Meaning |
742
+ |---|---|---|
743
+ | `controlQuality` | Q | Overall motor coordination (0–1) |
744
+ | `reactionTime_s` | s | Neuromuscular response latency |
745
+ | `stability` | Q | Balance and postural stability (0–1) |
746
+ | `fineControl` | Q | Precision of fine movements (0–1) |
747
+
748
+ ### Resilience
749
+
750
+ | Field | Unit | Meaning |
751
+ |---|---|---|
752
+ | `surfaceIntegrity` | Q | Resistance to surface-layer damage |
753
+ | `bulkIntegrity` | Q | Resistance to bulk soft-tissue damage |
754
+ | `structureIntegrity` | Q | Resistance to skeletal or structural damage |
755
+ | `distressTolerance` | Q | Pain and distress tolerance (0–1) |
756
+ | `shockTolerance` | Q | Resistance to haemodynamic shock (0–1) |
757
+ | `concussionTolerance` | Q | CNS impact tolerance (0–1) |
758
+ | `heatTolerance` | Q | Thermal hazard resistance (0–1) |
759
+ | `coldTolerance` | Q | Cold hazard resistance (0–1) |
760
+ | `fatigueRate` | Q | Fatigue accumulation rate multiplier |
761
+ | `recoveryRate` | Q | Recovery rate multiplier |
762
+ | `magicResist` | Q | Resistance to capability effects (magic/technology); q(1.0) = fully immune (optional) |
763
+
764
+ ### Perception (Phase 4)
765
+
766
+ | Field | Unit | Meaning |
767
+ |---|---|---|
768
+ | `visionRange_m` | m | Maximum reliable visual detection range |
769
+ | `visionArcDeg` | degrees | Horizontal field of view (120° human, 360° robot) |
770
+ | `halfArcCosQ` | Q | cos(visionArcDeg/2) pre-computed for fast sim-path arc checks |
771
+ | `hearingRange_m` | m | Omnidirectional acoustic detection range |
772
+ | `decisionLatency_s` | s | Minimum time between plan revisions (500ms human, 50ms robot) |
773
+ | `attentionDepth` | integer | Maximum simultaneously tracked threats |
774
+ | `threatHorizon_m` | m | Range at which threats are integrated into decisions |
775
+
776
+ ### Individual variation
777
+
778
+ `generateIndividual(seed, archetype)` produces a unique entity by applying triangular
779
+ distributions to each attribute, parameterised by the archetype's variance fields.
780
+
781
+ A human with seed 1 might have `peakForce_N = 1640` (below average) but
782
+ `reserveEnergy_J = 26000` (high stamina). These values are physically meaningful and feed
783
+ directly into combat, movement, and fatigue calculations with no intermediate conversion.
784
+
785
+ Archetypes in `src/archetypes.ts`: `HUMAN_BASE`, `SERVICE_ROBOT`, `AMATEUR_BOXER`,
786
+ `PRO_BOXER`, `GRECO_WRESTLER`, `KNIGHT_INFANTRY`, `LARGE_PACIFIC_OCTOPUS`. Additional
787
+ archetypes (quadruped, alien, etc.) are data additions, not code changes.
788
+
789
+ ---
790
+
791
+ ## Anatomy and injury
792
+
793
+ Per-region injury tracking — fully data-driven via `BodyPlan` (Phase 8). Default is humanoid
794
+ (head, torso, left arm, right arm, left leg, right leg). Other plans: quadruped, theropod,
795
+ sauropod, avian, vermiform, centaur, octopoid — see `src/sim/bodyplan.ts`.
796
+
797
+ Each region tracks:
798
+
799
+ - `surfaceDamage` — skin, scales, outer covering
800
+ - `internalDamage` — organ, soft-tissue, deep injury
801
+ - `structuralDamage` — bone, frame, load-bearing structure
802
+ - `bleedingRate` — active blood loss rate
803
+ - `fractured` — set when `structuralDamage ≥ 0.70`; persistent impairment until surgically repaired (Phase 9)
804
+ - `permanentDamage` — floor below which surgery cannot reduce `structuralDamage` (set at ≥ 0.90) (Phase 9)
805
+ - `bleedDuration_ticks` — ticks with active bleeding; infection onsets after 100 ticks if `internalDamage > 0.10` (Phase 9)
806
+ - `infectedTick` — tick at which infection began (`-1` = none); infected regions gain `+q(0.0003)` internal damage per tick (Phase 9)
807
+
808
+ Global injury state:
809
+
810
+ - `shock` — haemodynamic and neurological shock (0–1)
811
+ - `fluidLoss` — cumulative blood or fluid loss (0–1); fatal at 0.80 (Phase 9)
812
+ - `consciousness` — 1.0 = fully conscious, 0 = unconscious
813
+ - `dead` — irreversible cessation
814
+
815
+ ---
816
+
817
+ ## Functional impairment
818
+
819
+ Damage produces functional penalties that feed automatically into movement, combat, and
820
+ stamina calculations:
821
+
822
+ | Damage location | Effect |
823
+ |---|---|
824
+ | Leg structural | Sprint speed and acceleration reduction |
825
+ | Leg fracture | Up to −30% mobility (Phase 9) |
826
+ | Arm damage | Manipulation quality and parry effectiveness reduction |
827
+ | Arm fracture | Up to −25% manipulation (Phase 9) |
828
+ | Head damage | Coordination and consciousness degradation |
829
+ | Torso damage | Breathing impairment and shock vulnerability |
830
+ | Shock (global) | All performance multipliers degraded |
831
+
832
+ ---
833
+
834
+ ## Combat resolution
835
+
836
+ ### Hit resolution
837
+
838
+ 1. Attacker rolls against `controlQuality` and `reactionTime_s`
839
+ 2. Defender rolls against their defence mode (block, parry, dodge) and intensity
840
+ 3. Geometry influence (facing angle) modifies contest
841
+ 4. On hit: location selected by weighted region roll, hit quality computed
842
+ 5. Shield interposition checked against region coverage
843
+ 6. Armour checked per region and channel
844
+ 7. Residual energy applied to injury
845
+
846
+ ### Impact physics
847
+
848
+ Impact energy derived from:
849
+
850
+ - Weapon effective mass (kg)
851
+ - Relative velocity (m/s) from attacker's `peakPower_W` and `continuousPower_W`
852
+ - Leverage: parry leverage from weapon moment arm (N·m)
853
+ - Two-handed grip: 1.12× energy bonus when both arms are free
854
+
855
+ Converted to surface, internal, and structural injury proportional to penetration profile.
856
+ Bleeding rate increases proportionally to internal damage severity.
857
+
858
+ ### Weapon dynamics
859
+
860
+ - **Reach dominance**: shorter weapon is penalised in both attack and parry against a longer one
861
+ - **Miss recovery**: missed strikes add extra cooldown ticks proportional to weapon angular momentum (mass × reach)
862
+ - **Weapon bind**: successful parry with heavy weapons can lock both weapons; requires a strength contest (`breakBind`) to escape
863
+ - **Grappling**: strength+mass+technique contest; positions (standing/pinned/prone), throws, chokes, joint locks
864
+ - **Swing momentum carry**: `swingMomentumQ` decays 5%/tick; a clean hit sets it to `intensity × q(0.80)`; adds up to +12% energy on the next strike; reset to 0 on miss, block, or parry
865
+
866
+ ### Stamina
867
+
868
+ Actions cost energy (joules). When reserve falls below 15% of baseline, functional penalties
869
+ ramp in. At zero reserve the entity collapses prone and loses all active defence.
870
+
871
+ ### Medical treatment (Phase 9)
872
+
873
+ Treatment is issued via `TreatCommand` and resolved by `resolveTreat()` in the kernel.
874
+ The treater must be within 2 m of the target. Outcome scales with equipment tier and the
875
+ treater's `medical` skill (`treatmentRateMul`).
876
+
877
+ **Equipment tiers** (ascending capability): `bandage`, `surgicalKit`, `autodoc`, `nanomedicine`.
878
+
879
+ | Action | Min tier | Effect |
880
+ |---|---|---|
881
+ | `tourniquet` | bandage | Zeroes `bleedingRate` for one region immediately |
882
+ | `bandage` | bandage | Reduces `bleedingRate` by `q(0.005) × effectMul` per tick |
883
+ | `surgery` | surgicalKit | Reduces `structuralDamage`; clears fracture; clears infection (≥ surgicalKit) |
884
+ | `fluidReplacement` | autodoc | Reduces `fluidLoss`; slightly reduces shock |
885
+
886
+ **Natural clotting**: `bleedingRate` decays automatically each tick at a rate proportional
887
+ to `(1 − structuralDamage) × q(0.0002)`. Severe structural damage slows natural clotting.
888
+
889
+ **Injury progression** each tick:
890
+ - Bleeding accumulates `fluidLoss` at `bleedingRate / TICK_HZ`
891
+ - Fluid loss drives shock; shock erodes consciousness
892
+ - `fluidLoss ≥ 0.80` → immediate death
893
+ - Infection (if present) adds `+q(0.0003)` internal damage per tick
894
+
895
+ ### Ranged combat
896
+
897
+ Physics-based projectile system parallel to melee. Issued via `shoot` command.
898
+
899
+ **Energy at range** — linear drag approximation: `energy_J = launchEnergy_J × max(0, 1 − range_m × dragCoeff_perM)`. No energy means no hit.
900
+
901
+ **Accuracy** — angular dispersion converts to grouping radius at range (`dispersionQ × range_m`). Compared against body half-width (~20% of stature). Miss if error exceeds half-width; near-miss if within 3× half-width.
902
+
903
+ **Dispersion modifiers**: `controlQuality`, `fineControl`, fatigue, and aiming intensity all scale the base weapon dispersion.
904
+
905
+ **Aiming time** — issuing consecutive `shoot` commands against the same target while stationary accumulates `aimTicks` (max 20, capped at 50% dispersion reduction). Resets on target switch, movement above 0.5 m/s, or after firing.
906
+
907
+ **Moving target penalty** — target velocity adds a lead error (`velocity × 0.2 s reaction time`) to the grouping radius before the hit roll. A sprinting target at 30 m is dramatically harder to hit than a stationary one.
908
+
909
+ **Suppression** — near-misses set `suppressedTicks` on the target, applying a −10% `coordinationMul` penalty until the counter drains. Entities with `suppressedTicks ≥ 3` and low `distressTolerance` will go prone via AI; all suppressed entities seek cover at a higher threshold (q(0.50) vs q(0.30)).
910
+
911
+ **Ammo types** — a `RangedWeapon` may carry an `ammo: AmmoType[]` array. A `shoot` command with `ammoId` selects a round that overrides mass, drag, damage profile, and/or launch energy multiplier for that shot. `STARTER_AMMO` provides `ammo_ap` (penetrating), `ammo_hv` (×1.20 launch energy), and `ammo_hollow` (high bleed factor).
912
+
913
+ **Projectile categories:**
914
+
915
+ | Category | Launch energy | Examples |
916
+ |---|---|---|
917
+ | Thrown | Derived from thrower `peakPower_W` (÷10) | Sling |
918
+ | Bow | Fixed weapon property (J) | Short bow, long bow, crossbow |
919
+ | Firearm | Fixed weapon property (J) | Pistol, musket |
920
+
921
+ Hits reuse the existing injury pipeline (`applyImpactToInjury`) via a weapon proxy. Armour and shields interpose by the same rules as melee.
922
+
923
+ ---
924
+
925
+ ## Armour
926
+
927
+ Per-region coverage with per-channel protection:
928
+
929
+ | Channel | Protects against |
930
+ |---|---|
931
+ | Kinetic | Blunt and penetrating impacts |
932
+ | Thermal | Fire, extreme heat |
933
+ | Chemical | Corrosive aerosols |
934
+ | Electrical | Electrical discharge |
935
+ | Radiation | Ionising radiation |
936
+ | Suffocation | Airborne hazard (sealed suits) |
937
+ | ControlDisruption | EMP, neural disruption |
938
+
939
+ Armour properties: `resist_J` (kinetic threshold), `coverageByRegion` (probability of
940
+ interposition), `protectedDamageMul` (residual damage fraction), `channelResistMul`
941
+ (per-channel resistance modifier), `mobilityMul`, `fatigueMul`.
942
+
943
+ ---
944
+
945
+ ## Environmental hazards
946
+
947
+ Conditions accumulate on entities per tick and drive injury via the same per-channel,
948
+ per-region pipeline as combat:
949
+
950
+ | Condition | Effect |
951
+ |---|---|
952
+ | `onFire` | Surface damage + shock accumulation |
953
+ | `corrosiveExposure` | Surface and internal damage |
954
+ | `electricalOverload` | Internal damage + stun |
955
+ | `radiation` | Internal damage + shock |
956
+ | `suffocation` | Shock and consciousness erosion |
957
+
958
+ Armour provides protection against all channels. Traits (`radiationHardened`, `sealed`,
959
+ `nonConductive`, etc.) provide immunity or resistance.
960
+
961
+ ### Blast and fragmentation (Phase 10)
962
+
963
+ `applyExplosion(world, origin, spec, tick, trace)` applies a `BlastSpec` point-source explosion
964
+ to all living entities within the blast radius. Uses quadratic falloff (`1 − dist²/radius²`).
965
+
966
+ - **Blast wave**: delivered to torso as `BLAST_WEAPON` (high internal damage)
967
+ - **Fragments**: stochastic count per entity; each fragment hits a random region as `FRAG_WEAPON`
968
+ (high penetration bias, high bleed factor)
969
+ - Emits `BlastHit` trace event per affected entity
970
+
971
+ ### Fall damage (Phase 10)
972
+
973
+ `applyFallDamage(world, entityId, height_m, tick, trace)` applies fall physics.
974
+ KE = mass × g × height; 85% absorbed by muscles (15% transmitted). Any fall ≥ 1 m forces prone.
975
+ Damage distributed: locomotion-primary regions 70%, others 30% (body-plan-aware).
976
+ Humanoid fallback: legs 35% each, arms 10% each, torso/head 5% each.
977
+
978
+ ### Pharmacokinetics (Phase 10)
979
+
980
+ One-compartment model per active substance (`entity.substances`). Each tick:
981
+ absorption rate × pendingDose absorbed into concentration; elimination rate × concentration removed.
982
+ Effects activate when concentration exceeds `effectThreshold`:
983
+
984
+ | Effect type | Per-tick effect |
985
+ |---|---|
986
+ | `stimulant` | Reduces `fearQ` and slows fatigue accumulation |
987
+ | `anaesthetic` | Erodes `consciousness` |
988
+ | `poison` | Internal damage to torso + mild shock |
989
+ | `haemostatic` | Reduces `bleedingRate` across all regions |
990
+
991
+ `STARTER_SUBSTANCES` provides four ready-made entries: `stimulant`, `anaesthetic`, `poison`, `haemostatic`.
992
+
993
+ ### Ambient temperature (Phase 10)
994
+
995
+ `KernelContext.ambientTemperature_Q` — comfort range `[q(0.35), q(0.65)]`.
996
+ - Heat (above q(0.65)): shock + torso surface damage; scaled by `heatTolerance`
997
+ - Cold (below q(0.35)): shock + fatigue accumulation; scaled by `coldTolerance`
998
+
999
+ ### Technology spectrum (Phase 11)
1000
+
1001
+ `TechContext` gates which items are usable in a scenario without locking them to a specific era.
1002
+
1003
+ ```typescript
1004
+ import { TechEra, defaultTechContext, isCapabilityAvailable } from "./src/sim/tech.js";
1005
+ import { validateLoadout, STARTER_EXOSKELETONS } from "./src/equipment.js";
1006
+
1007
+ const ctx = defaultTechContext(TechEra.NearFuture);
1008
+ // ctx.available includes PoweredExoskeleton but not EnergyWeapons
1009
+
1010
+ const exo = STARTER_EXOSKELETONS.find(e => e.id === "exo_combat")!;
1011
+ const loadout = { items: [exo] };
1012
+ const errors = validateLoadout(loadout, ctx); // [] — valid
1013
+ ```
1014
+
1015
+ **`Exoskeleton` item kind** — integrated with kernel:
1016
+ - `speedMultiplier: Q` — factored into `stepMovement` baseMul
1017
+ - `forceMultiplier: Q` — applied to strike `energy_J` in `resolveAttack`
1018
+ - `powerDrain_W: number` — added to metabolic demand in `stepEnergy`
1019
+
1020
+ **Starter items**: `exo_combat` (+25% speed, +40% force, 200 W drain),
1021
+ `exo_heavy` (+10% speed, +80% force, 400 W drain), `arm_plate` (800 J resist, requires `MetallicArmour`),
1022
+ `rng_plasma_rifle` (2000 J, requires `EnergyWeapons`).
1023
+
1024
+ Era capabilities are cumulative: Prehistoric has none; DeepSpace has all eight.
1025
+
1026
+ **Magic and para-science types** (Phase 12B) — four additional `TechCapability` values gate
1027
+ Clarke's Third Law capability effects: `ArcaneMagic`, `DivineMagic`, `Psionics`, `Nanotech`.
1028
+ These are not assigned to any era by `ERA_DEFAULTS`; host applications opt in explicitly.
1029
+
1030
+ **Medical technology gate**: `TIER_TECH_REQ` in `medical.ts` maps medical tiers to required
1031
+ capabilities. When `ctx.techCtx` is set, `resolveTreat()` blocks treatment if the treater's
1032
+ tier requires a capability absent from the scenario. Currently: `nanomedicine` tier requires
1033
+ `NanomedicalRepair`. Tiers without a listed requirement work in any era.
1034
+
1035
+ ---
1036
+
1037
+ ## Capability Sources and Effects (Phase 12)
1038
+
1039
+ **Clarke's Third Law**: a fireball and a plasma grenade, a healing spell and a nanobot swarm,
1040
+ a mana pool and a fusion reactor — all resolve through identical engine primitives. The engine
1041
+ cannot distinguish magic from technology. Only the `tags` field differs.
1042
+
1043
+ ### CapabilitySource
1044
+
1045
+ Attached to `entity.capabilitySources?: CapabilitySource[]`. Each source is an energy reservoir
1046
+ (always in joules) with one of five regen models:
1047
+
1048
+ | RegenModel type | Behaviour |
1049
+ |---|---|
1050
+ | `rest` | Regens only when entity is stationary and not in combat |
1051
+ | `constant` | Regens every tick regardless of activity |
1052
+ | `ambient` | Regens proportional to `ambientGrid` cell value at entity's position |
1053
+ | `event` | Fires on tick intervals or kill triggers |
1054
+ | `boundless` | Never depletes; cost deduction skipped entirely |
1055
+
1056
+ ### ActivateCommand
1057
+
1058
+ ```typescript
1059
+ { kind: "activate", sourceId: string, effectId: string, targetId?: number, targetPos?: Vec3 }
1060
+ ```
1061
+
1062
+ ### EffectPayload variants
1063
+
1064
+ | Payload kind | Effect | Engine primitive |
1065
+ |---|---|---|
1066
+ | `impact` | Kinetic, thermal, internal, or penetrating damage | `applyImpactToInjury` |
1067
+ | `treatment` | Healing at specified tier and rate multiplier | `resolveTreat` |
1068
+ | `armourLayer` | Temporary per-channel armour overlay | `condition.temporaryArmour` |
1069
+ | `velocity` | Direct velocity delta (telekinesis, jump jet) | velocity integration |
1070
+ | `substance` | Pharmacokinetic substance injection | `stepSubstances` |
1071
+ | `structuralRepair` | Structural damage write-back (respects `permanentDamage`) | injury state |
1072
+ | `fieldEffect` | Places suppression zone in `world.activeFieldEffects` | `stepFieldEffects` |
1073
+
1074
+ ### Phase 12B extensions
1075
+
1076
+ - **Per-capability cooldowns**: `cooldown_ticks?: number` on `CapabilityEffect`; tracked in
1077
+ `action.capabilityCooldowns` (Map, key = `"sourceId:effectId"`); decremented at tick start.
1078
+ - **TechCapability gating**: `requiredCapability?: TechCapability` on `CapabilityEffect`;
1079
+ checked against `ctx.techCtx` when set. Includes magic gates: `ArcaneMagic`, `DivineMagic`,
1080
+ `Psionics`, `Nanotech`.
1081
+ - **Magic resistance**: `magicResist?: Q` on `Resilience`; seeded roll per non-self target in
1082
+ `applyCapabilityEffect`; q(1.0) = always resist; self-cast bypasses entirely.
1083
+ - **Kill-triggered regen**: `{ on: "kill", amount_J }` in `EventRegen.triggers`; dispatched at
1084
+ entity death; all living non-dead observers receive the reward (including the killer).
1085
+ - **Terrain-entry triggers**: `{ on: "terrain", tag, amount_J }` fires exactly once per
1086
+ cell-boundary crossing; `action.lastCellKey` tracks the previous cell; supply
1087
+ `KernelContext.terrainTagGrid` (Map of cell key → tag array) and optional `cellSize_m`.
1088
+ - **Concentration auras**: `castTime_ticks = -1` marks an ongoing per-tick effect;
1089
+ `entity.activeConcentration` holds the active aura; `cost_J` is deducted every tick;
1090
+ concentration breaks when reserve falls below `cost_J` or shock reaches q(0.30), emitting
1091
+ `CastInterrupted`.
1092
+ - **Linked sources**: `CapabilitySource.linkedFallbackId` names a secondary source to draw
1093
+ from when the primary is depleted; fallback can be `boundless` for unlimited overflow.
1094
+ - **Effect chains**: `FieldEffectSpec.chainPayload?: EffectPayload | EffectPayload[]` — payload
1095
+ applied to every living entity within the field's radius each tick while the field is active;
1096
+ fires before expiry so the final tick still delivers the payload.
1097
+
1098
+ ---
1099
+
1100
+ ## 3D model integration (Phase 14)
1101
+
1102
+ `src/model3d.ts` provides pure data-extraction functions for driving 3D character rigs from
1103
+ simulation state. No kernel changes, no state mutations. Call once per tick after `stepWorld`.
1104
+
1105
+ ```typescript
1106
+ import { extractRigSnapshots } from "./src/model3d.js";
1107
+
1108
+ const snapshots = extractRigSnapshots(world);
1109
+ for (const snap of snapshots) {
1110
+ // snap.mass — MassDistribution: per-segment mass and estimated CoG in real metres
1111
+ // snap.inertia — InertiaTensor: yaw/pitch/roll moment of inertia (kg·m²)
1112
+ // snap.animation — AnimationHints: locomotion blend weights, guarding, attacking, prone, dead
1113
+ // snap.pose — PoseModifier[]: per-region injury deformation blend weights
1114
+ // snap.grapple — GrapplePoseConstraint: relative pose lock for grappling pairs
1115
+ hostRenderer.updateRig(snap.entityId, snap);
1116
+ }
1117
+ ```
1118
+
1119
+ **Mass and inertia** are derived from `entity.bodyPlan` segment masses via canonical keyword
1120
+ matching (`head`, `torso`, `forearm`, `thigh`, `shin`, `wing`, etc.); fallback to a solid-sphere
1121
+ approximation when no body plan is present.
1122
+
1123
+ **Animation hints** — locomotion weights (`idle`/`walk`/`run`/`sprint`/`crawl`) are mutually
1124
+ exclusive; exactly one is `SCALE.Q` when mobile. Overlays include `guardingQ`, `attackingQ`,
1125
+ `shockQ`, `fearQ`, and boolean flags `prone`, `unconscious`, `dead`.
1126
+
1127
+ **Pose modifiers** — one entry per `injury.byRegion` key. `impairmentQ = max(structuralQ, surfaceQ)`.
1128
+ Map each `segmentId` to a skeleton bone and drive blend-shape or constraint weights.
1129
+
1130
+ **Grapple constraints** — `isHolder`/`isHeld` flags, `holdingEntityId`, `heldByIds`, `position`
1131
+ (`standing`/`prone`/`pinned`), and `gripQ`. Use to lock relative pose between grappling entities.
1132
+
1133
+ **Integration note:** These functions provide data snapshots only. Mapping Ananke's abstract
1134
+ segment IDs to a specific engine's skeleton, handling tick-rate mismatch (20 Hz → 60+ Hz), and
1135
+ wiring animation hints into a state machine are the host's responsibility. See
1136
+ `docs/bridge-api.md` for the full API reference and `docs/ecosystem.md` for Unity/Godot adapter
1137
+ sketches. A minimal runnable reference plugin (ROADMAP item 6) is the next priority for
1138
+ lowering this integration barrier.
1139
+
1140
+ ---
1141
+
1142
+ ## API stability contract
1143
+
1144
+ > Full reference: [`STABLE_API.md`](./STABLE_API.md) — Versioning policy: [`CHANGELOG.md`](./CHANGELOG.md)
1145
+
1146
+ Ananke distinguishes three tiers of API stability so adopters know what to pin and what to
1147
+ expect to change.
1148
+
1149
+ | Tier | What | Promise |
1150
+ |------|------|---------|
1151
+ | **Stable host API** | `stepWorld()`, `generateIndividual()`, `resolveHit()`, `Entity` shape (core fields), `q()` / `qMul()` / `clampQ()`, `ReplayRecorder` / `replayTo()`, `serializeReplay()` / `deserializeReplay()`, `extractRigSnapshots()` | No breaking changes without a major version bump and migration guide |
1152
+ | **Experimental extension API** | `stepPolityDay()`, `stepTechDiffusion()`, `applyEmotionalContagion()`, `compressMythsFromHistory()`, `stepNarrativeStress()`, `arena.ts` scenario DSL | Good-faith stability; may shift between minor versions with changelog |
1153
+ | **Internal kernel structures** | `src/sim/push.ts`, `src/sim/kernel.ts` internals, `src/rng.ts`, `eventSeed()` | No stability promise; do not import directly |
1154
+
1155
+ All replay, serialization, and campaign round-trip guarantees apply only to the Stable tier.
1156
+ Experimental APIs are safe to use but may require migration on minor version updates.
1157
+
1158
+ ---
1159
+
1160
+ ## Determinism rules
1161
+
1162
+ To maintain lockstep safety:
1163
+
1164
+ - Never use `Math.random()`
1165
+ - Avoid floating point in simulation path
1166
+ - Iterate in stable, deterministic order
1167
+ - Consume RNG in fixed sequence per event type
1168
+ - Use deterministic event batching
1169
+ - Avoid unordered map iteration for gameplay logic
1170
+ - All seeds derived from `(worldSeed, tick, entityId, eventType)` — no global state
1171
+
1172
+ ---
1173
+
1174
+ ## AI and Perception
1175
+
1176
+ Deterministic AI modules (Phase 4):
1177
+
1178
+ - `sensory.ts` — `canDetect()`: vision arc, hearing range, environmental multipliers
1179
+ - `perception.ts` — `perceiveLocal()`: sensory-filtered enemy and ally detection
1180
+ - `targeting.ts` — `pickTarget()`, `updateFocus()`: horizon-limited, focus stickiness
1181
+ - `decide.ts` — `decideCommandsForEntity()`: behaviour presets with decision latency
1182
+ - `presets.ts` — `AI_PRESETS`: lineInfantry, skirmisher
1183
+ - `system.ts` — `buildAICommands()`: full AI pass over world
1184
+
1185
+ **Decision latency**: entities re-plan at most once every `decisionLatency_s` seconds
1186
+ (10 ticks for humans, 1 tick for robots). Between plans, previous intent persists.
1187
+
1188
+ **Surprise mechanics**: `canDetect(defender, attacker, env)` returns detection quality Q.
1189
+ If attacker is outside defender's FoV (< q(0.8)), defensive intensity is scaled
1190
+ proportionally. Full surprise (q(0)) eliminates defensive reaction entirely.
1191
+
1192
+ ---
1193
+
1194
+ ## Skills (Phase 7)
1195
+
1196
+ Skills represent learned technique — adjustments to physical outcomes, not abstract point totals.
1197
+ They are provided by the host application and consumed by the engine. The engine never writes
1198
+ back to skill values; progression is the host's responsibility.
1199
+
1200
+ ### Skill map
1201
+
1202
+ Each entity carries an optional `skills?: SkillMap` (`Map<SkillId, SkillLevel>`).
1203
+ When a skill is absent, `getSkill()` returns neutral defaults (no effect on simulation output),
1204
+ making all existing entities fully backward-compatible.
1205
+
1206
+ ```typescript
1207
+ import { buildSkillMap } from "./src/sim/skills.js";
1208
+
1209
+ entity.skills = buildSkillMap({
1210
+ meleeCombat: { hitTimingOffset_s: -to.s(0.2), energyTransferMul: q(1.35) },
1211
+ meleeDefence: { energyTransferMul: q(1.5) },
1212
+ athleticism: { fatigueRateMul: q(0.75) },
1213
+ });
1214
+ ```
1215
+
1216
+ ### Skill domains
1217
+
1218
+ | SkillId | Physical effect |
1219
+ |---|---|
1220
+ | `meleeCombat` | `hitTimingOffset_s` reduces attack cooldown; `energyTransferMul` scales strike energy |
1221
+ | `meleeDefence` | `energyTransferMul` scales effective parry/block quality |
1222
+ | `grappling` | `energyTransferMul` scales grapple contest score |
1223
+ | `rangedCombat` | `dispersionMul` tightens grouping radius (more accurate fire) |
1224
+ | `throwingWeapons` | `energyTransferMul` scales thrown weapon launch energy |
1225
+ | `shieldCraft` | `energyTransferMul` boosts defence skill when blocking with a shield |
1226
+ | `medical` | `treatmentRateMul` reduces fluid loss from bleeding each tick |
1227
+ | `athleticism` | `fatigueRateMul` reduces fatigue accumulation per energy tick |
1228
+ | `tactics` | `hitTimingOffset_s` reduces AI decision latency |
1229
+ | `stealth` | `dispersionMul` reduces the subject's acoustic signature (harder to hear) |
1230
+
1231
+ ### Composing skill levels
1232
+
1233
+ `combineSkillLevels(a, b)` multiplies Q fields and adds time offsets, letting the host express
1234
+ synergy bonuses or composite experience modifiers before building the SkillMap:
1235
+
1236
+ ```typescript
1237
+ import { combineSkillLevels, defaultSkillLevel } from "./src/sim/skills.js";
1238
+
1239
+ // Melee expert with an athleticism timing synergy (−0.25 s total)
1240
+ const combined = combineSkillLevels(
1241
+ { ...defaultSkillLevel(), hitTimingOffset_s: -to.s(0.20), energyTransferMul: q(1.40) },
1242
+ { ...defaultSkillLevel(), hitTimingOffset_s: -to.s(0.05) },
1243
+ );
1244
+ entity.skills = buildSkillMap({ meleeCombat: combined });
1245
+ ```
1246
+
1247
+ ---
1248
+
1249
+ ## Replay and analytics (Phase 13)
1250
+
1251
+ ### Deterministic replay
1252
+
1253
+ Every simulation is reproducible from initial state + command log. `ReplayRecorder` snapshots
1254
+ the world before the first tick and records command maps per tick. `replayTo` reconstructs
1255
+ any past tick by restoring the snapshot and re-applying frames.
1256
+
1257
+ ```typescript
1258
+ import { ReplayRecorder, replayTo, serializeReplay, deserializeReplay } from "./src/replay.js";
1259
+
1260
+ const recorder = new ReplayRecorder(world);
1261
+ for (let i = 0; i < 100; i++) {
1262
+ recorder.record(world.tick, cmds);
1263
+ stepWorld(world, cmds, ctx);
1264
+ }
1265
+
1266
+ // Seek to any past tick
1267
+ const worldAt50 = replayTo(recorder.toReplay(), 50, ctx);
1268
+
1269
+ // Persist and restore across sessions
1270
+ const json = serializeReplay(recorder.toReplay());
1271
+ const restored = deserializeReplay(json);
1272
+ const worldAt50Again = replayTo(restored, 50, ctx);
1273
+ ```
1274
+
1275
+ `serializeReplay`/`deserializeReplay` handle `Map` fields (`entity.armourState`,
1276
+ `action.capabilityCooldowns`) that `JSON.stringify` would otherwise drop.
1277
+
1278
+ ### Combat analytics
1279
+
1280
+ `CollectingTrace` implements `TraceSink` and accumulates all trace events for offline
1281
+ analysis. Pass it to `stepWorld` via `ctx.trace`, then extract metrics.
1282
+
1283
+ ```typescript
1284
+ import { CollectingTrace, collectMetrics, survivalRate, meanTimeToIncapacitation }
1285
+ from "./src/metrics.js";
1286
+
1287
+ const tracer = new CollectingTrace();
1288
+ for (let i = 0; i < 200; i++) stepWorld(world, cmds, { ...ctx, trace: tracer });
1289
+
1290
+ const m = collectMetrics(tracer.events);
1291
+ console.log("Damage dealt by entity 1:", m.damageDealt.get(1)); // joules
1292
+ console.log("Hits landed by entity 1:", m.hitsLanded.get(1));
1293
+ console.log("Survival rate:", survivalRate(tracer.events, [1, 2, 3, 4]));
1294
+ console.log("Mean TTI (ticks):", meanTimeToIncapacitation(tracer.events, [1, 2, 3, 4], 200));
1295
+ ```
1296
+
1297
+ `collectMetrics` covers melee `Attack` events and ranged `ProjectileHit` events in a single
1298
+ pass over any flat `TraceEvent[]` — ordering and tick mixing are fine.
1299
+
1300
+ ### Visual debug layer (Phase 13)
1301
+
1302
+ `extractMotionVectors`, `extractHitTraces`, and `extractConditionSamples` in `src/debug.ts`
1303
+ transform world state and trace events into renderer-friendly snapshots:
1304
+
1305
+ ```typescript
1306
+ import { extractMotionVectors, extractHitTraces, extractConditionSamples } from "./src/debug.js";
1307
+ import { CollectingTrace } from "./src/metrics.js";
1308
+
1309
+ const tracer = new CollectingTrace();
1310
+ stepWorld(world, commands, { ...ctx, trace: tracer });
1311
+
1312
+ // Motion overlay — one entry per entity with position, velocity, and facing
1313
+ const arrows = extractMotionVectors(world);
1314
+
1315
+ // Hit display — melee and projectile hits with region and energy
1316
+ const { meleeHits, projectileHits } = extractHitTraces(tracer.events);
1317
+
1318
+ // Condition heatmap — fear, shock, consciousness, fluid loss per entity
1319
+ const heatmap = extractConditionSamples(world);
1320
+
1321
+ // Sample any past tick via replay
1322
+ const past = replayTo(recorder.toReplay(), 42, ctx);
1323
+ const pastHeatmap = extractConditionSamples(past);
1324
+ ```
1325
+
1326
+ ---
1327
+
1328
+ ## Character description layer (Phase 16)
1329
+
1330
+ `src/describe.ts` translates raw SI fixed-point attributes into human-readable summaries.
1331
+ No simulation dependencies — safe to import from any host application.
1332
+
1333
+ ```typescript
1334
+ import { describeCharacter, formatCharacterSheet, formatOneLine } from "./src/describe.js";
1335
+ import { generateIndividual } from "./src/generate.js";
1336
+ import { PRO_BOXER } from "./src/archetypes.js";
1337
+
1338
+ const attrs = generateIndividual(7, PRO_BOXER);
1339
+ const desc = describeCharacter(attrs);
1340
+
1341
+ console.log(formatOneLine(desc));
1342
+ // → "Tall (1.83 m), 86.4 kg; strength excellent (4982 N), reaction quick (180 ms), resilience tough."
1343
+
1344
+ console.log(formatCharacterSheet(desc));
1345
+ // Body
1346
+ // Stature: 1.83 m — tall
1347
+ // Mass: 86.4 kg — average build
1348
+ //
1349
+ // Performance
1350
+ // Strength: 4982 N [excellent] elite level — professional fighter strength
1351
+ // Power: 2214 W [excellent] elite explosive output
1352
+ // Endurance: 398 W [strong] strong sustained performance
1353
+ // Stamina: 40 kJ [strong] good combat energy reserves
1354
+ // ...
1355
+ ```
1356
+
1357
+ ### Tier system
1358
+
1359
+ Every attribute is rated 1–6 using breakpoints grounded in sports-science literature:
1360
+
1361
+ | Tier | Label | Meaning |
1362
+ |------|-------|---------|
1363
+ | 1 | feeble / sluggish / fragile | Well below human baseline |
1364
+ | 2 | weak / slow / low | Below-average adult |
1365
+ | 3 | average | Baseline healthy adult (HUMAN_BASE anchor) |
1366
+ | 4 | strong / precise / resilient | Trained athlete or competitive fighter |
1367
+ | 5 | excellent / fast / tough | Elite / professional level |
1368
+ | 6 | exceptional / instant / ironclad | Superhuman, mechanical, or distributed biology |
1369
+
1370
+ Reaction time and decision latency use an **inverted** scale (lower value = higher tier).
1371
+ `SERVICE_ROBOT` (80 ms reaction, 50 ms decision) → tier 6 "instant" / "machine-like" in both.
1372
+ `LARGE_PACIFIC_OCTOPUS` (no enclosed skull) → tier 6 "ironclad" concussion resistance.
1373
+
1374
+ ### API
1375
+
1376
+ ```typescript
1377
+ describeCharacter(attrs: IndividualAttributes): CharacterDescription
1378
+ // Returns a structured object with tier, label, comparison string, and formatted value
1379
+ // for every attribute. Vision and hearing are formatted strings (e.g. "200 m, 120° arc").
1380
+
1381
+ formatCharacterSheet(desc: CharacterDescription): string
1382
+ // Multi-line columnar output suitable for a character screen or debug log.
1383
+
1384
+ formatOneLine(desc: CharacterDescription): string
1385
+ // Single sentence, no newlines — suitable for tooltips or list views.
1386
+ ```
1387
+
1388
+ ---
1389
+
1390
+ ## Combat narrative layer (Phase 18)
1391
+
1392
+ `src/narrative.ts` converts raw `TraceEvent` streams into human-readable text. Like
1393
+ `src/describe.ts`, it has no simulation dependencies — safe to import from UI code or CLI tools.
1394
+
1395
+ ```typescript
1396
+ import { narrateEvent, buildCombatLog, describeInjuries, describeCombatOutcome }
1397
+ from "./src/narrative.js";
1398
+ import { CollectingTrace } from "./src/metrics.js";
1399
+ import { ALL_HISTORICAL_MELEE } from "./src/weapons.js";
1400
+
1401
+ const tracer = new CollectingTrace();
1402
+ for (let i = 0; i < 10 * TICK_HZ; i++) stepWorld(world, cmds, { ...ctx, trace: tracer });
1403
+
1404
+ // Build weapon profile lookup for verb selection
1405
+ const profiles = new Map(ALL_HISTORICAL_MELEE.map(w => [w.id, w.damage]));
1406
+
1407
+ const cfg = {
1408
+ verbosity: "normal" as const,
1409
+ nameMap: new Map([[1, "Sir Roland"], [2, "the orc"]]),
1410
+ weaponProfiles: profiles,
1411
+ };
1412
+
1413
+ // Per-event narration
1414
+ for (const ev of tracer.events) {
1415
+ const line = narrateEvent(ev, cfg);
1416
+ if (line) console.log(line);
1417
+ }
1418
+ // → "Sir Roland stabs the orc in the torso"
1419
+ // → "the orc attacks Sir Roland — parried"
1420
+ // → "Sir Roland powerfully stabs the orc in the head"
1421
+ // → "the orc is knocked unconscious"
1422
+
1423
+ // Batch log
1424
+ const log = buildCombatLog(tracer.events, cfg);
1425
+
1426
+ // Injury summary
1427
+ const orc = world.entities.find(e => e.id === 2)!;
1428
+ console.log(describeInjuries(orc.injury));
1429
+ // → "Unconscious; Significant blood loss; head fractured"
1430
+
1431
+ // Outcome
1432
+ const summary = describeCombatOutcome(
1433
+ world.entities.map(e => ({ id: e.id, teamId: e.teamId, injury: e.injury })),
1434
+ 200,
1435
+ );
1436
+ console.log(summary);
1437
+ // → "Team 1 wins — Team 2 defeated (200 ticks)"
1438
+ ```
1439
+
1440
+ ### Verbosity levels
1441
+
1442
+ | Level | What is included |
1443
+ |-------|-----------------|
1444
+ | `terse` | Landed hits, KO, death, morale route/rally, fractures, blasts — nothing else |
1445
+ | `normal` | Adds blocked/parried/shield notes, misses, grapple start/break, weapon bind, treatment |
1446
+ | `verbose` | Adds grapple maintenance ticks, capability events |
1447
+
1448
+ ### Verb selection
1449
+
1450
+ Verb is chosen from the weapon's `WeaponDamageProfile` (supplied via `weaponProfiles` config):
1451
+
1452
+ | Profile dominant field | Verb |
1453
+ |------------------------|------|
1454
+ | `penetrationBias ≥ q(0.65)` | stab(s) |
1455
+ | `structuralFrac ≥ q(0.50)` | bludgeon(s) |
1456
+ | `surfaceFrac ≥ q(0.50)` | slash(es) |
1457
+ | Default | strike(s) |
1458
+
1459
+ Ranged: `penetrationBias ≥ q(0.80)` → snipe(s); `surfaceFrac ≥ q(0.55)` → blast(s); default → shoot(s).
1460
+
1461
+ Energy qualifiers: `< 10J` → "barely grazes"; `≥ 200J` → "powerfully"; `≥ 500J` → "devastatingly".
1462
+
1463
+ Set an entity's name to `"you"` in `nameMap` for second-person verb conjugation
1464
+ ("you strike" rather than "you strikes").
1465
+
1466
+ ---
1467
+
1468
+ ## Downtime & Recovery simulation (Phase 19)
1469
+
1470
+ `src/downtime.ts` bridges the gap between 20 Hz combat and hours-to-weeks campaign time.
1471
+ It runs a compressed 1 Hz loop applying the same healing physics as the kernel, suitable
1472
+ for computing wound outcomes, resource consumption, and recovery timelines between sessions.
1473
+
1474
+ ```typescript
1475
+ import { stepDowntime, MEDICAL_RESOURCES } from "./src/downtime.js";
1476
+
1477
+ const reports = stepDowntime(world, 3600, {
1478
+ treatments: new Map([
1479
+ [1, { careLevel: "first_aid" }],
1480
+ [2, { careLevel: "field_medicine", onsetDelay_s: 120 }],
1481
+ ]),
1482
+ rest: true,
1483
+ });
1484
+
1485
+ for (const r of reports) {
1486
+ console.log(`Entity ${r.entityId}: died=${r.died}, bleedingStopped=${r.bleedingStopped}`);
1487
+ console.log(` Fluid loss: ${r.injuryAtStart.fluidLoss} → ${r.injuryAtEnd.fluidLoss}`);
1488
+ console.log(` Resources used: ${r.totalCostUnits} cost units`);
1489
+ console.log(` Combat ready in: ${r.combatReadyAt_s}s`);
1490
+ }
1491
+ ```
1492
+
1493
+ **Care levels:** `"none"` | `"first_aid"` | `"field_medicine"` | `"hospital"` | `"autodoc"` | `"nanomedicine"`
1494
+
1495
+ **Resource catalogue** (`MEDICAL_RESOURCES`): 7 items from field bandage (1 unit) to nanomed dose (2000 units). All items have `costUnits` and `massGrams` for encumbrance and economy integration.
1496
+
1497
+ **Calibration:** `q(0.06)` bleedingRate → fatal in ~267 simulated seconds without treatment. First aid stops bleeding in under 60 seconds. Untreated infection fatal within 21 simulated days.
1498
+
1499
+ ---
1500
+
1501
+ ## Arena simulation framework (Phase 20)
1502
+
1503
+ `src/arena.ts` provides a declarative scenario DSL for batch-running combat trials with statistical expectations. Use it to validate simulation realism, balance archetypes, and author calibration tests.
1504
+
1505
+ ```typescript
1506
+ import { runArena, expectWinRate, expectSurvivalRate, formatArenaReport }
1507
+ from "./src/arena.js";
1508
+ import { mkKnight, mkBoxer } from "./src/presets.js";
1509
+
1510
+ const scenario = {
1511
+ name: "Knight vs Boxer",
1512
+ combatants: [
1513
+ { id: 1, teamId: 1, factory: () => mkKnight(1, 1, 0, 0) },
1514
+ { id: 2, teamId: 2, factory: () => mkBoxer(2, 2, 1, 0) },
1515
+ ],
1516
+ maxTicks: 400,
1517
+ expectations: [
1518
+ expectWinRate(1, 0.70), // knight wins ≥ 70% of trials
1519
+ expectSurvivalRate(1, 1, 0.90), // knight survives ≥ 90%
1520
+ ],
1521
+ };
1522
+
1523
+ const result = runArena(scenario, 50);
1524
+ console.log(formatArenaReport(result));
1525
+ ```
1526
+
1527
+ **Six built-in calibration scenarios** validate core physics:
1528
+ `CALIBRATION_ARMED_VS_UNARMED`, `CALIBRATION_UNTREATED_KNIFE_WOUND`,
1529
+ `CALIBRATION_FIRST_AID_SAVES_LIVES`, `CALIBRATION_FRACTURE_RECOVERY`,
1530
+ `CALIBRATION_INFECTION_UNTREATED`, `CALIBRATION_PLATE_ARMOUR`.
1531
+
1532
+ **Recovery integration:** supply `scenario.recovery` to run `stepDowntime` post-combat and get `recoveryStats` (mean days to combat-ready, p90 resource cost, etc.).
1533
+
1534
+ ---
1535
+
1536
+ ## Character progression (Phase 21)
1537
+
1538
+ `src/progression.ts` adds the temporal axis: how entities improve through training and experience, decline through ageing, and carry permanent marks from injury. No simulation dependencies — safe to use in save-game serialisation and UI layers.
1539
+
1540
+ ```typescript
1541
+ import {
1542
+ createProgressionState, awardXP, advanceSkill,
1543
+ applyTrainingSession, stepAgeing, applyAgeingDelta,
1544
+ deriveSequelae, serialiseProgression,
1545
+ } from "./src/progression.js";
1546
+ import { buildSkillMap } from "./src/sim/skills.js";
1547
+ import { to, q } from "./src/units.js";
1548
+
1549
+ // XP and milestones
1550
+ const prog = createProgressionState();
1551
+ const milestones = awardXP(prog, "meleeCombat", 1, world.tick); // +1 XP per combat
1552
+ for (const m of milestones) {
1553
+ entity.skills = advanceSkill(entity.skills ?? buildSkillMap({}), m.domain, m.delta);
1554
+ }
1555
+
1556
+ // Physical training (peakForce_N ceiling 3500 N)
1557
+ const plan = { sessions: [], frequency_d: 3/7, ceiling: to.N(3500) };
1558
+ const sess = { attribute: "peakForce_N", intensity_Q: q(0.50), duration_s: 3600 };
1559
+ entity.attributes.performance.peakForce_N =
1560
+ applyTrainingSession(entity.attributes.performance.peakForce_N, plan, sess, 3);
1561
+
1562
+ // Annual ageing
1563
+ const delta = stepAgeing(entity.attributes, entity.ageYears ?? 30);
1564
+ applyAgeingDelta(entity.attributes, delta);
1565
+
1566
+ // Injury sequelae after a bad fracture
1567
+ const seqs = deriveSequelae(entity.injury.byRegion["leftLeg"]!, entity.bodyPlan!);
1568
+ for (const s of seqs) prog.sequelae.push({ region: "leftLeg", ...s });
1569
+
1570
+ // Persist
1571
+ const json = serialiseProgression(prog); // Map-aware JSON
1572
+ ```
1573
+
1574
+ **Milestone thresholds** grow geometrically (`BASE_XP=20`, `GROWTH_FACTOR=1.80`): milestone 0 at 20 XP, milestone 1 at 36, milestone 2 at 65, milestone 3 at 117… Calibrated so 100 combats (1 XP each) reduce `meleeCombat` reaction time by ~80 ms.
1575
+
1576
+ **Training calibration:** 12-week strength programme (3×/week, moderate intensity) raises `peakForce_N` by 150–300 N. Overtraining penalty applies above 5 sessions/week.
1577
+
1578
+ **Ageing:** 1 %/year performance decline after 35; +2 ms decision latency per year after 45. Integrating from age 20 to 70 always stays above zero (physically plausible minimum).
1579
+
1580
+ **Sequelae types:** `fracture_malunion` (−15 % peak force), `nerve_damage` (−10 % fine control), `scar_tissue` (surface bleed threshold −5 %).
1581
+
1582
+ ---
1583
+
1584
+ ## Campaign & World State (Phase 22)
1585
+
1586
+ `src/campaign.ts` is the persistence layer for multi-session campaigns. It tracks world time,
1587
+ entity state, location, and item stockpiles between encounters, and delegates wound recovery
1588
+ to `stepDowntime`.
1589
+
1590
+ ```typescript
1591
+ import {
1592
+ createCampaign, addLocation, travel,
1593
+ creditInventory, debitInventory, getInventoryCount,
1594
+ stepCampaignTime, mergeEntityState,
1595
+ serialiseCampaign, deserialiseCampaign,
1596
+ } from "./src/campaign.js";
1597
+
1598
+ // Create a campaign with starting entities
1599
+ const campaign = createCampaign("my-campaign", [fighter1, fighter2], "2025-01-01T00:00:00Z");
1600
+
1601
+ // Register locations with travel costs (seconds)
1602
+ addLocation(campaign, { id: "town", name: "Town", elevation_m: 50, travelCost: new Map() });
1603
+ addLocation(campaign, { id: "dungeon", name: "Dungeon", elevation_m: 20,
1604
+ travelCost: new Map([["town", 1800]]) }); // 30 min from dungeon to town
1605
+
1606
+ // Move an entity — advances worldTime_s by travel cost
1607
+ const travelTime = travel(campaign, fighter1.id, "dungeon"); // 1800
1608
+
1609
+ // After an encounter, merge updated entity states back into the registry
1610
+ mergeEntityState(campaign, [fighter1, fighter2]);
1611
+
1612
+ // Advance time with wound recovery (delegates to stepDowntime)
1613
+ const reports = stepCampaignTime(campaign, 3600, {
1614
+ downtimeConfig: {
1615
+ treatments: new Map([[fighter1.id, { careLevel: "first_aid" }]]),
1616
+ rest: true,
1617
+ },
1618
+ });
1619
+
1620
+ // Campaign inventory (arrows, bandages, rations, etc.)
1621
+ creditInventory(campaign, fighter1.id, "arrow", 30);
1622
+ debitInventory(campaign, fighter1.id, "arrow", 5); // returns false if insufficient
1623
+ getInventoryCount(campaign, fighter1.id, "arrow"); // 25
1624
+
1625
+ // Persist across sessions (Map-aware JSON)
1626
+ const json = serialiseCampaign(campaign);
1627
+ const restored = deserialiseCampaign(json);
1628
+ ```
1629
+
1630
+ **`CampaignState` fields:**
1631
+ - `id`, `epoch` — campaign identity and ISO start timestamp
1632
+ - `worldTime_s` — absolute simulated seconds since epoch (monotonically increasing)
1633
+ - `entities: Map<number, Entity>` — master registry; deep-cloned on write
1634
+ - `locations: Map<string, Location>` — registered locations with travel routing
1635
+ - `entityLocations: Map<number, string>` — current locationId per entity
1636
+ - `entityInventories: Map<number, Map<string, number>>` — campaign item stockpiles (separate from `entity.loadout`)
1637
+ - `log: Array<{ worldTime_s, text }>` — timestamped event log
1638
+
1639
+ **Healing integration:** `stepCampaignTime` builds a minimal `WorldState`, calls `stepDowntime`,
1640
+ then writes the healed `InjuryState` back into the entity registry. The optional
1641
+ `downtimeConfig` matches `stepDowntime`'s `DowntimeConfig` exactly; if omitted, all entities
1642
+ rest with `careLevel: "none"` (natural clotting only).
1643
+
1644
+ **Serialisation:** `serialiseCampaign`/`deserialiseCampaign` handle all nested `Map` fields
1645
+ using the `__ananke_map__` marker pattern — entities, locations, entityLocations,
1646
+ entityInventories, entity skills, armourState, and location travelCost all survive round-trip.
1647
+
1648
+ ---
1649
+
1650
+ ## Dialogue & Negotiation (Phase 23)
1651
+
1652
+ `src/dialogue.ts` resolves non-combat social encounters. No Charisma stat — intimidation
1653
+ strength comes from `peakForce_N`, persuasion from cognitive depth, deception is beaten by
1654
+ sharp minds. Fully deterministic when given a seed.
1655
+
1656
+ ```typescript
1657
+ import {
1658
+ resolveDialogue, applyDialogueOutcome, narrateDialogue, dialogueProbability,
1659
+ type DialogueContext,
1660
+ } from "./src/dialogue.js";
1661
+
1662
+ const ctx: DialogueContext = {
1663
+ initiator: knight,
1664
+ target: bandit,
1665
+ worldSeed: world.seed,
1666
+ tick: world.tick,
1667
+ };
1668
+
1669
+ // Check probability before rolling
1670
+ const P = dialogueProbability({ kind: "intimidate", intensity_Q: q(0.80) }, ctx);
1671
+ // → q(0.72) for a 2800N knight vs. a mid-fear bandit
1672
+
1673
+ // Roll the outcome
1674
+ const outcome = resolveDialogue({ kind: "intimidate", intensity_Q: q(0.80) }, ctx);
1675
+ // → { result: "success", fearDelta: q(0.15) } or { result: "failure", cooldown_s: 30 }
1676
+ // → { result: "escalate" } if target.fearQ < q(0.20) and failed
1677
+
1678
+ // Write deltas back to entities
1679
+ applyDialogueOutcome(outcome, bandit);
1680
+
1681
+ // Natural language description
1682
+ const line = narrateDialogue(
1683
+ { kind: "intimidate", intensity_Q: q(0.80) }, outcome,
1684
+ { verbosity: "verbose", nameMap: new Map([[1, "Sir Roland"], [2, "the bandit"]]) },
1685
+ { initiatorId: 1, targetId: 2 },
1686
+ );
1687
+ // → "Sir Roland attempted intimidation on the bandit — the target was cowed by the show of force."
1688
+ ```
1689
+
1690
+ ### Action types
1691
+
1692
+ | Action | Resolution | Escalates? |
1693
+ |--------|-----------|------------|
1694
+ | `intimidate` | `q(peakForce_N/4000) + fearQ − distressTolerance`; leader trait −q(0.15) | Yes, if fearQ < q(0.20) |
1695
+ | `persuade` | base q(0.40) + learningBonus(attentionDepth) + factionBonus − failurePenalty | No |
1696
+ | `deceive` | `plausibility_Q × (1 − attentionDepth/10)` | No |
1697
+ | `surrender` | Deterministic: accepted if `target.fearQ > q(0.40)` | No |
1698
+ | `negotiate` | Deterministic: accepted if trade utility is positive for target | No |
1699
+
1700
+ **`dialogueProbability(action, ctx)`** — exported helper that returns the success probability
1701
+ without rolling RNG. Useful for UI previews, AI decision-making, and testing.
1702
+
1703
+ **Verbosity levels** match the narrative layer: `terse` (label: result), `normal` (one sentence),
1704
+ `verbose` (entity names + full outcome description). Entity names resolved from `cfg.nameMap`.
1705
+
1706
+ ---
1707
+
1708
+ ## Faction & Reputation (Phase 24)
1709
+
1710
+ `src/faction.ts` tracks political standing, witnesses hostile events, and modulates AI
1711
+ behaviour based on inter-faction relationships. Fully deterministic — no RNG, standing
1712
+ changes are pure arithmetic.
1713
+
1714
+ ```typescript
1715
+ import {
1716
+ createFactionState, adjustStanding, getStanding,
1717
+ extractWitnessEvents, applyReputationDelta,
1718
+ type FactionState,
1719
+ } from "./src/faction.js";
1720
+ ```
1721
+
1722
+ - **`adjustStanding(state, factionId, delta)`** — clamp standing to [−SCALE.Q, SCALE.Q]
1723
+ - **`getStanding(state, factionId)`** — 0 for unknown factions
1724
+ - **`extractWitnessEvents(world, env)`** — returns assault events seen by bystanders
1725
+ - **`applyReputationDelta`** — propagate witness events into faction standing
1726
+
1727
+ AI target-selection uses `STANDING_HOSTILE_THRESHOLD = q(−0.40)` to activate attack
1728
+ mode; the `factionGuard` preset additionally suppresses attack below standing `q(0.60)`.
1729
+
1730
+ ---
1731
+
1732
+ ## Loot & Economy (Phase 25)
1733
+
1734
+ `src/economy.ts` provides item valuation, equipment wear, loot drop resolution, and
1735
+ trade evaluation. No kernel dependency — pure data management.
1736
+
1737
+ ```typescript
1738
+ import {
1739
+ computeItemValue, armourConditionQ, applyWear,
1740
+ resolveDrops, evaluateTradeOffer, totalInventoryValue,
1741
+ type ItemInventory, type TradeOffer, type DropTable,
1742
+ } from "./src/economy.js";
1743
+ ```
1744
+
1745
+ - **`computeItemValue(item, wear_Q?)`** — derives `baseValue`, `condition_Q`, `sellFraction`
1746
+ for any weapon, armour, or medical resource
1747
+ - **`applyWear(weapon, intensity_Q, seed?)`** — accumulates `WEAR_BASE = q(0.001)` per strike;
1748
+ penalty at `q(0.30)`, fumble at `q(0.70)`, breaks at `q(1.0)`
1749
+ - **`resolveDrops(entity, seed, extra?, config?)`** — dead → all equipped items drop;
1750
+ probabilistic extras rolled deterministically from seed
1751
+ - **`evaluateTradeOffer(offer, inventory)`** — `netValue` + `feasible` from accepting party's view
1752
+
1753
+ ---
1754
+
1755
+ ## Momentum Transfer & Knockback (Phase 26)
1756
+
1757
+ `src/sim/knockback.ts` adds the impulse-momentum half of Newtonian mechanics: every
1758
+ impact now imparts a velocity delta to the target. Calibrated to real physics — a
1759
+ 5.56 mm rifle round produces negligible knockback (≈ 0.05 m/s on 75 kg), while a
1760
+ large-creature kick can knock humans prone.
1761
+
1762
+ ```typescript
1763
+ import {
1764
+ computeKnockback, applyKnockback,
1765
+ STAGGER_THRESHOLD_mps, PRONE_THRESHOLD_mps, STAGGER_TICKS,
1766
+ } from "./src/sim/knockback.js";
1767
+
1768
+ const result = computeKnockback(energy_J, massEff_kg, target);
1769
+ // → { impulse_Ns, knockback_v, staggered, prone }
1770
+
1771
+ applyKnockback(target, result, { dx, dy });
1772
+ // → mutates velocity_mps, condition.prone, action.staggerTicks
1773
+ ```
1774
+
1775
+ **Physics**: `impulse = sqrt(2 × E × m_eff)` [N·s]; `Δv = impulse / m_target` [m/s].
1776
+ Stability coefficient reduces effective knockback before threshold checks:
1777
+ `effective_v = Δv × (1 − stabilityQ)`.
1778
+
1779
+ | Threshold | Value | Effect |
1780
+ |-----------|-------|--------|
1781
+ | `STAGGER_THRESHOLD_mps` | 0.5 m/s | 3-tick stagger window |
1782
+ | `PRONE_THRESHOLD_mps` | 2.0 m/s | `condition.prone = true` |
1783
+
1784
+ The kernel integrates knockback automatically for every melee and ranged hit — no
1785
+ changes to call sites required.
1786
+
1787
+ ---
1788
+
1789
+ ## Hydrostatic Shock & Cavitation (Phase 27)
1790
+
1791
+ `src/sim/hydrostatic.ts` models the wound-amplification physics of high-velocity
1792
+ projectiles. Above 600 m/s a temporary stretch wave radiates outward through
1793
+ inelastic tissue; above 900 m/s momentary vacuum cavitation further boosts
1794
+ haemorrhage in fluid-saturated organs.
1795
+
1796
+ ```typescript
1797
+ import {
1798
+ computeTemporaryCavityMul, computeCavitationBleed,
1799
+ HYDROSTATIC_THRESHOLD_mps, CAVITATION_THRESHOLD_mps,
1800
+ } from "./src/sim/hydrostatic.js";
1801
+
1802
+ // Multiplier applied to internalDamage (q(1.0) = no effect, q(3.0) = max)
1803
+ const cavMul = computeTemporaryCavityMul(v_impact_mps, "liver"); // → q(3.0) at 960 m/s
1804
+
1805
+ // Cavitation bleed boost for fluid-saturated tissue (torso, liver, lung, spleen, legs)
1806
+ const newBleed = computeCavitationBleed(v_impact_mps, currentBleed, "torso");
1807
+ ```
1808
+
1809
+ **Tissue compliance** governs how much the stretch wave amplifies damage:
1810
+
1811
+ | Region | Compliance | Behaviour |
1812
+ |--------|-----------|-----------|
1813
+ | bone / skull | q(0.05) | Brittle; maximum cavity damage |
1814
+ | brain / liver / spleen | q(0.10) | Very inelastic; high amplification |
1815
+ | lung | q(0.30) | Partially air-filled; intermediate |
1816
+ | torso | q(0.40) | Mixed; moderate amplification |
1817
+ | muscle / limbs | q(0.60) | Elastic; minimum amplification |
1818
+
1819
+ The kernel computes `v_impact_mps` from pre-armour projectile energy and mass in
1820
+ `resolveShoot`, passes it through `ImpactEvent`, and applies both functions
1821
+ automatically in the finalImpacts loop — no changes to call sites required.
1822
+
1823
+ ---
1824
+
1825
+ ## Cone AoE: Breath Weapons, Fire, Gas (Phase 28)
1826
+
1827
+ `src/sim/cone.ts` adds directional cone geometry to the capability system, enabling
1828
+ breath weapons, flamethrowers, gas dispensers, and sonic disorientation blasts.
1829
+
1830
+ ```typescript
1831
+ import { entityInCone, buildEntityFacingCone } from "./src/sim/cone.js";
1832
+ import type { CapabilityEffect, CapabilitySource } from "./src/sim/capability.js";
1833
+ import { q, SCALE } from "./src/units.js";
1834
+ import { DamageChannel } from "./src/channels.js";
1835
+
1836
+ // Dragon fire breath — 20 ticks sustained, 30° half-angle cone, 10m range
1837
+ const DRAGON_FIRE: CapabilityEffect = {
1838
+ id: "fire_breath",
1839
+ cost_J: 800, // deducted each sustained tick
1840
+ castTime_ticks: 5,
1841
+ sustainedTicks: 20, // fires 20 consecutive ticks
1842
+ coneHalfAngle_rad: Math.PI / 6, // 30° half-angle = 60° total cone
1843
+ coneDir: "facing", // follows entity's facingDirQ
1844
+ range_m: 10 * SCALE.m,
1845
+ payload: {
1846
+ kind: "weaponImpact",
1847
+ energy_J: 800,
1848
+ profile: {
1849
+ surfaceFrac: q(0.60), // fire burns surface heavily
1850
+ internalFrac: q(0.30), // convective heat reaches internal tissue
1851
+ structuralFrac: q(0.10),
1852
+ bleedFactor: q(0.05),
1853
+ penetrationBias: q(0.05),
1854
+ },
1855
+ },
1856
+ };
1857
+ // Total reserve cost for one breath: 800J × 20 ticks = 16 000 J
1858
+ ```
1859
+
1860
+ **Cone direction modes**:
1861
+ - `coneDir: "facing"` — cone follows the actor's `facingDirQ` (updated by movement commands)
1862
+ - `coneDir: "fixed"` with `coneDirFixed: { dx, dy }` — fixed world-space direction (gas cloud, mounted turret)
1863
+
1864
+ **Sustained emission** respects the same concentration-break rules as `castTime_ticks = -1`
1865
+ auras: shock ≥ q(0.30) cancels emission immediately. Each tick deducts `cost_J`; if the
1866
+ source reserve falls below `cost_J` the emission stops early.
1867
+
1868
+ **`weaponImpact` payload** allows any damage profile without being constrained to a
1869
+ `DamageChannel`. Unlike `impact` (which maps to a synthetic weapon via `DamageChannel`),
1870
+ `weaponImpact` takes a `WeaponDamageProfile` directly — enabling fire's
1871
+ surface-heavy/internal-pass-through split or acid's structural bias.
1872
+
1873
+ ---
1874
+
1875
+ ## Environmental Stress: Thermoregulation (Phase 29)
1876
+
1877
+ `src/sim/thermoregulation.ts` models core body temperature as a continuous heat-balance
1878
+ system. Unlike abstract "cold resistance" stats, temperature is tracked in real °C (encoded
1879
+ as Q) and driven by genuine thermophysics.
1880
+
1881
+ ```typescript
1882
+ import { stepCoreTemp, deriveTempModifiers, cToQ, CORE_TEMP_NORMAL_Q } from "./src/sim/thermoregulation.js";
1883
+ import type { KernelContext } from "./src/sim/context.js";
1884
+
1885
+ // Ambient temperature −10°C arctic environment
1886
+ const ctx: KernelContext = {
1887
+ thermalAmbient_Q: cToQ(-10), // cToQ converts Celsius to engine Q encoding
1888
+ tractionCoeff: q(0.9),
1889
+ };
1890
+
1891
+ // stepWorld automatically calls stepCoreTemp for every living entity each tick.
1892
+ // After simulation, query an entity's thermal state:
1893
+ const mods = deriveTempModifiers((entity.condition as any).coreTemp_Q ?? CORE_TEMP_NORMAL_Q);
1894
+ // → { powerMul: q(0.80), fineControlPen: q(0.15), latencyMul: q(1.2), dead: false }
1895
+ // → moderate hypothermia: −20% power, +20% reaction time
1896
+ ```
1897
+
1898
+ **Temperature stage thresholds** (Q encoding; `q(0.5)` = 37 °C; full range 10–64 °C):
1899
+
1900
+ | Stage | Core temp | `powerMul` | `fineControlPen` | `latencyMul` |
1901
+ |-------|-----------|-----------|-----------------|-------------|
1902
+ | Critical hyperthermia | > 40.1 °C | q(0.60) | q(0.30) | q(3.0) — dead |
1903
+ | Heat stroke | 39.4–40.1 °C | q(0.60) | q(0.20) | q(2.0) |
1904
+ | Heat exhaustion | 38.6–39.4 °C | q(0.85) | q(0.10) | q(1.0) |
1905
+ | Mild hyperthermia | 37.8–38.6 °C | q(0.95) | q(0) | q(1.0) |
1906
+ | Normal | 37.0–37.8 °C | q(1.0) | q(0) | q(1.0) |
1907
+ | Mild hypothermia | 36.2–37.0 °C | q(0.95) | q(0.05) | q(1.0) |
1908
+ | Moderate hypothermia | 34.6–36.2 °C | q(0.80) | q(0.15) | q(1.2) |
1909
+ | Severe hypothermia | 33.0–34.6 °C | q(0.50) | q(0.20) | q(3.0) |
1910
+ | Critical hypothermia | < 33.0 °C | q(0.50) | q(0.30) | q(4.0) — dead |
1911
+
1912
+ **Armour insulation** is modelled via `insulation_m2KW?: number` on `Armour` items.
1913
+ Higher insulation slows both heating and cooling. Typical values: plate armour 0.02, wool 0.15, fur 0.25.
1914
+
1915
+ **Downtime integration**: `DowntimeConfig.thermalAmbient_Q` enables temperature tracking
1916
+ through multi-hour recovery simulations. `EntityRecoveryReport.finalCoreTemp_Q` reports the
1917
+ final state.
1918
+
1919
+ ---
1920
+
1921
+ ## Nutrition & Starvation (Phase 30)
1922
+
1923
+ `src/sim/nutrition.ts` adds the longest survivability axis: caloric balance from hours to
1924
+ weeks. Hunger states impose escalating combat penalties and eventually cause mass loss —
1925
+ fat catabolism first, then muscle tissue.
1926
+
1927
+ ```typescript
1928
+ import {
1929
+ computeBMR, stepNutrition, consumeFood, deriveHungerModifiers,
1930
+ FOOD_ITEMS, type HungerState,
1931
+ } from "./src/sim/nutrition.js";
1932
+
1933
+ // BMR for a 75 kg entity: 80 W (Kleiber's law)
1934
+ const bmr = computeBMR(entity.attributes.morphology.mass_kg); // → 80
1935
+
1936
+ // stepWorld calls stepNutrition automatically at 1 Hz for every living entity.
1937
+ // Feed an entity from their food inventory:
1938
+ (entity as any).foodInventory = new Map([["ration_bar", 3], ["water_flask", 2]]);
1939
+ const ate = consumeFood(entity, "ration_bar", world.tick); // → true; caloricBalance += 2 000 000 J
1940
+
1941
+ // Query current state:
1942
+ const hunger: HungerState = (entity.condition as any).hungerState ?? "sated";
1943
+ const mods = deriveHungerModifiers(hunger);
1944
+ // → { staminaMul: q(1.0), forceMul: q(1.0), latencyMul: q(1.0), moraleDecay: q(0) } // sated
1945
+ // → { staminaMul: q(0.50), forceMul: q(0.80), latencyMul: q(1.50), moraleDecay: q(0.030) } // critical
1946
+ ```
1947
+
1948
+ **Hunger state thresholds** (deficit relative to BMR):
1949
+
1950
+ | State | Onset | `staminaMul` | `forceMul` | `latencyMul` | `moraleDecay` |
1951
+ |-------|-------|-------------|-----------|-------------|--------------|
1952
+ | `sated` | deficit < 12 h × BMR | q(1.0) | q(1.0) | q(1.0) | q(0) |
1953
+ | `hungry` | 12–24 h × BMR | q(0.90) | q(1.0) | q(1.0) | q(0) |
1954
+ | `starving` | 24–72 h × BMR | q(0.75) | q(0.90) | q(1.0) | q(0.030)/tick |
1955
+ | `critical` | ≥ 72 h × BMR | q(0.50) | q(0.80) | q(1.50) | q(0.030)/tick |
1956
+
1957
+ **Food catalogue** (`FOOD_ITEMS`):
1958
+
1959
+ | Item | Energy | Mass | Hydration |
1960
+ |------|--------|------|-----------|
1961
+ | `ration_bar` | 2 000 000 J | 500 g | — |
1962
+ | `dried_meat` | 1 500 000 J | 300 g | — |
1963
+ | `hardtack` | 800 000 J | 200 g | — |
1964
+ | `fresh_bread` | 700 000 J | 250 g | — |
1965
+ | `berry_handful` | 150 000 J | 50 g | — |
1966
+ | `water_flask` | 0 J | 500 g | 500 000 hydJ |
1967
+
1968
+ **Mass loss** (accumulated as float; applies only during starvation/critical):
1969
+ - Fat catabolism: 300 g/day (`mass_kg -= 300/86400 × delta_s`)
1970
+ - Muscle catabolism: 0.5 N/hour (`peakForce_N -= 0.5 × SCALE.N / 3600 × delta_s`, critical only)
1971
+
1972
+ **Entity food inventory**: attach `(entity as any).foodInventory = new Map<string, number>()`
1973
+ before calling `consumeFood`. Absent inventory = unlimited supply.
1974
+
1975
+ ---
1976
+
1977
+ ## Project layout
1978
+
1979
+ ```
1980
+ src/
1981
+ units.ts Fixed-point SI unit system and arithmetic helpers
1982
+ types.ts Core attribute interfaces (Morphology, Performance, Control, Resilience, Perception)
1983
+ channels.ts DamageChannel enum and bitmask helpers
1984
+ traits.ts Entity trait definitions and attribute multiplier application
1985
+ archetypes.ts Reference archetype baselines (HUMAN_BASE, SERVICE_ROBOT, AMATEUR_BOXER, PRO_BOXER, GRECO_WRESTLER, KNIGHT_INFANTRY, LARGE_PACIFIC_OCTOPUS)
1986
+ equipment.ts Weapon, Armour, Shield, RangedWeapon, Loadout types and starter item catalogue (includes wpn_boxing_gloves)
1987
+ generate.ts Procedural individual generation from archetype with variance distributions; NarrativeBias parameter (Phase 62)
1988
+ polity.ts Phase 61: Polity & World-State System — createPolity/Registry, trade, war, diplomacy, tech advancement, population-scale disease spread; integrates with Faction/Economy/Tech/Disease/Campaign
1989
+ presets.ts Entity factory functions for named real-world archetypes (mkBoxer, mkWrestler, mkKnight, mkOctopus, mkScubaDiver)
1990
+ derive.ts Movement caps and energy/fatigue derived from attributes and loadout
1991
+ replay.ts ReplayRecorder, replayTo, serializeReplay/deserializeReplay — deterministic replay
1992
+ metrics.ts CollectingTrace, collectMetrics, survivalRate, meanTimeToIncapacitation — analytics
1993
+ debug.ts extractMotionVectors, extractHitTraces, extractConditionSamples — visual debug layer
1994
+ model3d.ts deriveMassDistribution, deriveInertiaTensor, deriveAnimationHints, derivePoseModifiers, deriveGrappleConstraint, extractRigSnapshots — 3D rig integration
1995
+ describe.ts describeCharacter, formatCharacterSheet, formatOneLine — SI→human-readable translation layer (no sim dependencies)
1996
+ weapons.ts Historical weapons database — ~70 weapons across 6 eras (Prehistoric → Contemporary); shieldBypassQ for flexible weapons; magCapacity + shotInterval_s for magazine firearms
1997
+ narrative.ts narrateEvent, buildCombatLog, describeInjuries, describeCombatOutcome — combat narrative layer (no sim dependencies)
1998
+ downtime.ts stepDowntime(), MEDICAL_RESOURCES — 1 Hz wound recovery bridge (hours-to-weeks scale)
1999
+ arena.ts runArena(), expectWinRate/SurvivalRate/MeanDuration/Recovery/ResourceCost, formatArenaReport, 6 calibration scenarios
2000
+ progression.ts createProgressionState(), awardXP(), advanceSkill(), applyTrainingSession(), stepAgeing(), applyAgeingDelta(), deriveSequelae()
2001
+ campaign.ts createCampaign(), addLocation(), travel(), mergeEntityState(), stepCampaignTime(), debitInventory/creditInventory/getInventoryCount(), serialiseCampaign/deserialiseCampaign()
2002
+ dialogue.ts resolveDialogue(), applyDialogueOutcome(), narrateDialogue(), dialogueProbability() — social encounter resolution (intimidate/persuade/deceive/surrender/negotiate)
2003
+ faction.ts createFactionState(), adjustStanding(), extractWitnessEvents(), applyReputationDelta() — faction tracking and reputation system
2004
+ economy.ts computeItemValue(), applyWear(), resolveDrops(), evaluateTradeOffer() — item valuation, wear, loot drops, trade evaluation
2005
+ narrative-stress.ts runNarrativeStressTest(), formatStressTestReport(), beatEntityDefeated/Survives/TeamDefeated/ShockExceeds/Fatigued() — narrative push analyser (Phase 63)
2006
+
2007
+ sim/
2008
+ kernel.ts stepWorld(), applyFallDamage(), applyExplosion() — main simulation entry points
2009
+ entity.ts Entity type (all mutable simulation state)
2010
+ world.ts WorldState type
2011
+ kinds.ts CommandKind, TraceKind, MoveMode, DefenceMode enums (includes MoraleRally)
2012
+ body.ts BodyRegion type, region weights, hit-to-region mapping
2013
+ injury.ts InjuryState, per-region damage, bleeding rate helpers
2014
+ medical.ts MedicalTier, MedicalAction, tier rank/multiplier tables
2015
+ condition.ts ConditionState (fire, radiation, suffocation, stun, prone, etc.)
2016
+ impairment.ts deriveFunctionalState() — damage to mobility and manipulation penalties
2017
+ combat.ts resolveHit(), parryLeverageQ(), shield helpers
2018
+ grapple.ts Grapple resolution: score, positions, throw/choke/joint-lock
2019
+ weapon_dynamics.ts Reach dominance, two-handed bonus, miss recovery, weapon bind/break
2020
+ ranged.ts Ranged physics: energy at range, dispersion, grouping radius, costs
2021
+ explosion.ts BlastSpec, blastEnergyFracQ, fragmentsExpected, fragmentKineticEnergy
2022
+ substance.ts Substance, ActiveSubstance, STARTER_SUBSTANCES — pharmacokinetics model
2023
+ tech.ts TechEra, TechCapability, TechContext, defaultTechContext, isCapabilityAvailable
2024
+ capability.ts CapabilitySource, CapabilityEffect, RegenModel, EffectPayload, FieldEffect — Clarke's Third Law abstraction
2025
+ knockback.ts computeKnockback(), applyKnockback() — impulse-momentum transfer; stagger/prone checks
2026
+ hydrostatic.ts computeTemporaryCavityMul(), computeCavitationBleed() — high-velocity wound physics
2027
+ cone.ts entityInCone(), buildEntityFacingCone(), ConeSpec — directional cone AoE geometry (breath weapons, flamethrowers, gas)
2028
+ thermoregulation.ts stepCoreTemp(), deriveTempModifiers(), cToQ() — 9-stage core-temp model (mild hyperthermia → cardiac-arrest hypothermia)
2029
+ nutrition.ts computeBMR(), stepNutrition(), consumeFood(), deriveHungerModifiers(), FOOD_ITEMS — caloric balance, hunger states, fat/muscle catabolism
2030
+ events.ts ImpactEvent type, deterministic sort
2031
+ seeds.ts Deterministic per-event seed derivation
2032
+ formation.ts pickNearestEnemyInReach()
2033
+ frontage.ts applyFrontageCap() — limits engagers per target
2034
+ occlusion.ts isMeleeLaneOccludedByFriendly()
2035
+ density.ts computeDensityField() — crowd slowdown
2036
+ push.ts stepPushAndRepulsion() — entity separation
2037
+ spatial.ts Grid spatial index and neighbour queries
2038
+ indexing.ts buildWorldIndex() — O(1) id-to-entity lookup
2039
+ vec3.ts Fixed-point 3D vector maths
2040
+ team.ts isEnemy() helper
2041
+ intent.ts IntentState defaults
2042
+ action.ts ActionState defaults
2043
+ trace.ts TraceSink interface and nullTrace
2044
+ tuning.ts SimulationTuning presets (arcade, tactical, sim)
2045
+ testing.ts mkHumanoidEntity(), mkWorld() test helpers
2046
+
2047
+ bodyplan.ts BodyPlan, BodySegment, 8 body plan constants, resolveHitSegment, getExposureWeight
2048
+ sensory.ts canDetect(), SensoryEnvironment — vision arc + hearing + env modifiers
2049
+ skills.ts SkillId, SkillLevel, SkillMap, buildSkillMap, getSkill, combineSkillLevels
2050
+
2051
+ ai/
2052
+ types.ts AIPolicy interface
2053
+ presets.ts AI_PRESETS (lineInfantry, skirmisher)
2054
+ perception.ts perceiveLocal() — sensory-filtered enemy and ally detection
2055
+ targeting.ts pickTarget(), updateFocus() — horizon-limited target selection
2056
+ decide.ts decideCommandsForEntity() — with decision latency cooldown
2057
+ system.ts buildAICommands() — full AI pass over world
2058
+
2059
+ formation-unit.ts computeShieldWallCoverage, deriveRankSplit, stepFormationCasualtyFill, computeFormationMomentum, deriveFormationCohesion, deriveFormationAllyFearDecay — Phase 6 formation system
2060
+
2061
+ test/ Vitest test suite (one file per feature area)
2062
+ tools/ Developer utilities and runnable demos
2063
+ blade-runner.ts Artificial Life Validation — 365-day city-scale emergent-behaviour test
2064
+ emergent-validation.ts Emergent Behaviour Validation Suite — 4 historical combat scenarios × 100 seeds
2065
+ benchmark.ts Performance & Scalability Benchmarks — 10/100/500/1 000 entity throughput
2066
+ what-if.ts "What If?" Alternate History Engine — polity divergence × 100 seeds (Phase 64)
2067
+ generate-zoo.ts Simulation Zoo generator — 5 pre-computed scenarios embedded in docs/zoo/index.html
2068
+ generate-map.ts Generative Cartography — 180-day polity simulation → docs/map/index.html SVG viewer
2069
+ world-server.ts Persistent World Server — HTTP server with live polling client at docs/world-client/
2070
+ docs/
2071
+ onboarding.md New-engineer two-week onboarding guide
2072
+ contributing.md Contribution guide: conventions, PR checklist, module skeleton
2073
+ versioning.md Versioning contract: commit-hash pinning, breaking-change tiers, upgrade cadence
2074
+ ecosystem.md Ecosystem index: body-plan templates, renderer bridge boilerplate, companion repo suggestions
2075
+ integration-primer.md Deep technical onboarding (architecture, data-flow, gotchas)
2076
+ bridge-api.md Renderer bridge API reference
2077
+ use-case-validation.md Integration Milestone 1 — use-case fit validation
2078
+ narrative-stress-test.md Narrative Stress Test guide: API, Deus Ex score, cinematic benchmark table
2079
+ editors/
2080
+ index.html Editor hub — links to all visual design tools
2081
+ species-forge.html Species Forge — body plan + archetype + narrative bias editor
2082
+ culture-forge.html Culture Forge — cultural values, taboos, myth predispositions, diplomacy modifiers editor
2083
+ zoo/
2084
+ index.html Simulation Zoo — 5 pre-computed combat/disease scenarios with health visualiser
2085
+ map/
2086
+ index.html Generative Cartography — 180-day polity map with timeline slider
2087
+ world-client/
2088
+ index.html Persistent World Client — live-polling browser UI for world-server.ts
2089
+ companion-projects/
2090
+ ananke-godot-reference/README.md Godot 4 physics-driven character plugin starter
2091
+ ananke-unity-reference/README.md Unity 6 physics-driven character plugin starter
2092
+ ananke-threejs-bridge/README.md In-browser Three.js renderer bridge starter
2093
+ ananke-language-forge/README.md LLM-backed dynamic dialogue generation starter
2094
+ ananke-world-ui/README.md SvelteKit world management UI starter
2095
+ ananke-fantasy-species/README.md Fantasy / sci-fi species pack starter
2096
+ ananke-historical-battles/README.md Historical battle validation suite starter
2097
+ ananke-archive/README.md Searchable public simulation database starter
2098
+ ```
2099
+
2100
+ ---
2101
+
2102
+ ## Companion ecosystem
2103
+
2104
+ Seven companion projects are designed to build on top of Ananke. Each has a starter README
2105
+ in `docs/companion-projects/` describing the architecture, key Ananke entry points, and a
2106
+ suggested first milestone.
2107
+
2108
+ | Repository | Purpose |
2109
+ |---|---|
2110
+ | `ananke-godot-reference` | Godot 4 plugin driving a humanoid rig from `extractRigSnapshots` over WebSocket |
2111
+ | `ananke-unity-reference` | Unity 6 plugin using the HTTP polling sidecar; HumanBodyBones mapping |
2112
+ | `ananke-threejs-bridge` | In-browser Three.js renderer — no sidecar, WebAssembly kernel long-term target |
2113
+ | `ananke-language-forge` | LLM-backed dynamic dialogue generation reading `linguisticIntelligence_Q` and Phase 66 myth events |
2114
+ | `ananke-world-ui` | SvelteKit world management UI — supersedes `docs/editors/` when feature-complete |
2115
+ | `ananke-fantasy-species` | Fantasy / sci-fi species pack with allometric-scaled body plans and archetype templates |
2116
+ | `ananke-historical-battles` | Historical battle validation suite comparing outcomes against primary sources |
2117
+ | `ananke-archive` | Searchable public database of simulation runs, parameter spaces, and raw trace data |
2118
+
2119
+ ### Companion ecosystem infrastructure (ROADMAP CE-1 to CE-4)
2120
+
2121
+ The following Ananke-side changes are the highest-leverage items for enabling companion projects:
2122
+
2123
+ **CE-1 — npm publish + subpath exports map.** Remove `"private": true` from `package.json`,
2124
+ add a `"exports"` map so consumers can import `ananke/units`, `ananke/sim/kernel`, etc.
2125
+ without bundling the whole library. See ROADMAP for proposed exports JSON.
2126
+
2127
+ **CE-2 — `createWorld()` convenience factory.** A single call that builds a `World` with
2128
+ sensible defaults (5 humans, no terrain, tactical tuning). Eliminates 20 lines of setup
2129
+ boilerplate for companion projects that need a quick simulation harness.
2130
+
2131
+ **CE-3 — JSON scenario schema + `loadScenario()`.** Lets companion projects (especially
2132
+ `ananke-historical-battles` and `ananke-fantasy-species`) ship scenario definitions as JSON
2133
+ files rather than TypeScript, reducing the barrier to non-developer contribution.
2134
+
2135
+ **CE-4 — `src/index.ts` stable-API barrel.** A single import surface (`import { ... } from "ananke"`)
2136
+ covering everything in `STABLE_API.md`. Companion projects should depend on this barrel, not
2137
+ on internal module paths that may change.
2138
+
2139
+ ---
2140
+
2141
+ ## Performance baselines
2142
+
2143
+ Performance is a published contract, not just a tool that exists.
2144
+
2145
+ | Scenario | Entities | Median tick | Throughput | 20 Hz budget |
2146
+ |----------|----------|-------------|------------|--------------|
2147
+ | Melee skirmish | 10 | 0.19 ms | 5 300 ticks/s | 4% |
2148
+ | Mixed ranged/melee | 100 | 4.68 ms | 214 ticks/s | 9% |
2149
+ | Formation combat | 500 | 31 ms | 32 ticks/s | 62% |
2150
+ | Weather + disease | 1 000 | 64 ms | 16 ticks/s | 129% (exceeds real-time) |
2151
+
2152
+ Measured on Node 22, Apple M-series reference hardware. Full methodology, AI-budget
2153
+ breakdown, spatial-index comparison, memory footprint, and tuning guidance are in
2154
+ `docs/performance.md`. Run with `npm run run:benchmark`.
2155
+
2156
+ **Key constraint:** at 20 Hz real-time rate, 500 entities fits comfortably within budget;
2157
+ 1 000 entities requires a tick-rate reduction or spatial partitioning. `stepWorld` (kernel
2158
+ physics) consumes ≥ 95% of tick budget at all entity counts; AI is negligible (< 1%).
2159
+
2160
+ Benchmark regression is enforced in CI via `npm run benchmark-check` — throughput regressions
2161
+ are caught before release (ROADMAP item 15, complete).
2162
+
2163
+ ---
2164
+
2165
+ ## Validation
2166
+
2167
+ Ananke's validation strategy has two layers:
2168
+
2169
+ **Isolated subsystem validation** — 19+ subsystems (sprint speed, bleeding rate, sleep
2170
+ deprivation, thermoregulation, etc.) tested individually against empirical datasets with
2171
+ ±20 % tolerance on the simulated mean. Run with `npm run run:validation`.
2172
+
2173
+ **Emergent behaviour validation** — Four historical combat scenarios (English longbowmen at
2174
+ Agincourt, Roman testudo vs. Gaul charge, small-unit skirmish attrition, epidemic spread)
2175
+ each run across 100 seeds. The distribution of outcomes is compared against historical
2176
+ casualty data. Run with `npm run run:emergent-validation`.
2177
+
2178
+ Results from both suites are committed to `docs/` and linked from releases. See ROADMAP
2179
+ item PH-8 for the plan to make these first-class trust artifacts.
2180
+
2181
+ ---
2182
+
2183
+ ## Next steps — Platform Hardening
2184
+
2185
+ The simulation is architecturally complete. The highest-leverage remaining work is making
2186
+ the existing depth trustworthy and legible to adopters:
2187
+
2188
+ | Item | What | Status |
2189
+ |------|------|--------|
2190
+ | PH-1 | API tiering — Stable / Advanced / Internal tiers in exports and docs | Planned |
2191
+ | PH-2 | Versioning policy unification — one unambiguous adopter contract | Planned |
2192
+ | PH-3 | Minimal host integration contract document | Planned |
2193
+ | PH-4 | Save / replay / bridge contract tests — golden compatibility fixtures | Planned |
2194
+ | PH-5 | Bridge as first-class supported surface — `docs/bridge-contract.md` | Planned |
2195
+ | PH-6 | Entity / WorldState core vs. extensions split — JSDoc annotations | Planned |
2196
+ | PH-7 | Benchmark operational guide — tick-rate and entity-cap recommendations | Planned |
2197
+ | PH-8 | Emergent validation as flagship trust artifact — versioned, CI-enforced | Planned |
2198
+
2199
+ See ROADMAP `## Platform Hardening` for full scope of each item.