@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,77 @@
1
+ import { terrainKey } from "../terrain.js";
2
+ import { DT_S } from "../tick.js";
3
+ import { SCALE } from "../../units.js";
4
+ /**
5
+ * Per-entity per-tick regen of all capability sources.
6
+ * Called after stepMovement so velocity is current.
7
+ */
8
+ export function stepCapabilitySources(e, world, ctx) {
9
+ if (!e.capabilitySources)
10
+ return;
11
+ const cellSize_m = ctx.cellSize_m ?? Math.trunc(4 * SCALE.m);
12
+ for (const source of e.capabilitySources) {
13
+ const model = source.regenModel;
14
+ if (model.type === "boundless")
15
+ continue;
16
+ let regenRate_W = 0;
17
+ switch (model.type) {
18
+ case "constant":
19
+ regenRate_W = model.regenRate_W;
20
+ break;
21
+ case "rest": {
22
+ const speedAbs = Math.max(Math.abs(e.velocity_mps.x), Math.abs(e.velocity_mps.y));
23
+ const isResting = speedAbs <= Math.trunc(0.05 * SCALE.mps) && e.action.attackCooldownTicks === 0;
24
+ if (isResting)
25
+ regenRate_W = model.regenRate_W;
26
+ break;
27
+ }
28
+ case "ambient": {
29
+ const cx = Math.trunc(e.position_m.x / cellSize_m);
30
+ const cy = Math.trunc(e.position_m.y / cellSize_m);
31
+ const key = terrainKey(cx, cy);
32
+ const ambientVal = ctx.ambientGrid?.get(key) ?? 0;
33
+ if (ambientVal > 0) {
34
+ regenRate_W = Math.trunc(model.maxRate_W * ambientVal / SCALE.Q);
35
+ }
36
+ break;
37
+ }
38
+ case "event": {
39
+ for (const trigger of model.triggers) {
40
+ if (trigger.on === "tick") {
41
+ if (trigger._nextTick === undefined)
42
+ trigger._nextTick = world.tick + trigger.every_n;
43
+ if (world.tick >= trigger._nextTick) {
44
+ source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + trigger.amount_J);
45
+ trigger._nextTick = world.tick + trigger.every_n;
46
+ }
47
+ }
48
+ // kill triggers dispatched by kernel death-detection loop; terrain triggers below
49
+ }
50
+ break;
51
+ }
52
+ }
53
+ if (regenRate_W > 0) {
54
+ const regenThisTick = Math.trunc(regenRate_W * DT_S / SCALE.s);
55
+ source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + regenThisTick);
56
+ }
57
+ }
58
+ // Phase 12B: terrain-entry triggers — fire once per cell-boundary crossing
59
+ if (ctx.terrainTagGrid) {
60
+ const cx = Math.trunc(e.position_m.x / cellSize_m);
61
+ const cy = Math.trunc(e.position_m.y / cellSize_m);
62
+ const currentKey = terrainKey(cx, cy);
63
+ if (currentKey !== e.action.lastCellKey) {
64
+ const tags = ctx.terrainTagGrid.get(currentKey) ?? [];
65
+ for (const source of e.capabilitySources) {
66
+ if (source.regenModel.type !== "event")
67
+ continue;
68
+ for (const trig of source.regenModel.triggers) {
69
+ if (trig.on === "terrain" && tags.includes(trig.tag)) {
70
+ source.reserve_J = Math.min(source.maxReserve_J, source.reserve_J + trig.amount_J);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ e.action.lastCellKey = currentKey;
76
+ }
77
+ }
@@ -0,0 +1,9 @@
1
+ import type { WorldState } from "../world.js";
2
+ import type { TraceSink } from "../trace.js";
3
+ import type { Entity } from "../entity.js";
4
+ /**
5
+ * Phase 12B: advance a concentration aura for one tick.
6
+ * Deducts cost_J from the source and applies the effect payload.
7
+ * Clears activeConcentration if reserve is exhausted or entity is shocked.
8
+ */
9
+ export declare function stepConcentration(e: Entity, world: WorldState, trace: TraceSink, tick: number): void;
@@ -0,0 +1,25 @@
1
+ import { applyCapabilityEffect } from "../kernel.js";
2
+ import { TraceKinds } from "../kinds.js";
3
+ import { q } from "../../units.js";
4
+ /**
5
+ * Phase 12B: advance a concentration aura for one tick.
6
+ * Deducts cost_J from the source and applies the effect payload.
7
+ * Clears activeConcentration if reserve is exhausted or entity is shocked.
8
+ */
9
+ export function stepConcentration(e, world, trace, tick) {
10
+ const { sourceId, effectId, targetId } = e.activeConcentration;
11
+ const source = e.capabilitySources?.find(s => s.id === sourceId);
12
+ const effect = source?.effects.find(ef => ef.id === effectId);
13
+ const isBoundless = source?.regenModel.type === "boundless";
14
+ const interrupted = !source || !effect ||
15
+ (!isBoundless && source.reserve_J < effect.cost_J) ||
16
+ e.injury.shock >= q(0.30);
17
+ if (interrupted) {
18
+ delete e.activeConcentration;
19
+ trace.onEvent({ kind: TraceKinds.CastInterrupted, tick, entityId: e.id });
20
+ return;
21
+ }
22
+ if (!isBoundless)
23
+ source.reserve_J -= effect.cost_J;
24
+ applyCapabilityEffect(world, e, targetId, effect, trace, tick);
25
+ }
@@ -0,0 +1,17 @@
1
+ import type { Entity } from "../entity.js";
2
+ import type { WorldState } from "../world.js";
3
+ import type { TraceSink } from "../trace.js";
4
+ import { I32 } from "../../units.js";
5
+ import { HazardGrid } from "../terrain.js";
6
+ /**
7
+ * Phase 12B effect chains: apply chainPayload from each active FieldEffect to every
8
+ * living entity within its radius. Runs before expiry so the final tick still fires.
9
+ */
10
+ export declare function stepChainEffects(world: WorldState, trace: TraceSink, tick: number): void;
11
+ /**
12
+ * Decrement duration on timed field effects; remove expired ones.
13
+ * Permanent effects (duration_ticks === -1) are never removed.
14
+ */
15
+ export declare function stepFieldEffects(world: WorldState): void;
16
+ export declare function stepHazardEffects_legacy(entities: Entity[], grid: HazardGrid, cellSize_m: I32): void;
17
+ export declare function stepHazardEffects(entities: readonly Entity[], grid: HazardGrid, cellSize_m: I32): void;
@@ -0,0 +1,96 @@
1
+ import { terrainKey } from "../terrain.js";
2
+ import { applyHazardDamage } from "./hazards.js";
3
+ import { applyPayload } from "../kernel.js";
4
+ /**
5
+ * Phase 12B effect chains: apply chainPayload from each active FieldEffect to every
6
+ * living entity within its radius. Runs before expiry so the final tick still fires.
7
+ */
8
+ export function stepChainEffects(world, trace, tick) {
9
+ if (!world.activeFieldEffects?.length)
10
+ return;
11
+ for (const fe of world.activeFieldEffects) {
12
+ if (!fe.chainPayload)
13
+ continue;
14
+ const actor = world.entities.find(e => e.id === fe.placedByEntityId);
15
+ if (!actor)
16
+ continue;
17
+ const payloads = Array.isArray(fe.chainPayload)
18
+ ? fe.chainPayload
19
+ : [fe.chainPayload];
20
+ const radSq = fe.radius_m * fe.radius_m;
21
+ for (const target of world.entities) {
22
+ if (target.injury.dead)
23
+ continue;
24
+ const dx = target.position_m.x - fe.origin.x;
25
+ const dy = target.position_m.y - fe.origin.y;
26
+ if (dx * dx + dy * dy > radSq)
27
+ continue;
28
+ for (const p of payloads) {
29
+ applyPayload(world, actor, target, p, trace, tick, fe.id);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Decrement duration on timed field effects; remove expired ones.
36
+ * Permanent effects (duration_ticks === -1) are never removed.
37
+ */
38
+ export function stepFieldEffects(world) {
39
+ if (!world.activeFieldEffects?.length)
40
+ return;
41
+ world.activeFieldEffects = world.activeFieldEffects.filter(fe => {
42
+ if (fe.duration_ticks < 0)
43
+ return true; // permanent
44
+ fe.duration_ticks -= 1;
45
+ return fe.duration_ticks > 0;
46
+ });
47
+ }
48
+ export function stepHazardEffects_legacy(entities, grid, cellSize_m) {
49
+ const cs = Math.max(1, cellSize_m);
50
+ for (const e of entities) {
51
+ if (e.injury.dead)
52
+ continue;
53
+ const cx = Math.trunc(e.position_m.x / cs);
54
+ const cy = Math.trunc(e.position_m.y / cs);
55
+ const key = terrainKey(cx, cy);
56
+ const hazard = grid.get(key);
57
+ if (!hazard)
58
+ continue;
59
+ if (hazard.intensity > 0) {
60
+ applyHazardDamage(e, hazard);
61
+ }
62
+ if (hazard.duration_ticks > 0) {
63
+ hazard.duration_ticks -= 1;
64
+ if (hazard.duration_ticks === 0) {
65
+ grid.delete(key);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ function hazardKeyForEntity(e, cellSize_m) {
71
+ const cx = Math.floor(e.position_m.x / cellSize_m);
72
+ const cy = Math.floor(e.position_m.y / cellSize_m);
73
+ return terrainKey(cx, cy);
74
+ }
75
+ export function stepHazardEffects(entities, grid, cellSize_m) {
76
+ // 1) Apply hazards to entities (no duration ticking here)
77
+ for (const e of entities) {
78
+ const key = hazardKeyForEntity(e, cellSize_m); // <-- replace with your actual key computation
79
+ const hazard = grid.get(key);
80
+ if (!hazard)
81
+ continue;
82
+ applyHazardDamage(e, hazard); // <-- replace with your actual application call(s)
83
+ }
84
+ // 2) Tick/expire hazards once per cell per tick
85
+ // Collect keys first to avoid mutating the map while iterating it.
86
+ const keysToDelete = [];
87
+ for (const [key, hazard] of grid) {
88
+ if (hazard.duration_ticks > 0) {
89
+ hazard.duration_ticks -= 1;
90
+ if (hazard.duration_ticks === 0)
91
+ keysToDelete.push(key);
92
+ }
93
+ }
94
+ for (const key of keysToDelete)
95
+ grid.delete(key);
96
+ }
@@ -0,0 +1,3 @@
1
+ import type { KernelContext } from "../context.js";
2
+ import type { Entity } from "../entity.js";
3
+ export declare function stepEnergy(e: Entity, ctx: KernelContext): void;
@@ -0,0 +1,31 @@
1
+ import { DT_S } from "../tick.js";
2
+ import { q, qMul, clampQ, mulDiv, SCALE } from "../../units.js";
3
+ import { stepEnergyAndFatigue } from "../../derive.js";
4
+ import { getSkill } from "../skills.js";
5
+ import { findExoskeleton } from "../../equipment.js";
6
+ export function stepEnergy(e, ctx) {
7
+ const BASE_IDLE_W = 80;
8
+ const speedAbs = Math.max(Math.abs(e.velocity_mps.x), Math.abs(e.velocity_mps.y), Math.abs(e.velocity_mps.z));
9
+ const moving = speedAbs > Math.trunc(0.05 * SCALE.mps);
10
+ // Phase 11: powered exoskeleton adds continuous power draw to metabolic demand
11
+ const exoForEnergy = findExoskeleton(e.loadout);
12
+ // Phase 8B: flight increases stamina demand when entity is airborne
13
+ const flightSpecE = e.bodyPlan?.locomotion.flight;
14
+ const isFlying = flightSpecE !== undefined && e.attributes.morphology.mass_kg <= flightSpecE.liftCapacity_kg;
15
+ const flightDemandMul = (isFlying && moving) ? flightSpecE.flightStaminaCost : SCALE.Q;
16
+ const baseDemand = (moving ? 250 : BASE_IDLE_W) + (exoForEnergy ? exoForEnergy.powerDrain_W : 0);
17
+ const demand = mulDiv(baseDemand, flightDemandMul, SCALE.Q);
18
+ const fatigueBefore = e.energy.fatigue;
19
+ stepEnergyAndFatigue(e.attributes, e.energy, e.loadout, demand, DT_S, { tractionCoeff: ctx.tractionCoeff });
20
+ // Phase 7: athleticism.fatigueRateMul reduces fatigue accumulation each tick
21
+ const fatigueDelta = e.energy.fatigue - fatigueBefore;
22
+ if (fatigueDelta > 0) {
23
+ const athSkill = getSkill(e.skills, "athleticism");
24
+ if (athSkill.fatigueRateMul < SCALE.Q) {
25
+ e.energy.fatigue = clampQ((fatigueBefore + qMul(fatigueDelta, athSkill.fatigueRateMul)), 0, SCALE.Q);
26
+ }
27
+ }
28
+ if (!moving && e.injury.shock < q(0.4)) {
29
+ e.energy.fatigue = clampQ(e.energy.fatigue - q(0.0020), 0, SCALE.Q);
30
+ }
31
+ }
@@ -0,0 +1,4 @@
1
+ import type { Entity } from "../entity.js";
2
+ import type { HazardCell } from "../terrain.js";
3
+ /** Apply a single hazard cell's per-tick effect to an entity. */
4
+ export declare function applyHazardDamage(e: Entity, hazard: HazardCell): void;
@@ -0,0 +1,19 @@
1
+ import { SCALE, q, clampQ, qMul } from "../../units.js";
2
+ /** Apply a single hazard cell's per-tick effect to an entity. */
3
+ export function applyHazardDamage(e, hazard) {
4
+ const torso = e.injury.byRegion["torso"];
5
+ if (!torso)
6
+ return;
7
+ const intensity = hazard.intensity;
8
+ if (hazard.type === "fire") {
9
+ torso.surfaceDamage = clampQ(torso.surfaceDamage + qMul(intensity, q(0.003)), 0, SCALE.Q);
10
+ e.injury.shock = clampQ(e.injury.shock + qMul(intensity, q(0.005)), 0, SCALE.Q);
11
+ }
12
+ else if (hazard.type === "radiation") {
13
+ torso.internalDamage = clampQ(torso.internalDamage + qMul(intensity, q(0.004)), 0, SCALE.Q);
14
+ }
15
+ else if (hazard.type === "poison_gas") {
16
+ torso.internalDamage = clampQ(torso.internalDamage + qMul(intensity, q(0.002)), 0, SCALE.Q);
17
+ e.injury.consciousness = clampQ(e.injury.consciousness - qMul(intensity, q(0.003)), 0, SCALE.Q);
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ import type { Entity } from "../entity.js";
2
+ import type { WorldState } from "../world.js";
3
+ import { type Q } from "../../units.js";
4
+ export declare const SHOCK_FROM_FLUID: number;
5
+ export declare const SHOCK_FROM_INTERNAL: number;
6
+ export declare const CONSC_LOSS_FROM_SHOCK: number;
7
+ export declare const CONSC_LOSS_FROM_SUFF: number;
8
+ export declare const FATAL_FLUID_LOSS: Q;
9
+ export declare function stepConditionsToInjury(e: Entity, world: WorldState, ambientTemperature_Q?: Q): void;
10
+ export declare function stepInjuryProgression(e: Entity, tick: number): void;
@@ -0,0 +1,353 @@
1
+ import { q, clampQ, qMul, mulDiv, SCALE } from "../../units.js";
2
+ import { buildTraitProfile } from "../../traits.js";
3
+ import { deriveArmourProfile } from "../../equipment.js";
4
+ import { ALL_REGIONS, DEFAULT_REGION_WEIGHTS } from "../body.js";
5
+ import { getExposureWeight } from "../bodyplan.js";
6
+ import { DamageChannel } from "../../channels.js";
7
+ import { armourCoversHit } from "../kernel.js";
8
+ import { regionKOFactor, totalBleedingRate } from "../injury.js";
9
+ import { getSkill } from "../skills.js";
10
+ import { v3 } from "../vec3.js";
11
+ import { TICK_HZ, DT_S } from "../tick.js";
12
+ /* ------------------ Conditions -> injury (armour-aware) ------------------ */
13
+ export const SHOCK_FROM_FLUID = q(0.0040);
14
+ export const SHOCK_FROM_INTERNAL = q(0.0020);
15
+ export const CONSC_LOSS_FROM_SHOCK = q(0.0100);
16
+ export const CONSC_LOSS_FROM_SUFF = q(0.0200);
17
+ export const FATAL_FLUID_LOSS = q(0.80);
18
+ export function stepConditionsToInjury(e, world, ambientTemperature_Q) {
19
+ const traitProfile = buildTraitProfile(e.traits);
20
+ const armour = deriveArmourProfile(e.loadout);
21
+ // Phase 8: use body plan segments when available; fall back to humanoid defaults.
22
+ const planSegments = e.bodyPlan?.segments ?? null;
23
+ // Exposure weights: "what tends to be exposed" for systemic hazards.
24
+ const exposureWeights = (channel) => {
25
+ if (planSegments) {
26
+ // Data-driven: per-segment per-channel weights from body plan
27
+ const out = {};
28
+ for (const seg of planSegments)
29
+ out[seg.id] = getExposureWeight(seg, channel);
30
+ return out;
31
+ }
32
+ // Humanoid fallback
33
+ switch (channel) {
34
+ case DamageChannel.Thermal:
35
+ // Fire: limbs tend to be exposed and catch/keep burning; torso often partly shielded.
36
+ return {
37
+ head: q(0.18),
38
+ torso: q(0.28),
39
+ leftArm: q(0.14),
40
+ rightArm: q(0.14),
41
+ leftLeg: q(0.13),
42
+ rightLeg: q(0.13),
43
+ };
44
+ case DamageChannel.Chemical:
45
+ // Chemical/corrosive aerosols: more even, but torso still prominent.
46
+ // Note: condition.corrosiveExposure feeds this channel — Chemical and
47
+ // Corrosive are unified here. DamageChannel.Corrosive is reserved for
48
+ // future liquid-contact mechanics with a distinct distribution profile.
49
+ return {
50
+ head: q(0.16),
51
+ torso: q(0.36),
52
+ leftArm: q(0.12),
53
+ rightArm: q(0.12),
54
+ leftLeg: q(0.12),
55
+ rightLeg: q(0.12),
56
+ };
57
+ case DamageChannel.Radiation:
58
+ // Penetrating radiation: roughly proportional to mass (torso dominant).
59
+ return {
60
+ head: q(0.12),
61
+ torso: q(0.52),
62
+ leftArm: q(0.09),
63
+ rightArm: q(0.09),
64
+ leftLeg: q(0.09),
65
+ rightLeg: q(0.09),
66
+ };
67
+ case DamageChannel.Electrical:
68
+ // Conductive contact often through extremities.
69
+ return {
70
+ head: q(0.10),
71
+ torso: q(0.22),
72
+ leftArm: q(0.22),
73
+ rightArm: q(0.22),
74
+ leftLeg: q(0.12),
75
+ rightLeg: q(0.12),
76
+ };
77
+ default:
78
+ // Fallback: assume proportional to area.
79
+ return DEFAULT_REGION_WEIGHTS;
80
+ }
81
+ };
82
+ const applyDoseToRegion = (channel, region, dose) => {
83
+ if (dose <= 0)
84
+ return q(0);
85
+ if ((traitProfile.immuneMask & (1 << channel)) !== 0)
86
+ return q(0);
87
+ let out = dose;
88
+ if ((traitProfile.resistantMask & (1 << channel)) !== 0)
89
+ out = Math.trunc(out / 2);
90
+ const cov = (armour.coverageByRegion)[region] ?? q(0);
91
+ const armCovers = armourCoversHit(world, cov, e.id, (e.id ^ 0xBEEF) + (channel << 8) + regionSalt(region));
92
+ if (armCovers && ((armour.protects & (1 << channel)) !== 0)) {
93
+ const mul = armour.channelResistMul[channel] ?? q(1.0);
94
+ // A simple "resist factor" curve; for non-kinetic we treat resist_J as a generalised protective capacity.
95
+ const resistFactor = clampQ(q(1.0) - (mulDiv(Math.min(armour.resist_J, 800) * SCALE.Q, 1, 800)), q(0.20), q(1.0));
96
+ out = qMul(qMul(out, resistFactor), armour.protectedDamageMul);
97
+ out = qMul(out, mul);
98
+ }
99
+ return out;
100
+ };
101
+ const distribute = (channel, dose) => {
102
+ const w = exposureWeights(channel);
103
+ const out = {};
104
+ const regionList = planSegments ? planSegments.map(s => s.id) : ALL_REGIONS;
105
+ for (const r of regionList)
106
+ out[r] = qMul(dose, w[r] ?? q(0));
107
+ return out;
108
+ };
109
+ const fireBy = distribute(DamageChannel.Thermal, e.condition.onFire);
110
+ const corrBy = distribute(DamageChannel.Chemical, e.condition.corrosiveExposure);
111
+ const elecBy = distribute(DamageChannel.Electrical, e.condition.electricalOverload);
112
+ const radBy = distribute(DamageChannel.Radiation, e.condition.radiation);
113
+ // Suffocation is global rather than surface-localised.
114
+ const suff = (() => {
115
+ if ((traitProfile.immuneMask & (1 << DamageChannel.Suffocation)) !== 0)
116
+ return q(0);
117
+ let out = e.condition.suffocation;
118
+ if ((traitProfile.resistantMask & (1 << DamageChannel.Suffocation)) !== 0)
119
+ out = Math.trunc(out / 2);
120
+ // Simple: masks/helmets reduce suffocation slightly if they protect Suffocation.
121
+ const armCovers = armourCoversHit(world, (armour.coverageByRegion)["head"] ?? q(0), e.id, e.id ^ 0x5AFF);
122
+ if (armCovers && ((armour.protects & (1 << DamageChannel.Suffocation)) !== 0)) {
123
+ out = qMul(out, armour.protectedDamageMul);
124
+ }
125
+ return out;
126
+ })();
127
+ const FIRE_SURFACE_PER_TICK = q(0.0020);
128
+ const FIRE_SHOCK_PER_TICK = q(0.0010);
129
+ const CORR_SURFACE_PER_TICK = q(0.0015);
130
+ const CORR_INTERNAL_PER_TICK = q(0.0008);
131
+ const SUFF_SHOCK_PER_TICK = q(0.0015);
132
+ const ELEC_INTERNAL_PER_TICK = q(0.0010);
133
+ const ELEC_STUNNED_RISE = q(0.0200);
134
+ // Radiation: primary effect is internal cellular damage accumulating slowly.
135
+ // Rate calibrated so continuous exposure at q(1.0) reaches ~50% internal
136
+ // damage on the torso (highest-weight region) after ~250 ticks (12.5 s).
137
+ const RAD_INTERNAL_PER_TICK = q(0.0008);
138
+ const RAD_SHOCK_PER_TICK = q(0.0003);
139
+ const allRegionIds = planSegments ? planSegments.map(s => s.id) : ALL_REGIONS;
140
+ for (const r of allRegionIds) {
141
+ const fire = applyDoseToRegion(DamageChannel.Thermal, r, fireBy[r] ?? q(0));
142
+ const corr = applyDoseToRegion(DamageChannel.Chemical, r, corrBy[r] ?? q(0));
143
+ const elec = applyDoseToRegion(DamageChannel.Electrical, r, elecBy[r] ?? q(0));
144
+ const rad = applyDoseToRegion(DamageChannel.Radiation, r, radBy[r] ?? q(0));
145
+ const reg = e.injury.byRegion[r];
146
+ if (!reg)
147
+ continue;
148
+ if (fire > 0) {
149
+ reg.surfaceDamage = clampQ(reg.surfaceDamage + qMul(fire, FIRE_SURFACE_PER_TICK), 0, SCALE.Q);
150
+ e.injury.shock = clampQ(e.injury.shock + qMul(fire, FIRE_SHOCK_PER_TICK), 0, SCALE.Q);
151
+ }
152
+ if (corr > 0) {
153
+ reg.surfaceDamage = clampQ(reg.surfaceDamage + qMul(corr, CORR_SURFACE_PER_TICK), 0, SCALE.Q);
154
+ reg.internalDamage = clampQ(reg.internalDamage + qMul(corr, CORR_INTERNAL_PER_TICK), 0, SCALE.Q);
155
+ }
156
+ if (elec > 0) {
157
+ reg.internalDamage = clampQ(reg.internalDamage + qMul(elec, ELEC_INTERNAL_PER_TICK), 0, SCALE.Q);
158
+ e.condition.stunned = clampQ(e.condition.stunned + qMul(elec, ELEC_STUNNED_RISE), 0, SCALE.Q);
159
+ }
160
+ if (rad > 0) {
161
+ reg.internalDamage = clampQ(reg.internalDamage + qMul(rad, RAD_INTERNAL_PER_TICK), 0, SCALE.Q);
162
+ e.injury.shock = clampQ(e.injury.shock + qMul(rad, RAD_SHOCK_PER_TICK), 0, SCALE.Q);
163
+ }
164
+ }
165
+ if (suff > 0) {
166
+ e.injury.shock = clampQ(e.injury.shock + qMul(suff, SUFF_SHOCK_PER_TICK), 0, SCALE.Q);
167
+ }
168
+ // Phase 10: ambient temperature stress
169
+ if (ambientTemperature_Q !== undefined) {
170
+ const COMFORT_HIGH = q(0.65);
171
+ const COMFORT_LOW = q(0.35);
172
+ if (ambientTemperature_Q > COMFORT_HIGH) {
173
+ // Heat stress: shock + mild surface damage; heatTolerance scales dose
174
+ const excess = clampQ((ambientTemperature_Q - COMFORT_HIGH), q(0), q(1.0));
175
+ const baseDose = qMul(excess, q(0.025));
176
+ const heatTol = Math.max(1, e.attributes.resilience.heatTolerance);
177
+ const dose = mulDiv(baseDose, SCALE.Q, heatTol);
178
+ e.injury.shock = clampQ((e.injury.shock + dose), 0, SCALE.Q);
179
+ const torsoReg = e.injury.byRegion["torso"] ?? Object.values(e.injury.byRegion)[0];
180
+ if (torsoReg) {
181
+ torsoReg.surfaceDamage = clampQ((torsoReg.surfaceDamage + qMul(dose, q(0.20))), 0, SCALE.Q);
182
+ }
183
+ }
184
+ else if (ambientTemperature_Q < COMFORT_LOW) {
185
+ // Cold stress: shock + fatigue; coldTolerance scales dose
186
+ const deficit = clampQ((COMFORT_LOW - ambientTemperature_Q), q(0), q(1.0));
187
+ const baseDose = qMul(deficit, q(0.020));
188
+ const coldTol = Math.max(1, e.attributes.resilience.coldTolerance);
189
+ const dose = mulDiv(baseDose, SCALE.Q, coldTol);
190
+ e.injury.shock = clampQ((e.injury.shock + dose), 0, SCALE.Q);
191
+ e.energy.fatigue = clampQ((e.energy.fatigue + qMul(dose, q(0.50))), 0, SCALE.Q);
192
+ }
193
+ }
194
+ }
195
+ export function stepInjuryProgression(e, tick) {
196
+ if (e.injury.dead)
197
+ return;
198
+ // Phase 9: natural clotting — bleedingRate decays proportional to structural integrity.
199
+ // Heavily damaged tissue clots slowly; intact tissue clots quickly.
200
+ const CLOT_RATE_PER_TICK = q(0.0002);
201
+ const INFECTION_BLEED_THRESHOLD = q(0.05);
202
+ const INFECTION_INT_THRESHOLD = q(0.10);
203
+ const INFECTION_ONSET_TICKS = 100;
204
+ const INFECTION_DAMAGE_PER_TICK = q(0.0003);
205
+ const PERMANENT_THRESHOLD = q(0.90);
206
+ const PERMANENT_FLOOR_MUL = q(0.75);
207
+ for (const reg of Object.values(e.injury.byRegion)) {
208
+ // Clotting
209
+ if (reg.bleedingRate > 0) {
210
+ const structureIntegrity = clampQ((SCALE.Q - reg.structuralDamage), q(0), q(1.0));
211
+ const clotRate = qMul(structureIntegrity, CLOT_RATE_PER_TICK);
212
+ reg.bleedingRate = clampQ((reg.bleedingRate - clotRate), q(0), q(1.0));
213
+ }
214
+ // Infection timer — track consecutive ticks of active bleeding
215
+ if (reg.bleedingRate > INFECTION_BLEED_THRESHOLD) {
216
+ reg.bleedDuration_ticks++;
217
+ if (reg.bleedDuration_ticks >= INFECTION_ONSET_TICKS
218
+ && reg.internalDamage > INFECTION_INT_THRESHOLD
219
+ && reg.infectedTick < 0) {
220
+ reg.infectedTick = tick;
221
+ }
222
+ }
223
+ else {
224
+ reg.bleedDuration_ticks = Math.max(0, reg.bleedDuration_ticks - 1);
225
+ }
226
+ // Infection progression — infected regions accumulate internal damage
227
+ if (reg.infectedTick >= 0) {
228
+ reg.internalDamage = clampQ(reg.internalDamage + INFECTION_DAMAGE_PER_TICK, 0, SCALE.Q);
229
+ }
230
+ // Permanent damage floor update — set when structural damage is very high
231
+ if (reg.structuralDamage >= PERMANENT_THRESHOLD) {
232
+ const newFloor = qMul(reg.structuralDamage, PERMANENT_FLOOR_MUL);
233
+ if (newFloor > reg.permanentDamage)
234
+ reg.permanentDamage = newFloor;
235
+ }
236
+ }
237
+ // Phase 8B: hemolymph accumulation — breached open-fluid segments leak each tick
238
+ if (e.bodyPlan) {
239
+ for (const seg of e.bodyPlan.segments) {
240
+ if (seg.fluidSystem !== "open" || seg.hemolymphLossRate === undefined)
241
+ continue;
242
+ const segState = e.injury.byRegion[seg.id];
243
+ if (!segState)
244
+ continue;
245
+ const breachAt = seg.breachThreshold ?? q(0.8);
246
+ if (segState.structuralDamage >= breachAt) {
247
+ const loss = qMul(seg.hemolymphLossRate, segState.structuralDamage);
248
+ e.injury.hemolymphLoss = clampQ((e.injury.hemolymphLoss ?? 0) + loss, 0, SCALE.Q);
249
+ }
250
+ }
251
+ }
252
+ // Phase 8B: hemolymph fatal threshold — same as fluidLoss
253
+ const FATAL_HEMOLYMPH = q(0.80);
254
+ if ((e.injury.hemolymphLoss ?? 0) >= FATAL_HEMOLYMPH) {
255
+ e.injury.dead = true;
256
+ e.injury.consciousness = q(0);
257
+ e.velocity_mps = v3(0, 0, 0);
258
+ return;
259
+ }
260
+ // Phase 8B: molting tick countdown and structural repair on completion
261
+ if (e.molting?.active) {
262
+ e.molting.ticksRemaining = Math.max(0, e.molting.ticksRemaining - 1);
263
+ if (e.molting.ticksRemaining === 0) {
264
+ e.molting.active = false;
265
+ // Repair regeneratesViaMolting segments
266
+ if (e.bodyPlan) {
267
+ for (const seg of e.bodyPlan.segments) {
268
+ if (!seg.regeneratesViaMolting)
269
+ continue;
270
+ const segState = e.injury.byRegion[seg.id];
271
+ if (!segState)
272
+ continue;
273
+ segState.structuralDamage = clampQ((segState.structuralDamage - q(0.10)), 0, SCALE.Q);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ // Phase 8B: hemolymph clotting — passive decay of hemolymph loss each tick
279
+ const HEMOLYMPH_CLOT_RATE = q(0.0001);
280
+ if ((e.injury.hemolymphLoss ?? 0) > 0) {
281
+ e.injury.hemolymphLoss = clampQ(((e.injury.hemolymphLoss ?? 0) - HEMOLYMPH_CLOT_RATE), 0, SCALE.Q);
282
+ }
283
+ // Phase 8B: auto-molt trigger — fires when average structural damage on
284
+ // regeneratesViaMolting segments reaches MOLT_TRIGGER_THRESHOLD and no molt
285
+ // is already active. Post-molt repair (−q(0.10)) typically drops average below
286
+ // threshold, preventing immediate re-trigger for minor damage; severely damaged
287
+ // entities will re-molt until damage falls below the threshold.
288
+ const MOLT_TRIGGER_THRESHOLD = q(0.40);
289
+ const MOLT_DURATION_TICKS = TICK_HZ * 60; // 60 seconds at TICK_HZ fps
290
+ if (e.bodyPlan && !e.molting?.active) {
291
+ const regenSegs = e.bodyPlan.segments.filter(s => s.regeneratesViaMolting);
292
+ if (regenSegs.length > 0) {
293
+ let totalDmg = 0;
294
+ for (const seg of regenSegs) {
295
+ totalDmg += e.injury.byRegion[seg.id]?.structuralDamage ?? 0;
296
+ }
297
+ const avgDmg = Math.trunc(totalDmg / regenSegs.length);
298
+ if (avgDmg >= MOLT_TRIGGER_THRESHOLD) {
299
+ e.molting = {
300
+ active: true,
301
+ ticksRemaining: MOLT_DURATION_TICKS,
302
+ softeningSegments: regenSegs.map(s => s.id),
303
+ };
304
+ }
305
+ }
306
+ }
307
+ // Phase 8B: wing passive regeneration — slow structural repair on wing segments
308
+ // when not actively molting (molting repair is handled above on completion).
309
+ const WING_REGEN_RATE = q(0.0001);
310
+ if (e.bodyPlan?.locomotion.flight && !e.molting?.active) {
311
+ for (const wid of e.bodyPlan.locomotion.flight.wingSegments) {
312
+ const ws = e.injury.byRegion[wid];
313
+ if (ws && ws.structuralDamage > 0) {
314
+ ws.structuralDamage = clampQ((ws.structuralDamage - WING_REGEN_RATE), 0, SCALE.Q);
315
+ }
316
+ }
317
+ }
318
+ const bleedRate = totalBleedingRate(e.injury);
319
+ const rawBleedThisTick = Math.trunc((bleedRate * DT_S) / SCALE.s);
320
+ // Phase 7: medical.treatmentRateMul reduces fluid loss (passive wound management)
321
+ const medSkill = getSkill(e.skills, "medical");
322
+ const bleedThisTick = medSkill.treatmentRateMul > SCALE.Q
323
+ ? mulDiv(rawBleedThisTick, SCALE.Q, medSkill.treatmentRateMul)
324
+ : rawBleedThisTick;
325
+ e.injury.fluidLoss = clampQ(e.injury.fluidLoss + bleedThisTick, 0, SCALE.Q);
326
+ e.injury.shock = clampQ(e.injury.shock + qMul(e.injury.fluidLoss, SHOCK_FROM_FLUID) + qMul(e.injury.byRegion["torso"]?.internalDamage ?? q(0), SHOCK_FROM_INTERNAL), 0, SCALE.Q);
327
+ const loss = clampQ(qMul(e.injury.shock, CONSC_LOSS_FROM_SHOCK) + qMul(e.condition.suffocation, CONSC_LOSS_FROM_SUFF) + qMul(regionKOFactor(e.injury), q(0.0100)), 0, SCALE.Q);
328
+ e.injury.consciousness = clampQ(e.injury.consciousness - loss, 0, SCALE.Q);
329
+ // Phase 9: explicit fatal fluid loss threshold (complements the shock path)
330
+ if (e.injury.fluidLoss >= FATAL_FLUID_LOSS || e.injury.shock >= SCALE.Q || e.injury.consciousness === 0) {
331
+ e.injury.dead = true;
332
+ e.injury.consciousness = q(0);
333
+ e.velocity_mps = v3(0, 0, 0);
334
+ }
335
+ }
336
+ function regionSalt(region) {
337
+ // Well-known humanoid regions get stable salts; others use a hash of the id string.
338
+ switch (region) {
339
+ case "head": return 0x11;
340
+ case "torso": return 0x22;
341
+ case "leftArm": return 0x33;
342
+ case "rightArm": return 0x44;
343
+ case "leftLeg": return 0x55;
344
+ case "rightLeg": return 0x66;
345
+ default: {
346
+ // Deterministic hash of the segment id (FNV-1a-like)
347
+ let h = 0x77;
348
+ for (let i = 0; i < region.length; i++)
349
+ h = ((h ^ region.charCodeAt(i)) * 0x1f) & 0xFF;
350
+ return h || 0x77;
351
+ }
352
+ }
353
+ }
@@ -0,0 +1,11 @@
1
+ import type { WorldState } from "../world.js";
2
+ import type { Entity } from "../entity.js";
3
+ import type { WorldIndex } from "../indexing.js";
4
+ import type { SpatialIndex } from "../spatial.js";
5
+ import type { TraceSink } from "../trace.js";
6
+ import { KernelContext } from "../context.js";
7
+ /**
8
+ * Per-entity morale update — accumulates fear from all sources and applies decay.
9
+ * Emits a MoraleRoute trace event whenever the entity crosses the routing threshold.
10
+ */
11
+ export declare function stepMoraleForEntity(world: WorldState, e: Entity, index: WorldIndex, spatial: SpatialIndex, aliveBeforeTick: Set<number>, teamRoutingFrac: Map<number, number>, trace: TraceSink, ctx: KernelContext): void;