@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,64 @@
1
+ import { BodyRegion } from "./body";
2
+ import { BodySegmentId } from "./bodyplan";
3
+ /** Commands are player/AI intentions */
4
+ export declare const CommandKinds: {
5
+ readonly Move: "move";
6
+ readonly Attack: "attack";
7
+ readonly AttackNearest: "attackNearest";
8
+ readonly Defend: "defend";
9
+ readonly Grapple: "grapple";
10
+ readonly BreakGrapple: "breakGrapple";
11
+ readonly BreakBind: "breakBind";
12
+ readonly Shoot: "shoot";
13
+ readonly Treat: "treat";
14
+ readonly SetProne: "setProne";
15
+ readonly Activate: "activate";
16
+ };
17
+ export type CommandKind = typeof CommandKinds[keyof typeof CommandKinds];
18
+ /** Trace events are engine observations */
19
+ export declare const TraceKinds: {
20
+ readonly TickStart: "tickStart";
21
+ readonly TickEnd: "tickEnd";
22
+ readonly Intent: "intent";
23
+ readonly Move: "move";
24
+ readonly Injury: "injury";
25
+ readonly Attack: "attack";
26
+ readonly AttackAttempt: "attackAttempt";
27
+ readonly Grapple: "grapple";
28
+ readonly KO: "ko";
29
+ readonly Death: "death";
30
+ readonly WeaponBind: "weaponBind";
31
+ readonly WeaponBindBreak: "weaponBindBreak";
32
+ readonly ProjectileHit: "projectileHit";
33
+ readonly MoraleRoute: "moraleRoute";
34
+ readonly MoraleRally: "moraleRally";
35
+ readonly Fracture: "fracture";
36
+ readonly TreatmentApplied: "treatmentApplied";
37
+ readonly BlastHit: "blastHit";
38
+ readonly CapabilityActivated: "capabilityActivated";
39
+ readonly CapabilitySuppressed: "capabilitySuppressed";
40
+ readonly CastInterrupted: "castInterrupted";
41
+ };
42
+ export type TraceKind = typeof TraceKinds[keyof typeof TraceKinds];
43
+ export type AllKinds = CommandKind | TraceKind;
44
+ export declare const MoveModes: {
45
+ readonly Walk: "walk";
46
+ readonly Run: "run";
47
+ readonly Sprint: "sprint";
48
+ readonly Crawl: "crawl";
49
+ readonly Hover: "hover";
50
+ };
51
+ export type MoveMode = typeof MoveModes[keyof typeof MoveModes];
52
+ export declare const DefenceModes: {
53
+ readonly None: "none";
54
+ readonly Block: "block";
55
+ readonly Parry: "parry";
56
+ readonly Dodge: "dodge";
57
+ };
58
+ export type DefenceMode = typeof DefenceModes[keyof typeof DefenceModes];
59
+ export declare const EngageModes: {
60
+ readonly None: "none";
61
+ readonly Strike: "strike";
62
+ };
63
+ export type EngageMode = typeof EngageModes[keyof typeof EngageModes];
64
+ export type HitArea = BodyRegion | BodySegmentId;
@@ -0,0 +1,56 @@
1
+ // src/sim/kinds.ts
2
+ /** Commands are player/AI intentions */
3
+ export const CommandKinds = {
4
+ Move: "move",
5
+ Attack: "attack",
6
+ AttackNearest: "attackNearest",
7
+ Defend: "defend",
8
+ Grapple: "grapple",
9
+ BreakGrapple: "breakGrapple",
10
+ BreakBind: "breakBind", // Phase 2C
11
+ Shoot: "shoot", // Phase 3
12
+ Treat: "treat", // Phase 9
13
+ SetProne: "setProne",
14
+ Activate: "activate", // Phase 12: use a capability source effect
15
+ };
16
+ /** Trace events are engine observations */
17
+ export const TraceKinds = {
18
+ TickStart: "tickStart",
19
+ TickEnd: "tickEnd",
20
+ Intent: "intent",
21
+ Move: "move",
22
+ Injury: "injury",
23
+ Attack: "attack",
24
+ AttackAttempt: "attackAttempt",
25
+ Grapple: "grapple",
26
+ KO: "ko",
27
+ Death: "death",
28
+ WeaponBind: "weaponBind", // Phase 2C
29
+ WeaponBindBreak: "weaponBindBreak", // Phase 2C
30
+ ProjectileHit: "projectileHit", // Phase 3
31
+ MoraleRoute: "moraleRoute", // Phase 5
32
+ MoraleRally: "moraleRally", // Phase 18
33
+ Fracture: "fracture", // Phase 9
34
+ TreatmentApplied: "treatmentApplied", // Phase 9
35
+ BlastHit: "blastHit", // Phase 10
36
+ CapabilityActivated: "capabilityActivated", // Phase 12
37
+ CapabilitySuppressed: "capabilitySuppressed", // Phase 12
38
+ CastInterrupted: "castInterrupted", // Phase 12
39
+ };
40
+ export const MoveModes = {
41
+ Walk: "walk",
42
+ Run: "run",
43
+ Sprint: "sprint",
44
+ Crawl: "crawl",
45
+ Hover: "hover",
46
+ };
47
+ export const DefenceModes = {
48
+ None: "none",
49
+ Block: "block",
50
+ Parry: "parry",
51
+ Dodge: "dodge",
52
+ };
53
+ export const EngageModes = {
54
+ None: "none",
55
+ Strike: "strike",
56
+ };
@@ -0,0 +1,50 @@
1
+ import type { Entity } from "./entity.js";
2
+ /** Effective Δv at which a target begins to stumble (0.5 m/s in SCALE.mps units). */
3
+ export declare const STAGGER_THRESHOLD_mps: number;
4
+ /** Effective Δv at which a target is knocked prone (2.0 m/s in SCALE.mps units). */
5
+ export declare const PRONE_THRESHOLD_mps: number;
6
+ /** Ticks of reduced-action window while staggered. */
7
+ export declare const STAGGER_TICKS = 3;
8
+ export interface KnockbackResult {
9
+ /** Raw impulse in real Newton-seconds (diagnostic; not used in further simulation math). */
10
+ impulse_Ns: number;
11
+ /** Effective velocity delta in SCALE.mps units, after stability reduction. Applied to target. */
12
+ knockback_v: number;
13
+ /** True when effective_v ≥ STAGGER_THRESHOLD and entity is not knocked prone. */
14
+ staggered: boolean;
15
+ /** True when effective_v ≥ PRONE_THRESHOLD. Implies staggered. */
16
+ prone: boolean;
17
+ }
18
+ /**
19
+ * Derive knockback result from an impact.
20
+ *
21
+ * Physics derivation:
22
+ * impulse = sqrt(2 × E × m_eff) [N·s — same as p = m_eff × v_head]
23
+ * raw_Δv = impulse / m_target [m/s]
24
+ * effective_v = raw_Δv × (1 − stabilityQ)
25
+ *
26
+ * Calibration:
27
+ * 5.56 mm (4 g, 1760 J, 75 kg target): Δv ≈ 0.05 m/s — negligible ✓
28
+ * 12-gauge slug (28 g, 2100 J, 75 kg): Δv ≈ 0.25 m/s raw — stagger on low-stability target
29
+ * Large creature kick (50 kg eff, 400 J, 75 kg): Δv ≈ 2.67 m/s raw — prone on low-stability
30
+ *
31
+ * @param energy_J Impact energy (raw joules; SCALE.J = 1).
32
+ * @param massEff_kg Effective striking mass in SCALE.kg units (wpn + body fraction, or projectile).
33
+ * @param target Target entity (reads mass_kg and stabilityQ).
34
+ */
35
+ export declare function computeKnockback(energy_J: number, massEff_kg: number, target: Entity): KnockbackResult;
36
+ /**
37
+ * Apply knockback result to an entity.
38
+ *
39
+ * - Adds `result.knockback_v` to the entity's velocity in the hit direction.
40
+ * - Sets `condition.prone = true` when result.prone.
41
+ * - Sets `action.staggerTicks = STAGGER_TICKS` when result.staggered.
42
+ *
43
+ * @param entity Target entity (mutated in-place).
44
+ * @param result Result from `computeKnockback`.
45
+ * @param dir Direction from attacker to target in SCALE.m coordinates (unnormalised).
46
+ */
47
+ export declare function applyKnockback(entity: Entity, result: KnockbackResult, dir: {
48
+ dx: number;
49
+ dy: number;
50
+ }): void;
@@ -0,0 +1,82 @@
1
+ // src/sim/knockback.ts — Phase 26: Momentum Transfer & Knockback
2
+ //
3
+ // Impulse-momentum model for melee and ranged impacts.
4
+ // Physics: impulse = sqrt(2 × E × m_eff); Δv = impulse / m_target
5
+ // Stability modifier reduces effective knockback before threshold checks.
6
+ //
7
+ // Math.sqrt is used for impulse calculation — acceptable per project convention
8
+ // (already used in kernel.ts for velocity magnitude computations).
9
+ import { SCALE, qMul } from "../units.js";
10
+ import { normaliseDirCheapQ } from "./vec3.js";
11
+ import { mulDiv } from "../units.js";
12
+ // ── Constants ─────────────────────────────────────────────────────────────────
13
+ /** Effective Δv at which a target begins to stumble (0.5 m/s in SCALE.mps units). */
14
+ export const STAGGER_THRESHOLD_mps = Math.trunc(0.5 * SCALE.mps); // 5 000
15
+ /** Effective Δv at which a target is knocked prone (2.0 m/s in SCALE.mps units). */
16
+ export const PRONE_THRESHOLD_mps = Math.trunc(2.0 * SCALE.mps); // 20 000
17
+ /** Ticks of reduced-action window while staggered. */
18
+ export const STAGGER_TICKS = 3;
19
+ // ── computeKnockback ──────────────────────────────────────────────────────────
20
+ /**
21
+ * Derive knockback result from an impact.
22
+ *
23
+ * Physics derivation:
24
+ * impulse = sqrt(2 × E × m_eff) [N·s — same as p = m_eff × v_head]
25
+ * raw_Δv = impulse / m_target [m/s]
26
+ * effective_v = raw_Δv × (1 − stabilityQ)
27
+ *
28
+ * Calibration:
29
+ * 5.56 mm (4 g, 1760 J, 75 kg target): Δv ≈ 0.05 m/s — negligible ✓
30
+ * 12-gauge slug (28 g, 2100 J, 75 kg): Δv ≈ 0.25 m/s raw — stagger on low-stability target
31
+ * Large creature kick (50 kg eff, 400 J, 75 kg): Δv ≈ 2.67 m/s raw — prone on low-stability
32
+ *
33
+ * @param energy_J Impact energy (raw joules; SCALE.J = 1).
34
+ * @param massEff_kg Effective striking mass in SCALE.kg units (wpn + body fraction, or projectile).
35
+ * @param target Target entity (reads mass_kg and stabilityQ).
36
+ */
37
+ export function computeKnockback(energy_J, massEff_kg, target) {
38
+ const zero = { impulse_Ns: 0, knockback_v: 0, staggered: false, prone: false };
39
+ if (energy_J <= 0 || massEff_kg <= 0)
40
+ return zero;
41
+ // Convert to real units for physics calculation
42
+ const massEff_real = massEff_kg / SCALE.kg; // real kg
43
+ const massTarget_real = Math.max(0.001, target.attributes.morphology.mass_kg / SCALE.kg); // real kg
44
+ // impulse = sqrt(2 × E × m_eff) [N·s]
45
+ const impulse_Ns = Math.sqrt(2 * energy_J * massEff_real);
46
+ // raw Δv in SCALE.mps units
47
+ const raw_v = Math.trunc((impulse_Ns / massTarget_real) * SCALE.mps);
48
+ // Stability modifier: higher stability → less effective knockback
49
+ const stabilityQ = target.attributes.control.stability;
50
+ const knockback_v = Math.trunc(qMul(raw_v, SCALE.Q - stabilityQ));
51
+ const prone = knockback_v >= PRONE_THRESHOLD_mps;
52
+ const staggered = !prone && knockback_v >= STAGGER_THRESHOLD_mps;
53
+ return { impulse_Ns, knockback_v, staggered, prone };
54
+ }
55
+ // ── applyKnockback ────────────────────────────────────────────────────────────
56
+ /**
57
+ * Apply knockback result to an entity.
58
+ *
59
+ * - Adds `result.knockback_v` to the entity's velocity in the hit direction.
60
+ * - Sets `condition.prone = true` when result.prone.
61
+ * - Sets `action.staggerTicks = STAGGER_TICKS` when result.staggered.
62
+ *
63
+ * @param entity Target entity (mutated in-place).
64
+ * @param result Result from `computeKnockback`.
65
+ * @param dir Direction from attacker to target in SCALE.m coordinates (unnormalised).
66
+ */
67
+ export function applyKnockback(entity, result, dir) {
68
+ if (result.knockback_v === 0)
69
+ return;
70
+ if (dir.dx === 0 && dir.dy === 0)
71
+ return;
72
+ // Cheap Chebyshev normalisation — no float division in the hot path
73
+ const dirQ = normaliseDirCheapQ({ x: dir.dx, y: dir.dy, z: 0 });
74
+ entity.velocity_mps.x += mulDiv(result.knockback_v, dirQ.x, SCALE.Q);
75
+ entity.velocity_mps.y += mulDiv(result.knockback_v, dirQ.y, SCALE.Q);
76
+ if (result.prone) {
77
+ entity.condition.prone = true;
78
+ }
79
+ if (result.staggered) {
80
+ entity.action.staggerTicks = Math.max(entity.action.staggerTicks ?? 0, STAGGER_TICKS);
81
+ }
82
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Phase 32B — Multi-Limb Granularity
3
+ *
4
+ * Per-limb state for entities whose body plan has multiple manipulation segments
5
+ * (octopoids, arachnids, multi-armed creatures). Integrates with grapple resolution
6
+ * by reducing effective contest force when limbs are severed or fatigued.
7
+ *
8
+ * Backward-compatible: entities without a limbStates field behave identically to
9
+ * the existing single-pool grapple model.
10
+ */
11
+ import { type Q, type I32 } from "../units.js";
12
+ import type { BodyPlan } from "./bodyplan.js";
13
+ import type { InjuryState } from "./injury.js";
14
+ export interface LimbState {
15
+ /** Matches a BodySegment.id in the entity's body plan. */
16
+ segmentId: string;
17
+ /** Current grip quality on this limb (0 = no grip, q(1.0) = full grip). */
18
+ gripQ: Q;
19
+ /** Entity id currently held by this limb; 0 = limb is free. */
20
+ engagedWith: number;
21
+ /** Float fatigue accumulator [same unit as energy_J]. Sub-unit precision. */
22
+ fatigueJ: number;
23
+ }
24
+ /**
25
+ * Build initial LimbState[] from a BodyPlan.
26
+ * Only segments with `manipulationRole === "primary"` are included.
27
+ * Returns an empty array if no such segments exist.
28
+ */
29
+ export declare function buildLimbStates(plan: BodyPlan): LimbState[];
30
+ /**
31
+ * Compute the effective force multiplier given current limb states and injury.
32
+ *
33
+ * A limb is excluded when its body segment has structural damage at SCALE.Q
34
+ * (fully destroyed / severed). The result is:
35
+ * (activeLimbs / totalLimbs) × averageGripQ
36
+ *
37
+ * Returns q(1.0) when limbStates is empty (degenerate case, caller should not
38
+ * invoke this function if limb count is zero).
39
+ */
40
+ export declare function effectiveLimbForceMul(limbStates: LimbState[], injury: InjuryState): Q;
41
+ /**
42
+ * Tick fatigue for engaged limbs. Each active engaged limb drains
43
+ * `peakForce_N / limbCount` energy per second (sub-unit float accumulator).
44
+ *
45
+ * The accumulated fatigueJ is informational for host use; it does not directly
46
+ * reduce gripQ here (that is a host-side policy decision, e.g. after a threshold).
47
+ */
48
+ export declare function stepLimbFatigue(limbStates: LimbState[], peakForce_N: I32, delta_s: number): void;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Phase 32B — Multi-Limb Granularity
3
+ *
4
+ * Per-limb state for entities whose body plan has multiple manipulation segments
5
+ * (octopoids, arachnids, multi-armed creatures). Integrates with grapple resolution
6
+ * by reducing effective contest force when limbs are severed or fatigued.
7
+ *
8
+ * Backward-compatible: entities without a limbStates field behave identically to
9
+ * the existing single-pool grapple model.
10
+ */
11
+ import { q, SCALE, clampQ, qMul, mulDiv } from "../units.js";
12
+ // ── Limb initialisation ───────────────────────────────────────────────────────
13
+ /**
14
+ * Build initial LimbState[] from a BodyPlan.
15
+ * Only segments with `manipulationRole === "primary"` are included.
16
+ * Returns an empty array if no such segments exist.
17
+ */
18
+ export function buildLimbStates(plan) {
19
+ return plan.segments
20
+ .filter(s => s.manipulationRole === "primary")
21
+ .map(s => ({
22
+ segmentId: s.id,
23
+ gripQ: q(0),
24
+ engagedWith: 0,
25
+ fatigueJ: 0,
26
+ }));
27
+ }
28
+ // ── Force contribution ────────────────────────────────────────────────────────
29
+ /**
30
+ * Compute the effective force multiplier given current limb states and injury.
31
+ *
32
+ * A limb is excluded when its body segment has structural damage at SCALE.Q
33
+ * (fully destroyed / severed). The result is:
34
+ * (activeLimbs / totalLimbs) × averageGripQ
35
+ *
36
+ * Returns q(1.0) when limbStates is empty (degenerate case, caller should not
37
+ * invoke this function if limb count is zero).
38
+ */
39
+ export function effectiveLimbForceMul(limbStates, injury) {
40
+ const total = limbStates.length;
41
+ if (total === 0)
42
+ return q(1.0);
43
+ let activeCount = 0;
44
+ let gripSum = 0;
45
+ for (const ls of limbStates) {
46
+ const region = injury.byRegion?.[ls.segmentId];
47
+ const structDmg = region?.structuralDamage ?? 0;
48
+ if (structDmg >= SCALE.Q)
49
+ continue; // severed / destroyed
50
+ activeCount++;
51
+ gripSum += ls.gripQ;
52
+ }
53
+ if (activeCount === 0)
54
+ return q(0);
55
+ const activeFrac = mulDiv(activeCount, SCALE.Q, total); // e.g. 6/8 = q(0.75)
56
+ const avgGrip = Math.trunc(gripSum / activeCount);
57
+ // If all grips are 0 (entity not currently grappling) return activeFrac directly
58
+ if (avgGrip === 0)
59
+ return activeFrac;
60
+ return clampQ(qMul(activeFrac, avgGrip), q(0), q(1.0));
61
+ }
62
+ // ── Fatigue accumulation ──────────────────────────────────────────────────────
63
+ /**
64
+ * Tick fatigue for engaged limbs. Each active engaged limb drains
65
+ * `peakForce_N / limbCount` energy per second (sub-unit float accumulator).
66
+ *
67
+ * The accumulated fatigueJ is informational for host use; it does not directly
68
+ * reduce gripQ here (that is a host-side policy decision, e.g. after a threshold).
69
+ */
70
+ export function stepLimbFatigue(limbStates, peakForce_N, delta_s) {
71
+ const activeEngaged = limbStates.filter(ls => ls.engagedWith !== 0);
72
+ if (activeEngaged.length === 0)
73
+ return;
74
+ const drainPerLimb = (peakForce_N / limbStates.length) * delta_s;
75
+ for (const ls of activeEngaged) {
76
+ ls.fatigueJ += drainPerLimb;
77
+ }
78
+ }
@@ -0,0 +1,32 @@
1
+ import { type Q } from "../units.js";
2
+ import type { TechCapability } from "./tech.js";
3
+ /**
4
+ * Capability tier of the equipment used during treatment.
5
+ * Passed on TreatCommand; the kernel scales effectiveness accordingly.
6
+ */
7
+ export type MedicalTier = "none" | "bandage" | "surgicalKit" | "autodoc" | "nanomedicine";
8
+ /** Ordinal rank used for minimum-tier comparisons. Higher = more capable. */
9
+ export declare const TIER_RANK: Record<MedicalTier, number>;
10
+ /**
11
+ * Effectiveness multiplier per tier.
12
+ * Applied as: reduction = BASE_RATE × TIER_MUL × (medSkill.treatmentRateMul / SCALE.Q)
13
+ */
14
+ export declare const TIER_MUL: Record<MedicalTier, Q>;
15
+ /**
16
+ * Available treatment actions.
17
+ *
18
+ * tourniquet — zeroes bleedingRate in one region immediately; requires ≥ bandage tier
19
+ * bandage — reduces bleedingRate per tick; requires ≥ bandage tier
20
+ * surgery — reduces structuralDamage per tick; clears fracture when healed; requires ≥ surgicalKit
21
+ * fluidReplacement — restores fluidLoss per tick; requires ≥ autodoc
22
+ */
23
+ export type MedicalAction = "tourniquet" | "bandage" | "surgery" | "fluidReplacement";
24
+ /** Minimum tier required for each action. */
25
+ export declare const ACTION_MIN_TIER: Record<MedicalAction, MedicalTier>;
26
+ /**
27
+ * Phase 11: TechCapability that must be available in TechContext to use equipment at this tier.
28
+ * When ctx.techCtx is provided and the capability is absent, treatment is blocked.
29
+ *
30
+ * Tiers not listed here have no technology requirement (they work in any era).
31
+ */
32
+ export declare const TIER_TECH_REQ: Partial<Record<MedicalTier, TechCapability>>;
@@ -0,0 +1,33 @@
1
+ // src/sim/medical.ts — Phase 9: medical treatment types; Phase 11: tech gating
2
+ import { q } from "../units.js";
3
+ /** Ordinal rank used for minimum-tier comparisons. Higher = more capable. */
4
+ export const TIER_RANK = {
5
+ none: 0, bandage: 1, surgicalKit: 2, autodoc: 3, nanomedicine: 4,
6
+ };
7
+ /**
8
+ * Effectiveness multiplier per tier.
9
+ * Applied as: reduction = BASE_RATE × TIER_MUL × (medSkill.treatmentRateMul / SCALE.Q)
10
+ */
11
+ export const TIER_MUL = {
12
+ none: q(0),
13
+ bandage: q(0.50),
14
+ surgicalKit: q(0.80),
15
+ autodoc: q(1.00),
16
+ nanomedicine: q(1.20),
17
+ };
18
+ /** Minimum tier required for each action. */
19
+ export const ACTION_MIN_TIER = {
20
+ tourniquet: "bandage",
21
+ bandage: "bandage",
22
+ surgery: "surgicalKit",
23
+ fluidReplacement: "autodoc",
24
+ };
25
+ /**
26
+ * Phase 11: TechCapability that must be available in TechContext to use equipment at this tier.
27
+ * When ctx.techCtx is provided and the capability is absent, treatment is blocked.
28
+ *
29
+ * Tiers not listed here have no technology requirement (they work in any era).
30
+ */
31
+ export const TIER_TECH_REQ = {
32
+ nanomedicine: "NanomedicalRepair",
33
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Phase 5 — Morale and Psychological State
3
+ *
4
+ * Pure fixed-point functions. No Math.random(), no Entity imports.
5
+ * All randomness via eventSeed + caller-supplied seed.
6
+ */
7
+ import type { Q } from "../units.js";
8
+ /** Fear added per tick of active suppression (incoming near-miss fire). */
9
+ export declare const FEAR_PER_SUPPRESSION_TICK: Q;
10
+ /** Fear added when a nearby ally is killed in the same tick. */
11
+ export declare const FEAR_FOR_ALLY_DEATH: Q;
12
+ /** Multiplier: fear added per tick = shock × this. */
13
+ export declare const FEAR_INJURY_MUL: Q;
14
+ /** Fear added per tick when enemies outnumber allies in awareness radius. */
15
+ export declare const FEAR_OUTNUMBERED: Q;
16
+ /** Fear added to a defender per surprise attack (attacker undetected). */
17
+ export declare const FEAR_SURPRISE: Q;
18
+ /** Fear added per tick when >50% of own team is already routing. */
19
+ export declare const FEAR_ROUTING_CASCADE: Q;
20
+ /** Additional fear decay per leader within AURA_RADIUS_m. */
21
+ export declare const LEADER_AURA_FEAR_REDUCTION: Q;
22
+ /** Additional fear decay per standard-bearer within AURA_RADIUS_m. */
23
+ export declare const BANNER_AURA_FEAR_REDUCTION: Q;
24
+ /** Radius within which leader/banner auras apply (SCALE.m units). */
25
+ export declare const AURA_RADIUS_m: number;
26
+ /** Ticks of attack suppression after recovering from routing. */
27
+ export declare const RALLY_COOLDOWN_TICKS = 60;
28
+ /** Base fear decay per tick, multiplied by distressTolerance. */
29
+ export declare const BASE_DECAY: Q;
30
+ /** Additional fear decay per nearby living ally (cohesion effect). */
31
+ export declare const ALLY_COHESION: Q;
32
+ /** Additional fear decay per ally in a tight formation (Phase 32E). */
33
+ export declare const FORMATION_COHESION: Q;
34
+ /**
35
+ * Fear decay rate per tick.
36
+ * Scales with distressTolerance (stoic entities recover faster)
37
+ * and with nearby living ally count (cohesion effect).
38
+ *
39
+ * Returns a Q value to subtract from fearQ each tick.
40
+ */
41
+ export declare function fearDecayPerTick(distressTolerance: Q, nearbyAllyCount: number, formationAllyCount?: number): Q;
42
+ /**
43
+ * Routing threshold — minimum fear to trigger retreat behaviour.
44
+ * Higher distressTolerance → bolder → threshold is higher.
45
+ *
46
+ * Range: q(0.50) at tolerance=0 → q(0.80) at tolerance=1.
47
+ */
48
+ export declare function moraleThreshold(distressTolerance: Q): Q;
49
+ /**
50
+ * Whether an entity is currently routing.
51
+ */
52
+ export declare function isRouting(fearQ: Q, distressTolerance: Q): boolean;
53
+ /**
54
+ * Effective pain level from shock (0..1), reduced by distress tolerance.
55
+ *
56
+ * painLevel = shock × (1 − distressTolerance)
57
+ *
58
+ * Returns a Q value representing probability that pain blocks voluntary action.
59
+ */
60
+ export declare function painLevel(shock: Q, distressTolerance: Q): Q;
61
+ /**
62
+ * Deterministic pain suppression check.
63
+ * Returns true if pain prevents the entity from initiating an attack this tick.
64
+ *
65
+ * @param seed - Caller supplies eventSeed(..., 0xPA15); value drives the roll.
66
+ * @param shock - Entity's current shock level.
67
+ * @param distressTolerance - Entity's pain tolerance.
68
+ */
69
+ export declare function painBlocksAction(seed: number, shock: Q, distressTolerance: Q): boolean;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Phase 5 — Morale and Psychological State
3
+ *
4
+ * Pure fixed-point functions. No Math.random(), no Entity imports.
5
+ * All randomness via eventSeed + caller-supplied seed.
6
+ */
7
+ import { SCALE, q, clampQ, qMul } from "../units.js";
8
+ // ── Fear increment constants ──────────────────────────────────────────────────
9
+ /** Fear added per tick of active suppression (incoming near-miss fire). */
10
+ export const FEAR_PER_SUPPRESSION_TICK = q(0.020);
11
+ /** Fear added when a nearby ally is killed in the same tick. */
12
+ export const FEAR_FOR_ALLY_DEATH = q(0.150);
13
+ /** Multiplier: fear added per tick = shock × this. */
14
+ export const FEAR_INJURY_MUL = q(0.012);
15
+ /** Fear added per tick when enemies outnumber allies in awareness radius. */
16
+ export const FEAR_OUTNUMBERED = q(0.010);
17
+ /** Fear added to a defender per surprise attack (attacker undetected). */
18
+ export const FEAR_SURPRISE = q(0.080);
19
+ /** Fear added per tick when >50% of own team is already routing. */
20
+ export const FEAR_ROUTING_CASCADE = q(0.030);
21
+ // ── Phase 5 extension constants ───────────────────────────────────────────────
22
+ /** Additional fear decay per leader within AURA_RADIUS_m. */
23
+ export const LEADER_AURA_FEAR_REDUCTION = q(0.015);
24
+ /** Additional fear decay per standard-bearer within AURA_RADIUS_m. */
25
+ export const BANNER_AURA_FEAR_REDUCTION = q(0.010);
26
+ /** Radius within which leader/banner auras apply (SCALE.m units). */
27
+ export const AURA_RADIUS_m = Math.trunc(20 * SCALE.m);
28
+ /** Ticks of attack suppression after recovering from routing. */
29
+ export const RALLY_COOLDOWN_TICKS = 60;
30
+ // ── Fear decay constants ──────────────────────────────────────────────────────
31
+ /** Base fear decay per tick, multiplied by distressTolerance. */
32
+ export const BASE_DECAY = q(0.008);
33
+ /** Additional fear decay per nearby living ally (cohesion effect). */
34
+ export const ALLY_COHESION = q(0.002);
35
+ /** Additional fear decay per ally in a tight formation (Phase 32E). */
36
+ export const FORMATION_COHESION = q(0.003);
37
+ // ── Pure functions ────────────────────────────────────────────────────────────
38
+ /**
39
+ * Fear decay rate per tick.
40
+ * Scales with distressTolerance (stoic entities recover faster)
41
+ * and with nearby living ally count (cohesion effect).
42
+ *
43
+ * Returns a Q value to subtract from fearQ each tick.
44
+ */
45
+ export function fearDecayPerTick(distressTolerance, nearbyAllyCount, formationAllyCount) {
46
+ const base = qMul(BASE_DECAY, distressTolerance);
47
+ const cohesion = Math.min(nearbyAllyCount * ALLY_COHESION, q(0.020)); // cap at q(0.020)
48
+ const formation = formationAllyCount
49
+ ? Math.min(formationAllyCount * FORMATION_COHESION, q(0.015)) // cap at q(0.015)
50
+ : 0;
51
+ return clampQ(base + cohesion + formation, 0, q(0.040));
52
+ }
53
+ /**
54
+ * Routing threshold — minimum fear to trigger retreat behaviour.
55
+ * Higher distressTolerance → bolder → threshold is higher.
56
+ *
57
+ * Range: q(0.50) at tolerance=0 → q(0.80) at tolerance=1.
58
+ */
59
+ export function moraleThreshold(distressTolerance) {
60
+ return clampQ(q(0.50) + qMul(distressTolerance, q(0.30)), q(0.50), q(0.80));
61
+ }
62
+ /**
63
+ * Whether an entity is currently routing.
64
+ */
65
+ export function isRouting(fearQ, distressTolerance) {
66
+ return fearQ >= moraleThreshold(distressTolerance);
67
+ }
68
+ /**
69
+ * Effective pain level from shock (0..1), reduced by distress tolerance.
70
+ *
71
+ * painLevel = shock × (1 − distressTolerance)
72
+ *
73
+ * Returns a Q value representing probability that pain blocks voluntary action.
74
+ */
75
+ export function painLevel(shock, distressTolerance) {
76
+ // shock × (SCALE.Q - distressTolerance) / SCALE.Q
77
+ return clampQ(qMul(shock, (SCALE.Q - distressTolerance)), 0, SCALE.Q);
78
+ }
79
+ /**
80
+ * Deterministic pain suppression check.
81
+ * Returns true if pain prevents the entity from initiating an attack this tick.
82
+ *
83
+ * @param seed - Caller supplies eventSeed(..., 0xPA15); value drives the roll.
84
+ * @param shock - Entity's current shock level.
85
+ * @param distressTolerance - Entity's pain tolerance.
86
+ */
87
+ export function painBlocksAction(seed, shock, distressTolerance) {
88
+ const pain = painLevel(shock, distressTolerance);
89
+ if (pain <= 0)
90
+ return false;
91
+ return (seed % SCALE.Q) < pain;
92
+ }