@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
@@ -0,0 +1,141 @@
1
+ // src/sim/terrain.ts — Phase 6: terrain friction and surface types
2
+ import { SCALE, q } from "../units.js";
3
+ /**
4
+ * Traction coefficient (Q) per surface type.
5
+ *
6
+ * Normal ground ≈ 0.80 (existing KernelContext default).
7
+ * Mud roughly halves usable force; ice allows only ~0.25×.
8
+ */
9
+ export const SURFACE_TRACTION = {
10
+ normal: q(0.80),
11
+ mud: q(0.40), // wet/muddy: ~50 % of normal traction
12
+ ice: q(0.20), // icy: ~25 % of normal traction
13
+ slope_up: q(0.65), // uphill: harder push-off, reduced effective force
14
+ slope_down: q(0.90), // downhill: slightly aided but less controlled
15
+ };
16
+ /**
17
+ * Terrain speed multiplier (Q) per surface type.
18
+ *
19
+ * Applied directly to maxSprintSpeed to capture effects that traction alone
20
+ * does not model (viscous drag in mud, slip uncertainty on ice, grade resistance).
21
+ *
22
+ * Normal = q(1.0) (no penalty).
23
+ */
24
+ export const SURFACE_SPEED_MUL = {
25
+ normal: q(1.00),
26
+ mud: q(0.60), // mud saps sprint speed: ~60 % of open-ground speed
27
+ ice: q(0.45), // ice: very hard to sprint without slipping (~45 %)
28
+ slope_up: q(0.75), // uphill grade: ~75 %
29
+ slope_down: q(1.10), // downhill assist: ~10 % bonus (capped in use)
30
+ };
31
+ /**
32
+ * Encode integer cell coordinates as a lookup key.
33
+ */
34
+ export function terrainKey(cellX, cellY) {
35
+ return `${cellX},${cellY}`;
36
+ }
37
+ /**
38
+ * Decode a terrain key back into cell coordinates.
39
+ */
40
+ export function parseTerrainKey(key) {
41
+ const [cx, cy] = key.split(",").map(Number);
42
+ return { cellX: cx, cellY: cy };
43
+ }
44
+ /**
45
+ * Look up the surface type at a given world position, or return undefined if
46
+ * the position has no terrain entry.
47
+ */
48
+ function surfaceAtPosition(grid, cellSize_m, pos_x, pos_y) {
49
+ if (!grid || grid.size === 0)
50
+ return undefined;
51
+ const cs = Math.max(1, cellSize_m);
52
+ const cx = Math.trunc(pos_x / cs);
53
+ const cy = Math.trunc(pos_y / cs);
54
+ return grid.get(terrainKey(cx, cy));
55
+ }
56
+ /**
57
+ * Look up the traction coefficient at a given world position.
58
+ *
59
+ * @param grid Terrain grid (may be undefined or empty)
60
+ * @param cellSize_m Cell size in SCALE.m units (e.g. 4*SCALE.m for 4 m cells)
61
+ * @param pos_x Entity x position in SCALE.m units
62
+ * @param pos_y Entity y position in SCALE.m units
63
+ * @param defaultTraction Fallback traction when no grid cell is found
64
+ * @returns Traction coefficient Q
65
+ */
66
+ export function tractionAtPosition(grid, cellSize_m, pos_x, pos_y, defaultTraction) {
67
+ const surf = surfaceAtPosition(grid, cellSize_m, pos_x, pos_y);
68
+ return surf !== undefined ? SURFACE_TRACTION[surf] : defaultTraction;
69
+ }
70
+ /**
71
+ * Look up the speed multiplier at a given world position.
72
+ *
73
+ * Returns q(1.0) when the position has no terrain entry (no penalty).
74
+ */
75
+ export function speedMulAtPosition(grid, cellSize_m, pos_x, pos_y) {
76
+ const surf = surfaceAtPosition(grid, cellSize_m, pos_x, pos_y);
77
+ return surf !== undefined ? SURFACE_SPEED_MUL[surf] : SCALE.Q;
78
+ }
79
+ /**
80
+ * Convenience: build a TerrainGrid from a flat record of "cx,cy" → SurfaceType.
81
+ */
82
+ export function buildTerrainGrid(cells) {
83
+ return new Map(Object.entries(cells));
84
+ }
85
+ /**
86
+ * Look up cover fraction at a world position.
87
+ * Returns 0 (no cover) if the grid is absent or the cell has no entry.
88
+ */
89
+ export function coverFractionAtPosition(grid, cellSize_m, pos_x, pos_y) {
90
+ if (!grid || grid.size === 0)
91
+ return 0;
92
+ const cs = Math.max(1, cellSize_m);
93
+ const cx = Math.trunc(pos_x / cs);
94
+ const cy = Math.trunc(pos_y / cs);
95
+ return (grid.get(terrainKey(cx, cy)) ?? 0);
96
+ }
97
+ /**
98
+ * Convenience: build an ObstacleGrid from a record of "cx,cy" → cover fraction Q.
99
+ * Use q(1.0) for impassable walls; fractional values for partial cover.
100
+ */
101
+ export function buildObstacleGrid(cells) {
102
+ return new Map(Object.entries(cells));
103
+ }
104
+ /**
105
+ * Look up elevation (height in SCALE.m units) at a world position.
106
+ * Returns 0 (ground level) if the grid is absent or the cell has no entry.
107
+ */
108
+ export function elevationAtPosition(grid, cellSize_m, pos_x, pos_y) {
109
+ if (!grid || grid.size === 0)
110
+ return 0;
111
+ const cs = Math.max(1, cellSize_m);
112
+ const cx = Math.trunc(pos_x / cs);
113
+ const cy = Math.trunc(pos_y / cs);
114
+ return grid.get(terrainKey(cx, cy)) ?? 0;
115
+ }
116
+ /**
117
+ * Convenience: build an ElevationGrid from a record of "cx,cy" → height (SCALE.m units).
118
+ */
119
+ export function buildElevationGrid(cells) {
120
+ return new Map(Object.entries(cells));
121
+ }
122
+ /**
123
+ * Look up slope info at a world position.
124
+ * Returns undefined if the grid is absent or the cell has no entry.
125
+ */
126
+ export function slopeAtPosition(grid, cellSize_m, pos_x, pos_y) {
127
+ if (!grid || grid.size === 0)
128
+ return undefined;
129
+ const cs = Math.max(1, cellSize_m);
130
+ const cx = Math.trunc(pos_x / cs);
131
+ const cy = Math.trunc(pos_y / cs);
132
+ return grid.get(terrainKey(cx, cy));
133
+ }
134
+ /** Convenience: build a SlopeGrid from a record of "cx,cy" → SlopeInfo. */
135
+ export function buildSlopeGrid(cells) {
136
+ return new Map(Object.entries(cells));
137
+ }
138
+ /** Convenience: build a HazardGrid from a record of "cx,cy" → HazardCell. */
139
+ export function buildHazardGrid(cells) {
140
+ return new Map(Object.entries(cells));
141
+ }
@@ -0,0 +1,13 @@
1
+ import { Weapon, type Loadout } from "../equipment.js";
2
+ import type { Entity } from "./entity.js";
3
+ import type { WorldState } from "./world.js";
4
+ import { ImpactEvent } from "./events.js";
5
+ /**
6
+ * Minimal humanoid entity for tests (deterministic attributes via generateIndividual()).
7
+ * Positions are fixed-point metres (SCALE.m).
8
+ */
9
+ export declare function mkHumanoidEntity(id: number, teamId: number, x_m: number, y_m: number, z_m?: number): Entity;
10
+ export declare function mkWorld(seed: number, entities: Entity[]): WorldState;
11
+ /** @deprecated Pass an explicit entity array instead: mkWorld(seed, [a, b]) */
12
+ export declare function mkWorld(seed: number, loadoutA: Loadout): WorldState;
13
+ export declare function mkImpactEvent(attackerId: number, targetId: number, region?: string, energy_J?: number, protectedByArmour?: boolean, blocked?: boolean, parried?: boolean, weaponId?: string, wpn?: Weapon, hitQuality?: number, shieldBlocked?: boolean): ImpactEvent;
@@ -0,0 +1,100 @@
1
+ import { generateIndividual } from "../generate.js";
2
+ import { HUMAN_BASE } from "../archetypes.js";
3
+ import { defaultIntent } from "./intent.js";
4
+ import { defaultAction } from "./action.js";
5
+ import { defaultCondition } from "./condition.js";
6
+ import { defaultInjury } from "./injury.js";
7
+ import { v3 } from "./vec3.js";
8
+ import { q, SCALE } from "../units.js";
9
+ /**
10
+ * Minimal humanoid entity for tests (deterministic attributes via generateIndividual()).
11
+ * Positions are fixed-point metres (SCALE.m).
12
+ */
13
+ export function mkHumanoidEntity(id, teamId, x_m, y_m, z_m = 0) {
14
+ const attrs = generateIndividual(id, HUMAN_BASE);
15
+ return {
16
+ id,
17
+ teamId,
18
+ attributes: attrs,
19
+ energy: { reserveEnergy_J: attrs.performance.reserveEnergy_J, fatigue: q(0) },
20
+ loadout: { items: [] },
21
+ traits: [],
22
+ position_m: v3(x_m, y_m, z_m),
23
+ velocity_mps: v3(0, 0, 0),
24
+ intent: defaultIntent(),
25
+ action: defaultAction(),
26
+ condition: defaultCondition(),
27
+ injury: defaultInjury(),
28
+ grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0), position: "standing" },
29
+ };
30
+ }
31
+ // implementation
32
+ export function mkWorld(seed, arg) {
33
+ // General form: mkWorld(seed, entities[])
34
+ if (Array.isArray(arg)) {
35
+ const entities = [...arg].sort((a, b) => a.id - b.id);
36
+ // Catch duplicate IDs early; they cause silent wrong results at runtime.
37
+ const ids = entities.map(e => e.id);
38
+ const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
39
+ if (dupes.length > 0) {
40
+ throw new Error(`mkWorld: duplicate entity IDs detected: ${[...new Set(dupes)].join(", ")}`);
41
+ }
42
+ return { tick: 0, seed, entities };
43
+ }
44
+ // Back-compat duel form: mkWorld(seed, loadoutA)
45
+ const loadoutA = (('items' in arg) ? arg : { items: [] });
46
+ const aAttrs = generateIndividual(1, HUMAN_BASE);
47
+ const bAttrs = generateIndividual(2, HUMAN_BASE);
48
+ return {
49
+ tick: 0,
50
+ seed,
51
+ entities: [
52
+ {
53
+ id: 1,
54
+ teamId: 1,
55
+ attributes: aAttrs,
56
+ energy: { reserveEnergy_J: aAttrs.performance.reserveEnergy_J, fatigue: q(0) },
57
+ loadout: loadoutA,
58
+ traits: [],
59
+ position_m: v3(0, 0, 0),
60
+ velocity_mps: v3(0, 0, 0),
61
+ intent: defaultIntent(),
62
+ action: defaultAction(),
63
+ condition: defaultCondition(),
64
+ injury: defaultInjury(),
65
+ grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0), position: "standing" },
66
+ },
67
+ {
68
+ id: 2,
69
+ teamId: 2,
70
+ attributes: bAttrs,
71
+ energy: { reserveEnergy_J: bAttrs.performance.reserveEnergy_J, fatigue: q(0) },
72
+ loadout: { items: [] },
73
+ traits: [],
74
+ position_m: v3(Math.trunc(2.0 * SCALE.m), 0, 0),
75
+ velocity_mps: v3(0, 0, 0),
76
+ intent: defaultIntent(),
77
+ action: defaultAction(),
78
+ condition: defaultCondition(),
79
+ injury: defaultInjury(),
80
+ grapple: { holdingTargetId: 0, heldByIds: [], gripQ: q(0), position: "standing" },
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ export function mkImpactEvent(attackerId, targetId, region = "", energy_J = 0, protectedByArmour = false, blocked = false, parried = false, weaponId = "", wpn = {}, hitQuality = q(0), shieldBlocked = false) {
86
+ return {
87
+ kind: "impact",
88
+ attackerId,
89
+ targetId,
90
+ region,
91
+ energy_J,
92
+ protectedByArmour,
93
+ blocked,
94
+ parried,
95
+ weaponId,
96
+ wpn,
97
+ hitQuality,
98
+ shieldBlocked,
99
+ };
100
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Phase 29 — Environmental Stress: Staged Hypothermia & Hyperthermia
3
+ *
4
+ * Core temperature model: linear Q encoding where q(0.5) = 37°C (normal body temp),
5
+ * 0 = 10°C, SCALE.Q = 64°C — so 1°C ≈ 185.2 Q units over the 54°C span.
6
+ *
7
+ * Heat balance (per second):
8
+ * ΔT_°C = (metabolicHeat − conductiveLoss) × delta_s / thermalMass
9
+ * metabolicHeat = peakPower_W × activityFrac × (1 − EFFICIENCY)
10
+ * conductiveLoss = (coreTemp_°C − ambientTemp_°C) / thermalResistance
11
+ * thermalResistance = 0.09 + sum(armour.insulation_m2KW) [°C/W]
12
+ * thermalMass = (mass_kg_real) × 3500 [J/°C]
13
+ */
14
+ import { Item } from "../equipment.js";
15
+ import { type Q } from "../units.js";
16
+ import type { Entity } from "./entity.js";
17
+ /** Celsius → Q-coded temperature (rounds to nearest integer). */
18
+ export declare function cToQ(celsius: number): Q;
19
+ /** Normal body temperature (37.0 °C). */
20
+ export declare const CORE_TEMP_NORMAL_Q: Q;
21
+ /** Mild hyperthermia entry (~37.8 °C). */
22
+ export declare const CORE_TEMP_HEAT_MILD: Q;
23
+ /** Heat exhaustion entry (~38.6 °C). */
24
+ export declare const CORE_TEMP_HEAT_EXHAUS: Q;
25
+ /** Heat stroke entry (~39.4 °C). */
26
+ export declare const CORE_TEMP_HEAT_STROKE: Q;
27
+ /** Mild hypothermia entry (~36.2 °C; below normal). */
28
+ export declare const CORE_TEMP_HYPOTHERMIA_MILD: Q;
29
+ /** Moderate hypothermia entry (~34.6 °C). */
30
+ export declare const CORE_TEMP_HYPOTHERMIA_MOD: Q;
31
+ /** Severe hypothermia entry (~33.0 °C). */
32
+ export declare const CORE_TEMP_HYPOTHERMIA_SEVERE: Q;
33
+ /** Helper: derive total armour insulation from loadout items. */
34
+ export declare function sumArmourInsulation(items: Item[]): number;
35
+ /**
36
+ * Compute the new core temperature Q value given explicit parameters (no entity mutation).
37
+ *
38
+ * Used by stepCoreTemp and by the downtime simulator (which does not hold an entity reference).
39
+ * Note: floating-point accumulation is intentional — sub-unit Q fractions accumulate correctly
40
+ * across successive calls since Q is stored as `number` (JS float).
41
+ */
42
+ export declare function computeNewCoreQ(coreQ: number, massReal_kg: number, // real kg (entity.attributes.morphology.mass_kg / SCALE.kg)
43
+ armourInsulation: number, isActive: boolean, // true if entity velocity ≥ 1 m/s
44
+ ambientTemp: Q, delta_s: number): Q;
45
+ /**
46
+ * Advance an entity's core temperature by `delta_s` seconds given the ambient temperature.
47
+ *
48
+ * Reads `entity.condition.coreTemp_Q` (defaults to CORE_TEMP_NORMAL_Q if absent).
49
+ * Writes the new value back to `entity.condition.coreTemp_Q` and returns it.
50
+ */
51
+ export declare function stepCoreTemp(entity: Entity, ambientTemp: Q, // Phase 29 Q-coded temperature (same scale as coreTemp_Q)
52
+ delta_s: number): Q;
53
+ export interface TempModifiers {
54
+ /** Effective multiplier on peakPower_W for action resolution. */
55
+ powerMul: Q;
56
+ /** Penalty subtracted from controlQuality. */
57
+ fineControlPen: Q;
58
+ /** Multiplier on decision latency. */
59
+ latencyMul: Q;
60
+ /** True when temperature is in death-trajectory range. */
61
+ dead: boolean;
62
+ }
63
+ /**
64
+ * Derive performance modifiers from current core temperature.
65
+ *
66
+ * Stages (high → low):
67
+ * > CRITICAL_HIGH : critical hyperthermia (dead=true)
68
+ * > HEAT_STROKE : heat stroke
69
+ * > HEAT_EXHAUS : heat exhaustion
70
+ * > HEAT_MILD : mild hyperthermia
71
+ * >= NORMAL : normal
72
+ * >= HYPO_MILD : mild hypothermia
73
+ * >= HYPO_MOD : moderate hypothermia
74
+ * >= HYPO_SEVERE : severe hypothermia
75
+ * < HYPO_SEVERE : critical hypothermia (dead=true)
76
+ */
77
+ export declare function deriveTempModifiers(coreTemp_Q: Q): TempModifiers;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Phase 29 — Environmental Stress: Staged Hypothermia & Hyperthermia
3
+ *
4
+ * Core temperature model: linear Q encoding where q(0.5) = 37°C (normal body temp),
5
+ * 0 = 10°C, SCALE.Q = 64°C — so 1°C ≈ 185.2 Q units over the 54°C span.
6
+ *
7
+ * Heat balance (per second):
8
+ * ΔT_°C = (metabolicHeat − conductiveLoss) × delta_s / thermalMass
9
+ * metabolicHeat = peakPower_W × activityFrac × (1 − EFFICIENCY)
10
+ * conductiveLoss = (coreTemp_°C − ambientTemp_°C) / thermalResistance
11
+ * thermalResistance = 0.09 + sum(armour.insulation_m2KW) [°C/W]
12
+ * thermalMass = (mass_kg_real) × 3500 [J/°C]
13
+ */
14
+ import { q, SCALE } from "../units.js";
15
+ // ── Temperature encoding helpers ──────────────────────────────────────────────
16
+ const TEMP_MIN_C = 10; // °C at Q = 0
17
+ const TEMP_RANGE_C = 54; // °C spanned by [0, SCALE.Q]
18
+ /** Q-coded temperature → Celsius (floating-point, for physics calculations only). */
19
+ function qToC(qVal) {
20
+ return TEMP_MIN_C + (qVal / SCALE.Q) * TEMP_RANGE_C;
21
+ }
22
+ /** Celsius → Q-coded temperature (rounds to nearest integer). */
23
+ export function cToQ(celsius) {
24
+ return Math.round(((celsius - TEMP_MIN_C) / TEMP_RANGE_C) * SCALE.Q);
25
+ }
26
+ // ── Exported threshold constants ──────────────────────────────────────────────
27
+ /** Normal body temperature (37.0 °C). */
28
+ export const CORE_TEMP_NORMAL_Q = q(0.500);
29
+ /** Mild hyperthermia entry (~37.8 °C). */
30
+ export const CORE_TEMP_HEAT_MILD = q(0.515);
31
+ /** Heat exhaustion entry (~38.6 °C). */
32
+ export const CORE_TEMP_HEAT_EXHAUS = q(0.529);
33
+ /** Heat stroke entry (~39.4 °C). */
34
+ export const CORE_TEMP_HEAT_STROKE = q(0.544);
35
+ /** Mild hypothermia entry (~36.2 °C; below normal). */
36
+ export const CORE_TEMP_HYPOTHERMIA_MILD = q(0.485);
37
+ /** Moderate hypothermia entry (~34.6 °C). */
38
+ export const CORE_TEMP_HYPOTHERMIA_MOD = q(0.456);
39
+ /** Severe hypothermia entry (~33.0 °C). */
40
+ export const CORE_TEMP_HYPOTHERMIA_SEVERE = q(0.426);
41
+ // Internal critical thresholds (not exported; outside the named stage range)
42
+ const CORE_TEMP_CRITICAL_HIGH = q(0.558); // ~40.1 °C — death trajectory
43
+ // Critical hypothermia is below HYPOTHERMIA_SEVERE (q(0.426) ≈ 33 °C)
44
+ /** Helper: derive total armour insulation from loadout items. */
45
+ export function sumArmourInsulation(items) {
46
+ let total = 0;
47
+ for (const item of items) {
48
+ if (item.kind === "armour" && item.insulation_m2KW != null)
49
+ total += item.insulation_m2KW;
50
+ }
51
+ return total;
52
+ }
53
+ // ── Metabolic heat model ──────────────────────────────────────────────────────
54
+ //
55
+ // Metabolic heat depends on body mass and activity level, NOT on combat peak power.
56
+ // Using W/kg specific metabolic heat production (tissue heat output directly):
57
+ // Resting (BMR): ~1.06 W/kg — standard basal metabolic rate for adults
58
+ // Active (brisk walk/march): ~5.5 W/kg — ≈ 5 MET
59
+ //
60
+ // These produce ≈ 80 W and ≈ 412 W for a 75 kg human, matching real physiology.
61
+ const REST_SPECIFIC_W = 1.06; // W / real kg — resting metabolic heat
62
+ const ACT_SPECIFIC_W = 5.50; // W / real kg — active/marching metabolic heat
63
+ /** Velocity threshold for "active" in SCALE.mps units (1.0 m/s). */
64
+ const ACTIVE_VEL_THRESH = Math.trunc(1.0 * SCALE.mps); // 10000
65
+ // ── Pure computation helper ───────────────────────────────────────────────────
66
+ /**
67
+ * Compute the new core temperature Q value given explicit parameters (no entity mutation).
68
+ *
69
+ * Used by stepCoreTemp and by the downtime simulator (which does not hold an entity reference).
70
+ * Note: floating-point accumulation is intentional — sub-unit Q fractions accumulate correctly
71
+ * across successive calls since Q is stored as `number` (JS float).
72
+ */
73
+ export function computeNewCoreQ(coreQ, massReal_kg, // real kg (entity.attributes.morphology.mass_kg / SCALE.kg)
74
+ armourInsulation, isActive, // true if entity velocity ≥ 1 m/s
75
+ ambientTemp, delta_s) {
76
+ if (massReal_kg <= 0)
77
+ return coreQ;
78
+ const coreC = qToC(coreQ);
79
+ const ambC = qToC(ambientTemp);
80
+ // Metabolic heat: mass-proportional (independent of combat peak power)
81
+ const specificW = isActive ? ACT_SPECIFIC_W : REST_SPECIFIC_W;
82
+ const metabolicHeat = massReal_kg * specificW; // W
83
+ const thermalResistance = 0.09 + armourInsulation; // °C/W
84
+ const thermalMass = massReal_kg * 3500; // J/°C
85
+ const conductiveLoss = (coreC - ambC) / thermalResistance; // W
86
+ const deltaTc = (metabolicHeat - conductiveLoss) * delta_s / thermalMass; // °C
87
+ const deltaTq = (deltaTc / TEMP_RANGE_C) * SCALE.Q; // fractional Q
88
+ return Math.max(0, Math.min(SCALE.Q, coreQ + deltaTq));
89
+ }
90
+ // ── Public API ────────────────────────────────────────────────────────────────
91
+ /**
92
+ * Advance an entity's core temperature by `delta_s` seconds given the ambient temperature.
93
+ *
94
+ * Reads `entity.condition.coreTemp_Q` (defaults to CORE_TEMP_NORMAL_Q if absent).
95
+ * Writes the new value back to `entity.condition.coreTemp_Q` and returns it.
96
+ */
97
+ export function stepCoreTemp(entity, ambientTemp, // Phase 29 Q-coded temperature (same scale as coreTemp_Q)
98
+ delta_s) {
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ const cond = entity.condition;
101
+ const coreQ = cond.coreTemp_Q ?? CORE_TEMP_NORMAL_Q;
102
+ const mReal = entity.attributes.morphology.mass_kg / SCALE.kg;
103
+ const insul = sumArmourInsulation(entity.loadout.items)
104
+ + (entity.physiology?.naturalInsulation_m2KW ?? 0);
105
+ const vx = entity.velocity_mps.x;
106
+ const vy = entity.velocity_mps.y;
107
+ const vMag = Math.sqrt(vx * vx + vy * vy);
108
+ const newCoreQ = computeNewCoreQ(coreQ, mReal, insul, vMag >= ACTIVE_VEL_THRESH, ambientTemp, delta_s);
109
+ cond.coreTemp_Q = newCoreQ;
110
+ return newCoreQ;
111
+ }
112
+ /**
113
+ * Derive performance modifiers from current core temperature.
114
+ *
115
+ * Stages (high → low):
116
+ * > CRITICAL_HIGH : critical hyperthermia (dead=true)
117
+ * > HEAT_STROKE : heat stroke
118
+ * > HEAT_EXHAUS : heat exhaustion
119
+ * > HEAT_MILD : mild hyperthermia
120
+ * >= NORMAL : normal
121
+ * >= HYPO_MILD : mild hypothermia
122
+ * >= HYPO_MOD : moderate hypothermia
123
+ * >= HYPO_SEVERE : severe hypothermia
124
+ * < HYPO_SEVERE : critical hypothermia (dead=true)
125
+ */
126
+ export function deriveTempModifiers(coreTemp_Q) {
127
+ if (coreTemp_Q >= CORE_TEMP_CRITICAL_HIGH) {
128
+ // Critical hyperthermia (> ~40°C) — death trajectory
129
+ return { powerMul: q(0.60), fineControlPen: q(0.30), latencyMul: q(3.0), dead: true };
130
+ }
131
+ if (coreTemp_Q >= CORE_TEMP_HEAT_STROKE) {
132
+ // Heat stroke (~39–40°C) — −40% power, decision latency ×2
133
+ return { powerMul: q(0.60), fineControlPen: q(0.20), latencyMul: q(2.0), dead: false };
134
+ }
135
+ if (coreTemp_Q >= CORE_TEMP_HEAT_EXHAUS) {
136
+ // Heat exhaustion (~38.6–39.4°C) — −15% power, fine control penalty
137
+ return { powerMul: q(0.85), fineControlPen: q(0.10), latencyMul: q(1.0), dead: false };
138
+ }
139
+ if (coreTemp_Q >= CORE_TEMP_HEAT_MILD) {
140
+ // Mild hyperthermia (~37.8–38.6°C) — −5% power
141
+ return { powerMul: q(0.95), fineControlPen: q(0), latencyMul: q(1.0), dead: false };
142
+ }
143
+ if (coreTemp_Q >= CORE_TEMP_NORMAL_Q) {
144
+ // Normal (37.0–37.8°C)
145
+ return { powerMul: q(1.0), fineControlPen: q(0), latencyMul: q(1.0), dead: false };
146
+ }
147
+ if (coreTemp_Q >= CORE_TEMP_HYPOTHERMIA_MILD) {
148
+ // Mild hypothermia (~36.2–37.0°C) — shivering, −5% power
149
+ return { powerMul: q(0.95), fineControlPen: q(0.05), latencyMul: q(1.0), dead: false };
150
+ }
151
+ if (coreTemp_Q >= CORE_TEMP_HYPOTHERMIA_MOD) {
152
+ // Moderate hypothermia (~34.6–36.2°C) — −20% power, reaction time +20%
153
+ return { powerMul: q(0.80), fineControlPen: q(0.15), latencyMul: q(1.2), dead: false };
154
+ }
155
+ if (coreTemp_Q >= CORE_TEMP_HYPOTHERMIA_SEVERE) {
156
+ // Severe hypothermia (~33.0–34.6°C) — −50% power, decision latency ×3
157
+ return { powerMul: q(0.50), fineControlPen: q(0.20), latencyMul: q(3.0), dead: false };
158
+ }
159
+ // Critical hypothermia (< ~33°C) — cardiac arrest, death trajectory
160
+ return { powerMul: q(0.50), fineControlPen: q(0.30), latencyMul: q(4.0), dead: true };
161
+ }
@@ -0,0 +1,3 @@
1
+ import { I32 } from "../units.js";
2
+ export declare const TICK_HZ = 20;
3
+ export declare const DT_S: I32;
@@ -0,0 +1,3 @@
1
+ import { to } from "../units.js";
2
+ export const TICK_HZ = 20; // Hz (good for tactical combat)
3
+ export const DT_S = to.s(1 / TICK_HZ); // seconds per tick
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Phase 32C — Venom & Chemical Injection
3
+ *
4
+ * Models wound-injection venom: onset delay, per-second internal damage and fear
5
+ * accumulation, duration, and antidote clearance.
6
+ *
7
+ * Follows the 1 Hz accumulator pattern from Phase 30 (nutrition). Called from the
8
+ * kernel's __nutritionAccum gate and from downtime.ts per-second loop.
9
+ */
10
+ import { type Q } from "../units.js";
11
+ import type { Entity } from "./entity.js";
12
+ export interface VenomProfile {
13
+ id: string;
14
+ name: string;
15
+ /** Seconds before symptoms begin (onset latency). */
16
+ onsetDelay_s: number;
17
+ /** Per-second internal damage as Q fraction of max internal health. */
18
+ damageRate_Q: Q;
19
+ /** Per-second fear increment while symptomatic. */
20
+ fearRate_Q: Q;
21
+ /** Total duration without antidote [seconds]. */
22
+ duration_s: number;
23
+ /** Antidote consumable id (matches a FOOD_ITEMS entry or equipment catalogue item). */
24
+ antidoteId?: string;
25
+ }
26
+ export interface ActiveVenom {
27
+ profile: VenomProfile;
28
+ /** Accumulated seconds since injection (includes pre-onset time). */
29
+ elapsedSeconds: number;
30
+ }
31
+ export declare const VENOM_PROFILES: readonly VenomProfile[];
32
+ /** Look up a VenomProfile by id. Returns undefined if unknown. */
33
+ export declare function getVenomProfile(id: string): VenomProfile | undefined;
34
+ /**
35
+ * Advance all active venoms on an entity by `delta_s` seconds.
36
+ *
37
+ * Mutates:
38
+ * entity.activeVenoms (elapsedSeconds incremented; expired entries removed)
39
+ * entity.injury (torso internalDamage incremented per symptomatic venom)
40
+ * entity.condition.fearQ (incremented per symptomatic venom)
41
+ */
42
+ export declare function stepToxicology(entity: Entity, delta_s: number): void;
43
+ /**
44
+ * Inject a venom into an entity by profile id.
45
+ * Returns false if the id is unknown.
46
+ */
47
+ export declare function injectVenom(entity: Entity, venomId: string): boolean;
48
+ /**
49
+ * Apply an antidote, clearing all active venoms that list the given item id.
50
+ * Returns true if at least one venom was cleared.
51
+ */
52
+ export declare function applyAntidote(entity: Entity, antidoteId: string): boolean;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Phase 32C — Venom & Chemical Injection
3
+ *
4
+ * Models wound-injection venom: onset delay, per-second internal damage and fear
5
+ * accumulation, duration, and antidote clearance.
6
+ *
7
+ * Follows the 1 Hz accumulator pattern from Phase 30 (nutrition). Called from the
8
+ * kernel's __nutritionAccum gate and from downtime.ts per-second loop.
9
+ */
10
+ import { q, SCALE, clampQ } from "../units.js";
11
+ // ── Venom catalogue ───────────────────────────────────────────────────────────
12
+ export const VENOM_PROFILES = [
13
+ {
14
+ id: "venom_insect",
15
+ name: "Insect venom",
16
+ onsetDelay_s: 30,
17
+ damageRate_Q: q(0.008), // ~0.08% max internal/s → lethal in ~200s without aid
18
+ fearRate_Q: q(0.005),
19
+ duration_s: 300,
20
+ },
21
+ {
22
+ id: "venom_snake",
23
+ name: "Serpent venom",
24
+ onsetDelay_s: 60,
25
+ damageRate_Q: q(0.012), // ~0.12% max internal/s → lethal in ~140s without aid
26
+ fearRate_Q: q(0.010),
27
+ duration_s: 600,
28
+ antidoteId: "antivenom",
29
+ },
30
+ {
31
+ id: "venom_paralytic",
32
+ name: "Paralytic toxin",
33
+ onsetDelay_s: 10,
34
+ damageRate_Q: q(0.003), // low damage, but high fear and rapid onset
35
+ fearRate_Q: q(0.020),
36
+ duration_s: 120,
37
+ antidoteId: "antivenom",
38
+ },
39
+ ];
40
+ const VENOM_BY_ID = new Map(VENOM_PROFILES.map(v => [v.id, v]));
41
+ /** Look up a VenomProfile by id. Returns undefined if unknown. */
42
+ export function getVenomProfile(id) {
43
+ return VENOM_BY_ID.get(id);
44
+ }
45
+ // ── Public API ────────────────────────────────────────────────────────────────
46
+ /**
47
+ * Advance all active venoms on an entity by `delta_s` seconds.
48
+ *
49
+ * Mutates:
50
+ * entity.activeVenoms (elapsedSeconds incremented; expired entries removed)
51
+ * entity.injury (torso internalDamage incremented per symptomatic venom)
52
+ * entity.condition.fearQ (incremented per symptomatic venom)
53
+ */
54
+ export function stepToxicology(entity, delta_s) {
55
+ const venoms = entity.activeVenoms;
56
+ if (!venoms || venoms.length === 0)
57
+ return;
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ const cond = entity.condition;
60
+ const torsoRegion = entity.injury.byRegion?.["torso"];
61
+ for (const av of venoms) {
62
+ av.elapsedSeconds += delta_s;
63
+ if (av.elapsedSeconds < av.profile.onsetDelay_s)
64
+ continue; // still pre-onset
65
+ // Internal damage to torso (if body plan has torso; otherwise apply to shock)
66
+ const dmgInc = Math.trunc(av.profile.damageRate_Q * delta_s);
67
+ if (torsoRegion !== undefined) {
68
+ torsoRegion.internalDamage = clampQ((torsoRegion.internalDamage + dmgInc), q(0), q(1.0));
69
+ }
70
+ else {
71
+ // Fallback: apply as shock when no torso region
72
+ entity.injury.shock = clampQ((entity.injury.shock + dmgInc), q(0), q(1.0));
73
+ }
74
+ // Fear increment
75
+ const fearInc = Math.trunc(av.profile.fearRate_Q * delta_s);
76
+ cond.fearQ = clampQ((cond.fearQ + fearInc), q(0), SCALE.Q);
77
+ }
78
+ // Remove expired entries
79
+ entity.activeVenoms = venoms.filter(av => av.elapsedSeconds < av.profile.duration_s);
80
+ }
81
+ /**
82
+ * Inject a venom into an entity by profile id.
83
+ * Returns false if the id is unknown.
84
+ */
85
+ export function injectVenom(entity, venomId) {
86
+ const profile = VENOM_BY_ID.get(venomId);
87
+ if (!profile)
88
+ return false;
89
+ if (!entity.activeVenoms)
90
+ entity.activeVenoms = [];
91
+ entity.activeVenoms.push({ profile, elapsedSeconds: 0 });
92
+ return true;
93
+ }
94
+ /**
95
+ * Apply an antidote, clearing all active venoms that list the given item id.
96
+ * Returns true if at least one venom was cleared.
97
+ */
98
+ export function applyAntidote(entity, antidoteId) {
99
+ if (!entity.activeVenoms || entity.activeVenoms.length === 0)
100
+ return false;
101
+ const before = entity.activeVenoms.length;
102
+ entity.activeVenoms = entity.activeVenoms.filter(av => av.profile.antidoteId !== antidoteId);
103
+ return entity.activeVenoms.length < before;
104
+ }