@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,150 @@
1
+ import { type Q } from "../units.js";
2
+ import type { Entity } from "./entity.js";
3
+ /** Movement gait of the mounted pair. */
4
+ export type MountGait = "walk" | "trot" | "gallop" | "charge";
5
+ /** Species-level mount data record. */
6
+ export interface MountProfile {
7
+ id: string;
8
+ name: string;
9
+ /** Body mass of the mount [SCALE.kg]. */
10
+ mass_kg: number;
11
+ /** Height of rider's seat above ground [SCALE.m]. */
12
+ riderHeightBonus_m: number;
13
+ /** Maximum load the mount can carry (rider + equipment) [SCALE.kg]. */
14
+ maxCarryCapacity_kg: number;
15
+ /** Walk speed [SCALE.mps]. */
16
+ walkSpeed_mps: number;
17
+ /** Trot speed [SCALE.mps]. */
18
+ trotSpeed_mps: number;
19
+ /** Gallop speed [SCALE.mps]. */
20
+ gallopSpeed_mps: number;
21
+ /** Charge speed — typically faster than gallop [SCALE.mps]. */
22
+ chargeSpeed_mps: number;
23
+ /** Mount postural stability [Q]. Partially inherited by rider. */
24
+ stability_Q: Q;
25
+ /**
26
+ * Fear threshold [Q].
27
+ * When mount shockQ exceeds this value, fear propagates to the rider and
28
+ * the mount risks bolting (forced dismount).
29
+ */
30
+ fearThreshold_Q: Q;
31
+ }
32
+ /** Kinetic energy delivered to a target in a charge attack. */
33
+ export interface ChargeBonus {
34
+ /** Extra energy delivered to the target [J, SCALE.J = 1]. */
35
+ bonusEnergy_J: number;
36
+ /** Effective striking mass (the fraction of mount mass in the impact) [SCALE.kg]. */
37
+ strikeMass_kg: number;
38
+ }
39
+ /** Why a dismount was triggered. */
40
+ export type DismountCause = "none" | "rider_shock" | "mount_dead" | "mount_bolt";
41
+ /** Result of evaluating one mount/rider tick. */
42
+ export interface MountStepResult {
43
+ /** True when the rider should be dismounted this tick. */
44
+ shouldDismount: boolean;
45
+ /** Reason for the dismount (or "none" if not dismounting). */
46
+ dismountCause: DismountCause;
47
+ /** Fall injury energy [J] — non-zero only when shouldDismount is true. */
48
+ fallEnergy_J: number;
49
+ /** Fear Q to add to the rider's condition from mount panic [Q]. */
50
+ fearPressure_Q: Q;
51
+ }
52
+ /**
53
+ * Per-entity mount/rider pair state, stored on `entity.mount`.
54
+ *
55
+ * On the rider entity: `mountId` is set, `riderId` is 0.
56
+ * On the mount entity: `riderId` is set, `mountId` is 0.
57
+ */
58
+ export interface MountState {
59
+ /** Entity ID of the mount being ridden. 0 if this entity is not riding. */
60
+ mountId: number;
61
+ /** Entity ID of the rider being carried. 0 if this entity is not a mount. */
62
+ riderId: number;
63
+ /** Current movement gait. */
64
+ gait: MountGait;
65
+ }
66
+ /** Fraction of mount mass that contributes to charge strike energy [Q]. */
67
+ export declare const CHARGE_MASS_FRAC: Q;
68
+ /** Rider shock level above which a forced dismount occurs [Q]. */
69
+ export declare const DISMOUNT_SHOCK_Q: Q;
70
+ /** Aim / accuracy bonus per real metre of rider elevation [Q]. */
71
+ export declare const HEIGHT_AIM_BONUS_PER_M: Q;
72
+ /** Maximum rider height aim bonus (caps at 2.5 m for war elephant) [Q]. */
73
+ export declare const HEIGHT_AIM_BONUS_MAX: Q;
74
+ /** Fraction of mount stability that transfers to the rider [Q]. */
75
+ export declare const RIDER_STABILITY_INHERIT: Q;
76
+ /** Fraction of excess mount-shock (beyond fearThreshold) that propagates to rider [Q]. */
77
+ export declare const MOUNT_FEAR_CONTAGION: Q;
78
+ export declare const PONY: MountProfile;
79
+ export declare const HORSE: MountProfile;
80
+ export declare const WARHORSE: MountProfile;
81
+ export declare const CAMEL: MountProfile;
82
+ export declare const WAR_ELEPHANT: MountProfile;
83
+ /** All mount profiles in one array. */
84
+ export declare const ALL_MOUNTS: MountProfile[];
85
+ /**
86
+ * Return the mount's speed in SCALE.mps for the given gait.
87
+ */
88
+ export declare function getMountGaitSpeed(profile: MountProfile, gait: MountGait): number;
89
+ /**
90
+ * Compute the bonus kinetic energy delivered to a target during a mounted charge.
91
+ *
92
+ * Only `CHARGE_MASS_FRAC` (8%) of the mount's mass participates in the impact;
93
+ * the remainder is absorbed through the mount's body.
94
+ *
95
+ * `bonusEnergy_J = ½ × strikeMass × v²` (SI, result in joules)
96
+ *
97
+ * @param speed_Smps Current charge speed [SCALE.mps].
98
+ */
99
+ export declare function computeChargeBonus(profile: MountProfile, speed_Smps: number): ChargeBonus;
100
+ /**
101
+ * Derive the aim/accuracy bonus a rider gains from elevation.
102
+ *
103
+ * `aimBonus_Q = (riderHeightBonus_m / SCALE.m) × HEIGHT_AIM_BONUS_PER_M`
104
+ * Capped at HEIGHT_AIM_BONUS_MAX = q(0.30).
105
+ */
106
+ export declare function deriveRiderHeightBonus(profile: MountProfile): Q;
107
+ /**
108
+ * Derive the stability bonus a rider inherits from a well-balanced mount.
109
+ *
110
+ * `stabilityBonus_Q = mount.stability_Q × RIDER_STABILITY_INHERIT / SCALE.Q`
111
+ * Capped at q(0.20).
112
+ */
113
+ export declare function deriveRiderStabilityBonus(profile: MountProfile): Q;
114
+ /**
115
+ * Compute the fall injury energy when a rider is dismounted from height.
116
+ *
117
+ * Models a free-fall from the rider's seat height:
118
+ * fallEnergy_J = riderMass × g × height
119
+ *
120
+ * @param riderMass_Skg Rider's mass [SCALE.kg].
121
+ */
122
+ export declare function computeFallEnergy_J(profile: MountProfile, riderMass_Skg: number): number;
123
+ /**
124
+ * Derive the fear pressure transmitted from a panicking mount to its rider.
125
+ *
126
+ * Returns q(0) when the mount's shockQ is at or below its fearThreshold.
127
+ * Above the threshold, 40% of the excess is propagated to the rider.
128
+ *
129
+ * @param mountShockQ Current shock level of the mount [Q].
130
+ * @param fearThreshold_Q Mount's panic threshold [Q].
131
+ */
132
+ export declare function deriveMountFearPressure(mountShockQ: Q, fearThreshold_Q: Q): Q;
133
+ /**
134
+ * Evaluate a single mounted-combat tick, returning dismount and fear outcomes.
135
+ *
136
+ * Does NOT mutate any entity — pure computation for the host to apply.
137
+ *
138
+ * Dismount priority: rider_shock > mount_dead > mount_bolt.
139
+ *
140
+ * @param riderShockQ Rider's current shockQ [Q].
141
+ * @param mountShockQ Mount's current shockQ [Q].
142
+ * @param mountDead True if the mount has died this tick.
143
+ * @param profile Mount species profile.
144
+ * @param riderMass_Skg Rider's mass [SCALE.kg] — used for fall energy.
145
+ */
146
+ export declare function checkMountStep(riderShockQ: Q, mountShockQ: Q, mountDead: boolean, profile: MountProfile, riderMass_Skg?: number): MountStepResult;
147
+ /** True if this entity is currently riding a mount. */
148
+ export declare function entityIsMounted(entity: Entity): boolean;
149
+ /** True if this entity is currently carrying a rider. */
150
+ export declare function entityIsMount(entity: Entity): boolean;
@@ -0,0 +1,225 @@
1
+ // src/sim/mount.ts — Phase 59: Mounted Combat & Riding
2
+ //
3
+ // Physics-grounded model for rider/mount pairs.
4
+ //
5
+ // Charge attack: a fraction of the mount's kinetic energy is delivered to the target.
6
+ // bonusEnergy_J = ½ × (mass_kg × CHARGE_MASS_FRAC / SCALE.Q) × v²
7
+ //
8
+ // Height advantage: elevated rider gains an aim bonus proportional to mount height.
9
+ // aimBonus_Q = min(riderHeightBonus_m / SCALE.m × HEIGHT_AIM_BONUS_PER_M, HEIGHT_AIM_BONUS_MAX)
10
+ //
11
+ // Fear contagion: when mount shock exceeds its fearThreshold, excess shock propagates to rider.
12
+ //
13
+ // Forced dismount: triggered by rider over-shock, mount death, or mount bolting.
14
+ //
15
+ // Public API:
16
+ // getMountGaitSpeed(profile, gait) → SCALE.mps
17
+ // computeChargeBonus(profile, speed_Smps) → ChargeBonus
18
+ // deriveRiderHeightBonus(profile) → Q
19
+ // deriveRiderStabilityBonus(profile) → Q
20
+ // computeFallEnergy_J(profile, riderMass_Skg)→ number (joules)
21
+ // deriveMountFearPressure(mountShockQ, fearThreshold_Q) → Q
22
+ // checkMountStep(riderShockQ, mountShockQ, mountDead, profile) → MountStepResult
23
+ // entityIsMounted(entity) → boolean
24
+ // entityIsMount(entity) → boolean
25
+ import { q, clampQ, to, SCALE } from "../units.js";
26
+ // ── Constants ─────────────────────────────────────────────────────────────────
27
+ /** Fraction of mount mass that contributes to charge strike energy [Q]. */
28
+ export const CHARGE_MASS_FRAC = q(0.08);
29
+ /** Rider shock level above which a forced dismount occurs [Q]. */
30
+ export const DISMOUNT_SHOCK_Q = q(0.65);
31
+ /** Aim / accuracy bonus per real metre of rider elevation [Q]. */
32
+ export const HEIGHT_AIM_BONUS_PER_M = q(0.12);
33
+ /** Maximum rider height aim bonus (caps at 2.5 m for war elephant) [Q]. */
34
+ export const HEIGHT_AIM_BONUS_MAX = q(0.30);
35
+ /** Fraction of mount stability that transfers to the rider [Q]. */
36
+ export const RIDER_STABILITY_INHERIT = q(0.15);
37
+ /** Fraction of excess mount-shock (beyond fearThreshold) that propagates to rider [Q]. */
38
+ export const MOUNT_FEAR_CONTAGION = q(0.40);
39
+ // ── Mount profiles ────────────────────────────────────────────────────────────
40
+ export const PONY = {
41
+ id: "pony",
42
+ name: "Pony",
43
+ mass_kg: to.kg(250),
44
+ riderHeightBonus_m: to.m(0.90),
45
+ maxCarryCapacity_kg: to.kg(100),
46
+ walkSpeed_mps: to.mps(1.5),
47
+ trotSpeed_mps: to.mps(4.0),
48
+ gallopSpeed_mps: to.mps(10.0),
49
+ chargeSpeed_mps: to.mps(11.0),
50
+ stability_Q: q(0.82),
51
+ fearThreshold_Q: q(0.50),
52
+ };
53
+ export const HORSE = {
54
+ id: "horse",
55
+ name: "Horse",
56
+ mass_kg: to.kg(450),
57
+ riderHeightBonus_m: to.m(1.20),
58
+ maxCarryCapacity_kg: to.kg(130),
59
+ walkSpeed_mps: to.mps(1.6),
60
+ trotSpeed_mps: to.mps(4.5),
61
+ gallopSpeed_mps: to.mps(14.0),
62
+ chargeSpeed_mps: to.mps(16.0),
63
+ stability_Q: q(0.88),
64
+ fearThreshold_Q: q(0.58),
65
+ };
66
+ export const WARHORSE = {
67
+ id: "warhorse",
68
+ name: "Warhorse",
69
+ mass_kg: to.kg(550),
70
+ riderHeightBonus_m: to.m(1.30),
71
+ maxCarryCapacity_kg: to.kg(150),
72
+ walkSpeed_mps: to.mps(1.5),
73
+ trotSpeed_mps: to.mps(4.0),
74
+ gallopSpeed_mps: to.mps(13.0),
75
+ chargeSpeed_mps: to.mps(17.0),
76
+ stability_Q: q(0.92),
77
+ fearThreshold_Q: q(0.72), // battle-trained — much harder to panic
78
+ };
79
+ export const CAMEL = {
80
+ id: "camel",
81
+ name: "Camel",
82
+ mass_kg: to.kg(400),
83
+ riderHeightBonus_m: to.m(1.80),
84
+ maxCarryCapacity_kg: to.kg(160),
85
+ walkSpeed_mps: to.mps(1.5),
86
+ trotSpeed_mps: to.mps(3.5),
87
+ gallopSpeed_mps: to.mps(11.0),
88
+ chargeSpeed_mps: to.mps(12.0),
89
+ stability_Q: q(0.80),
90
+ fearThreshold_Q: q(0.52),
91
+ };
92
+ export const WAR_ELEPHANT = {
93
+ id: "war_elephant",
94
+ name: "War Elephant",
95
+ mass_kg: to.kg(3_000),
96
+ riderHeightBonus_m: to.m(2.50),
97
+ maxCarryCapacity_kg: to.kg(300),
98
+ walkSpeed_mps: to.mps(2.0),
99
+ trotSpeed_mps: to.mps(4.0),
100
+ gallopSpeed_mps: to.mps(7.0),
101
+ chargeSpeed_mps: to.mps(8.0),
102
+ stability_Q: q(0.92),
103
+ fearThreshold_Q: q(0.48), // prone to panic but devastating when controlled
104
+ };
105
+ /** All mount profiles in one array. */
106
+ export const ALL_MOUNTS = [PONY, HORSE, WARHORSE, CAMEL, WAR_ELEPHANT];
107
+ // ── Core computation ──────────────────────────────────────────────────────────
108
+ /**
109
+ * Return the mount's speed in SCALE.mps for the given gait.
110
+ */
111
+ export function getMountGaitSpeed(profile, gait) {
112
+ switch (gait) {
113
+ case "walk": return profile.walkSpeed_mps;
114
+ case "trot": return profile.trotSpeed_mps;
115
+ case "gallop": return profile.gallopSpeed_mps;
116
+ case "charge": return profile.chargeSpeed_mps;
117
+ }
118
+ }
119
+ /**
120
+ * Compute the bonus kinetic energy delivered to a target during a mounted charge.
121
+ *
122
+ * Only `CHARGE_MASS_FRAC` (8%) of the mount's mass participates in the impact;
123
+ * the remainder is absorbed through the mount's body.
124
+ *
125
+ * `bonusEnergy_J = ½ × strikeMass × v²` (SI, result in joules)
126
+ *
127
+ * @param speed_Smps Current charge speed [SCALE.mps].
128
+ */
129
+ export function computeChargeBonus(profile, speed_Smps) {
130
+ if (speed_Smps <= 0)
131
+ return { bonusEnergy_J: 0, strikeMass_kg: 0 };
132
+ const strikeMass_kg = Math.round(profile.mass_kg * CHARGE_MASS_FRAC / SCALE.Q);
133
+ // ½ × (strikeMass_kg / SCALE.kg) × (speed_Smps / SCALE.mps)² — result in J (SCALE.J = 1)
134
+ const bonusEnergy_J = Math.round(0.5 * strikeMass_kg * speed_Smps * speed_Smps / (SCALE.kg * SCALE.mps * SCALE.mps));
135
+ return { bonusEnergy_J, strikeMass_kg };
136
+ }
137
+ /**
138
+ * Derive the aim/accuracy bonus a rider gains from elevation.
139
+ *
140
+ * `aimBonus_Q = (riderHeightBonus_m / SCALE.m) × HEIGHT_AIM_BONUS_PER_M`
141
+ * Capped at HEIGHT_AIM_BONUS_MAX = q(0.30).
142
+ */
143
+ export function deriveRiderHeightBonus(profile) {
144
+ const heightBonus_Q = Math.round(profile.riderHeightBonus_m * HEIGHT_AIM_BONUS_PER_M / SCALE.m);
145
+ return clampQ(heightBonus_Q, q(0), HEIGHT_AIM_BONUS_MAX);
146
+ }
147
+ /**
148
+ * Derive the stability bonus a rider inherits from a well-balanced mount.
149
+ *
150
+ * `stabilityBonus_Q = mount.stability_Q × RIDER_STABILITY_INHERIT / SCALE.Q`
151
+ * Capped at q(0.20).
152
+ */
153
+ export function deriveRiderStabilityBonus(profile) {
154
+ return clampQ(Math.round(profile.stability_Q * RIDER_STABILITY_INHERIT / SCALE.Q), q(0), q(0.20));
155
+ }
156
+ /**
157
+ * Compute the fall injury energy when a rider is dismounted from height.
158
+ *
159
+ * Models a free-fall from the rider's seat height:
160
+ * fallEnergy_J = riderMass × g × height
161
+ *
162
+ * @param riderMass_Skg Rider's mass [SCALE.kg].
163
+ */
164
+ export function computeFallEnergy_J(profile, riderMass_Skg) {
165
+ if (profile.riderHeightBonus_m <= 0)
166
+ return 0;
167
+ // E = m × g × h (SI: kg × m/s² × m = J)
168
+ // m = riderMass_Skg / SCALE.kg
169
+ // g = 9.807 m/s²
170
+ // h = riderHeightBonus_m / SCALE.m
171
+ return Math.round(riderMass_Skg / SCALE.kg * 9.807 * profile.riderHeightBonus_m / SCALE.m);
172
+ }
173
+ /**
174
+ * Derive the fear pressure transmitted from a panicking mount to its rider.
175
+ *
176
+ * Returns q(0) when the mount's shockQ is at or below its fearThreshold.
177
+ * Above the threshold, 40% of the excess is propagated to the rider.
178
+ *
179
+ * @param mountShockQ Current shock level of the mount [Q].
180
+ * @param fearThreshold_Q Mount's panic threshold [Q].
181
+ */
182
+ export function deriveMountFearPressure(mountShockQ, fearThreshold_Q) {
183
+ if (mountShockQ <= fearThreshold_Q)
184
+ return q(0);
185
+ const excess_Q = mountShockQ - fearThreshold_Q;
186
+ return clampQ(Math.round(excess_Q * MOUNT_FEAR_CONTAGION / SCALE.Q), q(0), SCALE.Q);
187
+ }
188
+ /**
189
+ * Evaluate a single mounted-combat tick, returning dismount and fear outcomes.
190
+ *
191
+ * Does NOT mutate any entity — pure computation for the host to apply.
192
+ *
193
+ * Dismount priority: rider_shock > mount_dead > mount_bolt.
194
+ *
195
+ * @param riderShockQ Rider's current shockQ [Q].
196
+ * @param mountShockQ Mount's current shockQ [Q].
197
+ * @param mountDead True if the mount has died this tick.
198
+ * @param profile Mount species profile.
199
+ * @param riderMass_Skg Rider's mass [SCALE.kg] — used for fall energy.
200
+ */
201
+ export function checkMountStep(riderShockQ, mountShockQ, mountDead, profile, riderMass_Skg = to.kg(80)) {
202
+ const fearPressure_Q = deriveMountFearPressure(mountShockQ, profile.fearThreshold_Q);
203
+ // Evaluate dismount causes in priority order
204
+ let cause = "none";
205
+ if (riderShockQ > DISMOUNT_SHOCK_Q)
206
+ cause = "rider_shock";
207
+ else if (mountDead)
208
+ cause = "mount_dead";
209
+ else if (mountShockQ > profile.fearThreshold_Q)
210
+ cause = "mount_bolt";
211
+ const shouldDismount = cause !== "none";
212
+ const fallEnergy_J = shouldDismount
213
+ ? computeFallEnergy_J(profile, riderMass_Skg)
214
+ : 0;
215
+ return { shouldDismount, dismountCause: cause, fallEnergy_J, fearPressure_Q };
216
+ }
217
+ // ── Entity convenience ────────────────────────────────────────────────────────
218
+ /** True if this entity is currently riding a mount. */
219
+ export function entityIsMounted(entity) {
220
+ return (entity.mount?.mountId ?? 0) > 0;
221
+ }
222
+ /** True if this entity is currently carrying a rider. */
223
+ export function entityIsMount(entity) {
224
+ return (entity.mount?.riderId ?? 0) > 0;
225
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Phase 30 — Nutrition & Starvation
3
+ *
4
+ * Models long-term caloric balance, hunger states, and staged combat penalties
5
+ * from starvation. Complements the Phase 2B short-term stamina (reserveEnergy_J).
6
+ *
7
+ * BMR (Kleiber's law): BMR_W ≈ 80 × (mass_kg / 75)^0.75
8
+ * AMR (active): AMR_W = BMR + peakPower_W × activityFrac × 0.15
9
+ *
10
+ * Hunger state thresholds (caloric deficit relative to BMR):
11
+ * sated : deficit < 12 h × BMR
12
+ * hungry : 12 h × BMR ≤ deficit < 24 h × BMR
13
+ * starving: 24 h × BMR ≤ deficit < 72 h × BMR (+fat catabolism)
14
+ * critical: deficit ≥ 72 h × BMR (+muscle catabolism)
15
+ */
16
+ import { type Q } from "../units.js";
17
+ import type { Entity } from "./entity.js";
18
+ import type { HungerState } from "./condition.js";
19
+ export interface FoodItem {
20
+ id: string;
21
+ name: string;
22
+ /** Caloric energy in joules (e.g. 1 ration bar ≈ 2 MJ). */
23
+ energy_J: number;
24
+ /** Mass of the item in grams. */
25
+ massGrams: number;
26
+ /** Hydration provided in the same unit system as hydrationBalance_J. */
27
+ hydration_J?: number;
28
+ }
29
+ export declare const FOOD_ITEMS: readonly FoodItem[];
30
+ /**
31
+ * Compute Basal Metabolic Rate in watts (integer) using Kleiber's law.
32
+ * mass_kg must be in SCALE.kg units (e.g. 75 000 for 75 kg).
33
+ */
34
+ export declare function computeBMR(mass_kg: number): number;
35
+ /**
36
+ * Advance an entity's nutritional state by `delta_s` seconds.
37
+ *
38
+ * Mutates:
39
+ * condition.caloricBalance_J, condition.hydrationBalance_J, condition.hungerState
40
+ * attributes.morphology.mass_kg (during starving or critical)
41
+ * attributes.performance.peakForce_N (during critical only)
42
+ *
43
+ * `activity` is a Q value (0 = resting, q(1.0) = maximum).
44
+ */
45
+ export declare function stepNutrition(entity: Entity, delta_s: number, activity: Q): void;
46
+ /**
47
+ * Consume a food item from the entity's optional food inventory.
48
+ *
49
+ * Returns `false` if the food ID is unknown, or if the entity has a
50
+ * `foodInventory: Map<string, number>` that does not contain the item.
51
+ * When no inventory is present (undefined), consumption is unconditional.
52
+ *
53
+ * Side effects:
54
+ * - caloricBalance_J += food.energy_J
55
+ * - hydrationBalance_J += food.hydration_J (if any)
56
+ * - lastMealTick = tick
57
+ * - injury.fluidLoss reduced by scale(hydration_J) [for water_flask]
58
+ * - hungerState re-derived
59
+ */
60
+ export declare function consumeFood(entity: Entity, foodId: string, tick: number): boolean;
61
+ /**
62
+ * Derive performance modifiers from hunger state.
63
+ *
64
+ * staminaMul : multiplier on effective stamina energy drain (Phase 2B)
65
+ * forceMul : multiplier on effective peakForce_N in combat resolution
66
+ * latencyMul : multiplier on decision latency (Phase 4)
67
+ * moraleDecay: additional fear per tick (Phase 5)
68
+ */
69
+ export declare function deriveHungerModifiers(state: HungerState): {
70
+ staminaMul: Q;
71
+ forceMul: Q;
72
+ latencyMul: Q;
73
+ moraleDecay: Q;
74
+ };
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Phase 30 — Nutrition & Starvation
3
+ *
4
+ * Models long-term caloric balance, hunger states, and staged combat penalties
5
+ * from starvation. Complements the Phase 2B short-term stamina (reserveEnergy_J).
6
+ *
7
+ * BMR (Kleiber's law): BMR_W ≈ 80 × (mass_kg / 75)^0.75
8
+ * AMR (active): AMR_W = BMR + peakPower_W × activityFrac × 0.15
9
+ *
10
+ * Hunger state thresholds (caloric deficit relative to BMR):
11
+ * sated : deficit < 12 h × BMR
12
+ * hungry : 12 h × BMR ≤ deficit < 24 h × BMR
13
+ * starving: 24 h × BMR ≤ deficit < 72 h × BMR (+fat catabolism)
14
+ * critical: deficit ≥ 72 h × BMR (+muscle catabolism)
15
+ */
16
+ import { q, SCALE, clampQ } from "../units.js";
17
+ export const FOOD_ITEMS = [
18
+ { id: "ration_bar", name: "Ration bar", energy_J: 2_000_000, massGrams: 500 },
19
+ { id: "dried_meat", name: "Dried meat", energy_J: 1_500_000, massGrams: 300 },
20
+ { id: "hardtack", name: "Hardtack", energy_J: 800_000, massGrams: 200 },
21
+ { id: "fresh_bread", name: "Fresh bread", energy_J: 700_000, massGrams: 250 },
22
+ { id: "berry_handful", name: "Berries", energy_J: 150_000, massGrams: 50 },
23
+ { id: "water_flask", name: "Water flask", energy_J: 0, massGrams: 500, hydration_J: 500_000 },
24
+ ];
25
+ const FOOD_BY_ID = new Map(FOOD_ITEMS.map(f => [f.id, f]));
26
+ // ── Metabolic constants ───────────────────────────────────────────────────────
27
+ /** Fluid loss rate: ~2.5 L/day, scaled as 1 mL = 1000 hydration_J → 29 J/s. */
28
+ const FLUID_LOSS_RATE = 29; // hydration_J per second
29
+ /**
30
+ * Fat catabolism during starvation: ~300 g/day in SCALE.kg units.
31
+ * SCALE.kg = 1000 so 300 g = 300 SCALE.kg units.
32
+ * 300 / 86400 ≈ 0.003472 SCALE.kg/s — accumulated as float.
33
+ */
34
+ const FAT_CATAB_RATE = 300 / 86400; // SCALE.kg per second
35
+ /**
36
+ * Muscle catabolism in critical starvation: 0.5 N/hour reduction in peakForce_N.
37
+ * 0.5 N × SCALE.N = 50 SCALE.N / 3600 s ≈ 0.01389 SCALE.N/s — accumulated as float.
38
+ */
39
+ const MUSCLE_CATAB_RATE = 0.5 * SCALE.N / 3600; // SCALE.N per second
40
+ /** Seconds of BMR-equivalent deficit to enter hungry state (12 h). */
41
+ const HUNGRY_SECONDS = 12 * 3600; // 43 200
42
+ /** Seconds of BMR-equivalent deficit to enter starving state (24 h). */
43
+ const STARVING_SECONDS = 24 * 3600; // 86 400
44
+ /** Seconds of BMR-equivalent deficit to enter critical state (72 h). */
45
+ const CRITICAL_SECONDS = 72 * 3600; // 259 200
46
+ // ── Pure helpers ──────────────────────────────────────────────────────────────
47
+ /**
48
+ * Compute Basal Metabolic Rate in watts (integer) using Kleiber's law.
49
+ * mass_kg must be in SCALE.kg units (e.g. 75 000 for 75 kg).
50
+ */
51
+ export function computeBMR(mass_kg) {
52
+ const massReal = mass_kg / SCALE.kg;
53
+ return Math.round(80 * Math.pow(massReal / 75, 0.75));
54
+ }
55
+ /** Derive hunger state from caloric balance and BMR. */
56
+ function deriveHungerState(caloricBalance, bmr) {
57
+ const deficit = -caloricBalance; // positive when in deficit
58
+ if (deficit < HUNGRY_SECONDS * bmr)
59
+ return "sated";
60
+ if (deficit < STARVING_SECONDS * bmr)
61
+ return "hungry";
62
+ if (deficit < CRITICAL_SECONDS * bmr)
63
+ return "starving";
64
+ return "critical";
65
+ }
66
+ // ── Public API ────────────────────────────────────────────────────────────────
67
+ /**
68
+ * Advance an entity's nutritional state by `delta_s` seconds.
69
+ *
70
+ * Mutates:
71
+ * condition.caloricBalance_J, condition.hydrationBalance_J, condition.hungerState
72
+ * attributes.morphology.mass_kg (during starving or critical)
73
+ * attributes.performance.peakForce_N (during critical only)
74
+ *
75
+ * `activity` is a Q value (0 = resting, q(1.0) = maximum).
76
+ */
77
+ export function stepNutrition(entity, delta_s, activity) {
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ const cond = entity.condition;
80
+ const baseBMR = computeBMR(entity.attributes.morphology.mass_kg);
81
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
+ const bmrMul = entity.physiology?.bmrMultiplier ?? SCALE.Q;
83
+ const bmr = Math.round(baseBMR * bmrMul / SCALE.Q);
84
+ // Active metabolic rate: BMR + peakPower × activityFrac × 0.15
85
+ const actFrac = activity / SCALE.Q;
86
+ const amr = bmr + entity.attributes.performance.peakPower_W * actFrac * 0.15;
87
+ // Caloric and hydration drain
88
+ cond.caloricBalance_J = (cond.caloricBalance_J ?? 0) - amr * delta_s;
89
+ cond.hydrationBalance_J = (cond.hydrationBalance_J ?? 0) - FLUID_LOSS_RATE * delta_s;
90
+ // Derive and cache hunger state
91
+ const hungerState = deriveHungerState(cond.caloricBalance_J, bmr);
92
+ cond.hungerState = hungerState;
93
+ // Fat catabolism (starving and critical) — sub-unit float accumulation
94
+ if (hungerState === "starving" || hungerState === "critical") {
95
+ entity.attributes.morphology.mass_kg -= FAT_CATAB_RATE * delta_s;
96
+ if (entity.attributes.morphology.mass_kg < 0)
97
+ entity.attributes.morphology.mass_kg = 0;
98
+ }
99
+ // Muscle catabolism (critical only)
100
+ if (hungerState === "critical") {
101
+ entity.attributes.performance.peakForce_N -= MUSCLE_CATAB_RATE * delta_s;
102
+ if (entity.attributes.performance.peakForce_N < 0)
103
+ entity.attributes.performance.peakForce_N = 0;
104
+ }
105
+ }
106
+ /**
107
+ * Consume a food item from the entity's optional food inventory.
108
+ *
109
+ * Returns `false` if the food ID is unknown, or if the entity has a
110
+ * `foodInventory: Map<string, number>` that does not contain the item.
111
+ * When no inventory is present (undefined), consumption is unconditional.
112
+ *
113
+ * Side effects:
114
+ * - caloricBalance_J += food.energy_J
115
+ * - hydrationBalance_J += food.hydration_J (if any)
116
+ * - lastMealTick = tick
117
+ * - injury.fluidLoss reduced by scale(hydration_J) [for water_flask]
118
+ * - hungerState re-derived
119
+ */
120
+ export function consumeFood(entity, foodId, tick) {
121
+ const food = FOOD_BY_ID.get(foodId);
122
+ if (!food)
123
+ return false; // unknown food item
124
+ // Inventory check (undefined = unlimited supply)
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ const inventory = entity.foodInventory;
127
+ if (inventory !== undefined) {
128
+ const count = inventory.get(foodId) ?? 0;
129
+ if (count <= 0)
130
+ return false;
131
+ inventory.set(foodId, count - 1);
132
+ }
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ const cond = entity.condition;
135
+ cond.caloricBalance_J = (cond.caloricBalance_J ?? 0) + food.energy_J;
136
+ cond.hydrationBalance_J = (cond.hydrationBalance_J ?? 0) + (food.hydration_J ?? 0);
137
+ cond.lastMealTick = tick;
138
+ // Water flask: also reduces injury fluid loss
139
+ // Scale: 500 000 hydration_J → Math.trunc(500_000 / 500) = 1000 = q(0.10)
140
+ if (food.hydration_J) {
141
+ const fluidRestore = Math.trunc(food.hydration_J / 500);
142
+ entity.injury.fluidLoss = clampQ((entity.injury.fluidLoss - fluidRestore), q(0), q(1.0));
143
+ }
144
+ // Re-derive hunger state after eating
145
+ const bmr = computeBMR(entity.attributes.morphology.mass_kg);
146
+ cond.hungerState = deriveHungerState(cond.caloricBalance_J, bmr);
147
+ return true;
148
+ }
149
+ /**
150
+ * Derive performance modifiers from hunger state.
151
+ *
152
+ * staminaMul : multiplier on effective stamina energy drain (Phase 2B)
153
+ * forceMul : multiplier on effective peakForce_N in combat resolution
154
+ * latencyMul : multiplier on decision latency (Phase 4)
155
+ * moraleDecay: additional fear per tick (Phase 5)
156
+ */
157
+ export function deriveHungerModifiers(state) {
158
+ switch (state) {
159
+ case "sated":
160
+ return { staminaMul: q(1.0), forceMul: q(1.0), latencyMul: q(1.0), moraleDecay: q(0) };
161
+ case "hungry":
162
+ return { staminaMul: q(0.90), forceMul: q(1.0), latencyMul: q(1.0), moraleDecay: q(0) };
163
+ case "starving":
164
+ return { staminaMul: q(0.75), forceMul: q(0.90), latencyMul: q(1.0), moraleDecay: q(0.030) };
165
+ case "critical":
166
+ return { staminaMul: q(0.50), forceMul: q(0.80), latencyMul: q(1.50), moraleDecay: q(0.030) };
167
+ }
168
+ }
@@ -0,0 +1,8 @@
1
+ import type { Entity } from "./entity.js";
2
+ import type { WorldIndex } from "./indexing.js";
3
+ import type { SpatialIndex } from "./spatial.js";
4
+ import type { WorldState } from "./world.js";
5
+ export interface OcclusionQuery {
6
+ laneRadius_m: number;
7
+ }
8
+ export declare function isMeleeLaneOccludedByFriendly(world: WorldState | undefined, attacker: Entity, target: Entity, index: WorldIndex, spatial: SpatialIndex, q: OcclusionQuery): boolean;