@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,598 @@
1
+ import { SCALE, q, clampQ, qMul, mulDiv, to } from "./units.js";
2
+ import { DamageChannel, channelMask } from "./channels.js";
3
+ import { ALL_REGIONS, DEFAULT_REGION_WEIGHTS, weightedMean01 } from "./sim/body.js";
4
+ export const DEFAULT_CARRY_RULES = {
5
+ capacityFactor: q(0.25),
6
+ bulkToMassFactor: q(0.06),
7
+ };
8
+ export function computeLoadoutTotals(loadout, armourIsWorn = true) {
9
+ let mass = 0;
10
+ let bulk = 0;
11
+ let wornMass = 0;
12
+ let wornBulk = 0;
13
+ for (const it of loadout.items) {
14
+ mass += it.mass_kg;
15
+ bulk = (bulk + it.bulk) | 0;
16
+ if (armourIsWorn && it.kind === "armour") {
17
+ wornMass += it.mass_kg;
18
+ wornBulk = (wornBulk + it.bulk) | 0;
19
+ }
20
+ }
21
+ return {
22
+ carriedMass_kg: mass,
23
+ carriedBulk: bulk,
24
+ wornMass_kg: wornMass,
25
+ wornBulk: wornBulk,
26
+ carriedMassFracOfBody: q(0),
27
+ };
28
+ }
29
+ export function deriveCarryCapacityMass_kg(a, rules = DEFAULT_CARRY_RULES) {
30
+ const peakForceScaled = a.performance.peakForce_N;
31
+ const numerator = BigInt(peakForceScaled) * BigInt(SCALE.kg) * BigInt(rules.capacityFactor);
32
+ const denom = BigInt(SCALE.N) * BigInt(SCALE.Q) * 9810n;
33
+ const kgScaled = Number(numerator / denom);
34
+ return Math.max(1, kgScaled);
35
+ }
36
+ export function computeEncumbrance(a, loadout, rules = DEFAULT_CARRY_RULES) {
37
+ const totals = computeLoadoutTotals(loadout);
38
+ const bodyMass = Math.max(1, a.morphology.mass_kg);
39
+ totals.carriedMassFracOfBody = mulDiv(totals.carriedMass_kg * SCALE.Q, SCALE.kg, bodyMass);
40
+ const capacity_kg = Math.max(1, deriveCarryCapacityMass_kg(a, rules));
41
+ const massRatio = mulDiv(totals.carriedMass_kg * SCALE.Q, 1, capacity_kg);
42
+ const bulkAbove1 = Math.max(0, totals.carriedBulk - SCALE.Q);
43
+ const bulkTerm = qMul(bulkAbove1, rules.bulkToMassFactor);
44
+ const r = clampQ((massRatio + bulkTerm), 0, 5 * SCALE.Q);
45
+ const penalties = encumbranceCurve(r, a);
46
+ return { totals, penalties };
47
+ }
48
+ function encumbranceCurve(r, a) {
49
+ const overloaded = r > q(1.5);
50
+ const speedMul = piecewiseMul(r, q(1.0), q(0.92), q(0.78), q(0.55));
51
+ const accelMul = piecewiseMul(r, q(1.0), q(0.88), q(0.70), q(0.45));
52
+ const jumpMul = piecewiseMul(r, q(1.0), q(0.90), q(0.68), q(0.40));
53
+ const baseDemand = piecewiseMul(r, q(1.0), q(1.10), q(1.30), q(1.65));
54
+ const energyDemandMul = clampQ(qMul(baseDemand, a.resilience.fatigueRate), q(0.5), q(3.0));
55
+ const controlMul = piecewiseMul(r, q(1.0), q(0.96), q(0.88), q(0.75));
56
+ const stabilityMul = piecewiseMul(r, q(1.0), q(0.94), q(0.82), q(0.65));
57
+ return { speedMul, accelMul, jumpMul, energyDemandMul, controlMul, stabilityMul, encumbranceRatio: r, overloaded };
58
+ }
59
+ function piecewiseMul(r, a, b, c, d) {
60
+ const r05 = q(0.5), r10 = q(1.0), r15 = q(1.5);
61
+ if (r <= r05)
62
+ return a;
63
+ if (r <= r10) {
64
+ const t = mulDiv((r - r05), SCALE.Q, (r10 - r05));
65
+ return (a + mulDiv((b - a), t, SCALE.Q));
66
+ }
67
+ if (r <= r15) {
68
+ const t = mulDiv((r - r10), SCALE.Q, (r15 - r10));
69
+ return (b + mulDiv((c - b), t, SCALE.Q));
70
+ }
71
+ return d;
72
+ }
73
+ function emptyCoverage() {
74
+ const out = {};
75
+ for (const r of ALL_REGIONS)
76
+ out[r] = q(0);
77
+ return out;
78
+ }
79
+ export function deriveArmourProfile(loadout, armourState) {
80
+ const items = [...loadout.items].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
81
+ let protects = 0;
82
+ let protectedMul = q(1.0);
83
+ let mobilityMul = q(1.0);
84
+ let fatigueMul = q(1.0);
85
+ const coverageByRegion = emptyCoverage();
86
+ let resist_J = 0;
87
+ let reflectivity = q(0);
88
+ const channelResistMul = {};
89
+ for (const it of items) {
90
+ if (it.kind !== "armour")
91
+ continue;
92
+ protects |= it.protects;
93
+ protectedMul = qMul(protectedMul, it.protectedDamageMul);
94
+ mobilityMul = qMul(mobilityMul, it.mobilityMul ?? q(1.0));
95
+ fatigueMul = qMul(fatigueMul, it.fatigueMul ?? q(1.0));
96
+ for (const r of ALL_REGIONS) {
97
+ const c = it.coverageByRegion[r] ?? q(0);
98
+ const oneMinus = q(1.0) - c;
99
+ coverageByRegion[r] = (q(1.0) - qMul(q(1.0) - coverageByRegion[r], oneMinus));
100
+ }
101
+ // Phase 11C: ablative — use remaining resist if tracked, else full
102
+ const effectiveResist = (it.ablative && armourState?.has(it.id))
103
+ ? armourState.get(it.id).resistRemaining_J
104
+ : it.resist_J;
105
+ resist_J += effectiveResist;
106
+ // Phase 11C: reflectivity — take the maximum across all items
107
+ if (it.reflectivity && it.reflectivity > reflectivity) {
108
+ reflectivity = it.reflectivity;
109
+ }
110
+ if (it.channelResistMul) {
111
+ for (const k of Object.keys(it.channelResistMul)) {
112
+ const ch = Number(k);
113
+ const mul = it.channelResistMul[ch];
114
+ channelResistMul[ch] = channelResistMul[ch] ? qMul(channelResistMul[ch], mul) : mul;
115
+ }
116
+ }
117
+ }
118
+ return {
119
+ protects,
120
+ coverageByRegion: coverageByRegion,
121
+ coverageOverall: weightedMean01(coverageByRegion, DEFAULT_REGION_WEIGHTS),
122
+ resist_J: Math.max(0, resist_J),
123
+ protectedDamageMul: clampQ(protectedMul, q(0.05), q(1.0)),
124
+ mobilityMul: clampQ(mobilityMul, q(0.30), q(1.0)),
125
+ fatigueMul: clampQ(fatigueMul, q(0.80), q(3.0)),
126
+ channelResistMul,
127
+ reflectivity,
128
+ };
129
+ }
130
+ export function findWeapon(loadout, weaponId) {
131
+ const weapons = loadout.items
132
+ .filter((x) => x.kind === "weapon");
133
+ if (weapons.length === 0)
134
+ return null;
135
+ if (!weaponId)
136
+ return weapons[0];
137
+ return weapons.find(w => w.id === weaponId) ?? weapons[0];
138
+ }
139
+ export function findRangedWeapon(loadout, weaponId) {
140
+ const ranged = loadout.items.filter((x) => x.kind === "ranged");
141
+ if (ranged.length === 0)
142
+ return null;
143
+ if (!weaponId)
144
+ return ranged[0];
145
+ return ranged.find(w => w.id === weaponId) ?? ranged[0];
146
+ }
147
+ export function findShield(loadout) {
148
+ return loadout.items.find(item => item?.kind === "shield");
149
+ }
150
+ export function findExoskeleton(loadout) {
151
+ return (loadout.items.find((it) => it.kind === "exoskeleton") ?? null);
152
+ }
153
+ /** Phase 11C: return the first Sensor in the loadout, or null. */
154
+ export function findSensor(loadout) {
155
+ return (loadout.items.find((it) => it.kind === "sensor") ?? null);
156
+ }
157
+ /**
158
+ * Phase 11: check that every item in a loadout is usable in the given TechContext.
159
+ * Returns an array of error messages; empty array means the loadout is valid.
160
+ */
161
+ export function validateLoadout(loadout, ctx) {
162
+ const errors = [];
163
+ for (const item of loadout.items) {
164
+ if (!item.requiredCapabilities)
165
+ continue;
166
+ for (const cap of item.requiredCapabilities) {
167
+ if (!ctx.available.has(cap)) {
168
+ errors.push(`"${item.id}" requires capability "${cap}"`);
169
+ }
170
+ }
171
+ }
172
+ return errors;
173
+ }
174
+ export function deriveWeaponHandling(w, ownerStature_m) {
175
+ const reach = w.reach_m ?? Math.trunc(ownerStature_m * 0.45);
176
+ return {
177
+ handedness: w.handedness ?? "oneHand",
178
+ momentArm_m: w.momentArm_m ?? Math.trunc(reach * 0.55),
179
+ handlingLoadMul: w.handlingLoadMul ?? q(1.0),
180
+ };
181
+ }
182
+ export const STARTER_WEAPONS = [
183
+ {
184
+ id: "wpn_club",
185
+ kind: "weapon",
186
+ name: "Wooden club",
187
+ mass_kg: Math.round(1.2 * SCALE.kg),
188
+ bulk: q(1.4),
189
+ reach_m: Math.round(0.7 * SCALE.m),
190
+ handedness: "oneHand",
191
+ momentArm_m: Math.round(0.45 * SCALE.m),
192
+ handlingMul: q(1.10),
193
+ strikeEffectiveMassFrac: q(0.18),
194
+ strikeSpeedMul: q(0.95),
195
+ damage: {
196
+ surfaceFrac: q(0.35),
197
+ internalFrac: q(0.20),
198
+ structuralFrac: q(0.45),
199
+ bleedFactor: q(0.25),
200
+ penetrationBias: q(0.10),
201
+ },
202
+ },
203
+ {
204
+ id: "wpn_knife",
205
+ kind: "weapon",
206
+ name: "Knife",
207
+ mass_kg: Math.round(0.3 * SCALE.kg),
208
+ bulk: q(1.1),
209
+ reach_m: Math.round(0.2 * SCALE.m),
210
+ handedness: "oneHand",
211
+ momentArm_m: Math.round(0.18 * SCALE.m),
212
+ handlingMul: q(0.85),
213
+ strikeEffectiveMassFrac: q(0.10),
214
+ strikeSpeedMul: q(1.05),
215
+ damage: {
216
+ surfaceFrac: q(0.30),
217
+ internalFrac: q(0.60),
218
+ structuralFrac: q(0.10),
219
+ bleedFactor: q(0.95),
220
+ penetrationBias: q(0.85),
221
+ },
222
+ },
223
+ {
224
+ id: "wpn_longsword",
225
+ kind: "weapon",
226
+ name: "Longsword",
227
+ mass_kg: Math.round(1.5 * SCALE.kg),
228
+ bulk: q(1.5),
229
+ reach_m: Math.round(0.90 * SCALE.m),
230
+ handedness: "twoHand",
231
+ momentArm_m: Math.round(0.55 * SCALE.m),
232
+ handlingMul: q(1.05),
233
+ strikeEffectiveMassFrac: q(0.15),
234
+ strikeSpeedMul: q(1.00),
235
+ readyTime_s: to.s(0.75),
236
+ damage: {
237
+ surfaceFrac: q(0.35),
238
+ internalFrac: q(0.45),
239
+ structuralFrac: q(0.20),
240
+ bleedFactor: q(0.70),
241
+ penetrationBias: q(0.40),
242
+ },
243
+ },
244
+ // Phase 15: boxing gloves — concussive, minimal cutting, fast punches
245
+ {
246
+ id: "wpn_boxing_gloves",
247
+ kind: "weapon",
248
+ name: "Boxing Gloves",
249
+ mass_kg: Math.round(0.28 * SCALE.kg), // 10 oz ≈ 0.28 kg
250
+ bulk: q(0.9),
251
+ reach_m: Math.trunc(0.32 * SCALE.m), // 0.32 m effective reach
252
+ handedness: "oneHand",
253
+ momentArm_m: Math.trunc(0.22 * SCALE.m),
254
+ handlingMul: q(0.95),
255
+ strikeEffectiveMassFrac: q(0.07), // padding distributes force over larger area
256
+ strikeSpeedMul: q(1.20), // fast punches
257
+ damage: {
258
+ surfaceFrac: q(0.10), // padding minimises cuts
259
+ internalFrac: q(0.60), // concussive — brain/organ shock dominates
260
+ structuralFrac: q(0.08),
261
+ bleedFactor: q(0.04), // almost no bleeding
262
+ penetrationBias: q(0.03),
263
+ },
264
+ },
265
+ ];
266
+ // Phase 11: powered exoskeletons
267
+ export const STARTER_EXOSKELETONS = [
268
+ {
269
+ id: "exo_combat",
270
+ kind: "exoskeleton",
271
+ name: "Combat exoskeleton",
272
+ mass_kg: Math.round(25.0 * SCALE.kg),
273
+ bulk: q(2.5),
274
+ requiredCapabilities: ["PoweredExoskeleton"],
275
+ speedMultiplier: q(1.25), // +25% effective sprint speed
276
+ forceMultiplier: q(1.40), // +40% melee strike energy
277
+ powerDrain_W: 200, // equivalent to a second continuous-power budget
278
+ },
279
+ {
280
+ id: "exo_heavy",
281
+ kind: "exoskeleton",
282
+ name: "Heavy assault exoskeleton",
283
+ mass_kg: Math.round(45.0 * SCALE.kg),
284
+ bulk: q(3.0),
285
+ requiredCapabilities: ["PoweredExoskeleton"],
286
+ speedMultiplier: q(1.10), // heavy — modest speed gain
287
+ forceMultiplier: q(1.80), // massive force amplification
288
+ powerDrain_W: 400,
289
+ },
290
+ ];
291
+ export const STARTER_ARMOUR = [
292
+ {
293
+ id: "arm_leather",
294
+ kind: "armour",
295
+ name: "Leather armour",
296
+ mass_kg: Math.round(6.0 * SCALE.kg),
297
+ bulk: q(1.6),
298
+ protects: channelMask(DamageChannel.Kinetic, DamageChannel.Thermal),
299
+ coverageByRegion: {
300
+ head: q(0.10),
301
+ torso: q(0.70),
302
+ leftArm: q(0.45),
303
+ rightArm: q(0.45),
304
+ leftLeg: q(0.25),
305
+ rightLeg: q(0.25),
306
+ },
307
+ resist_J: 150,
308
+ protectedDamageMul: q(0.85),
309
+ channelResistMul: { [DamageChannel.Thermal]: q(1.10) },
310
+ mobilityMul: q(0.95),
311
+ fatigueMul: q(1.08),
312
+ },
313
+ {
314
+ id: "arm_mail",
315
+ kind: "armour",
316
+ name: "Mail armour",
317
+ mass_kg: Math.round(10.0 * SCALE.kg),
318
+ bulk: q(1.9),
319
+ requiredCapabilities: ["MetallicArmour"],
320
+ protects: channelMask(DamageChannel.Kinetic),
321
+ coverageByRegion: {
322
+ head: q(0.05),
323
+ torso: q(0.78),
324
+ leftArm: q(0.55),
325
+ rightArm: q(0.55),
326
+ leftLeg: q(0.20),
327
+ rightLeg: q(0.20),
328
+ },
329
+ resist_J: 350,
330
+ protectedDamageMul: q(0.75),
331
+ mobilityMul: q(0.90),
332
+ fatigueMul: q(1.15),
333
+ },
334
+ {
335
+ id: "arm_plate",
336
+ kind: "armour",
337
+ name: "Plate armour",
338
+ mass_kg: Math.round(20.0 * SCALE.kg),
339
+ bulk: q(2.2),
340
+ requiredCapabilities: ["MetallicArmour"],
341
+ protects: channelMask(DamageChannel.Kinetic, DamageChannel.Thermal),
342
+ coverageByRegion: {
343
+ head: q(0.75),
344
+ torso: q(0.90),
345
+ leftArm: q(0.80),
346
+ rightArm: q(0.80),
347
+ leftLeg: q(0.70),
348
+ rightLeg: q(0.70),
349
+ },
350
+ resist_J: 800,
351
+ protectedDamageMul: q(0.60),
352
+ mobilityMul: q(0.82),
353
+ fatigueMul: q(1.25),
354
+ },
355
+ ];
356
+ export const STARTER_SHIELDS = [
357
+ {
358
+ id: "shd_small",
359
+ kind: "shield",
360
+ name: "Small shield",
361
+ mass_kg: Math.round(3.0 * SCALE.kg),
362
+ bulk: q(1.2),
363
+ coverageQ: q(0.65),
364
+ blockResist_J: 120,
365
+ deflectQ: q(0.30),
366
+ arcDeg: 90,
367
+ regions: ["torso", "leftArm", "rightArm"],
368
+ covers: ["torso", "arm", "head"],
369
+ coverageProfileId: "shield_small_default",
370
+ manipulationMul: q(0.95),
371
+ mobilityMul: q(0.98),
372
+ fatigueMul: q(1.05),
373
+ },
374
+ ];
375
+ // Phase 3: starter ranged weapons
376
+ // damage profile: projectiles are penetrating; surface fraction is low.
377
+ const PROJECTILE_DAMAGE = {
378
+ surfaceFrac: q(0.20),
379
+ internalFrac: q(0.55),
380
+ structuralFrac: q(0.25),
381
+ bleedFactor: q(0.75),
382
+ penetrationBias: q(0.70),
383
+ };
384
+ export const STARTER_RANGED_WEAPONS = [
385
+ {
386
+ id: "rng_sling",
387
+ kind: "ranged",
388
+ name: "Sling",
389
+ category: "thrown",
390
+ mass_kg: Math.round(0.1 * SCALE.kg),
391
+ bulk: q(0.8),
392
+ launchEnergy_J: 0, // derived from thrower peakPower_W
393
+ projectileMass_kg: Math.round(0.08 * SCALE.kg),
394
+ dragCoeff_perM: q(0.012), // 1.2% energy loss per metre
395
+ dispersionQ: q(0.012), // 12 mrad base
396
+ recycleTime_s: to.s(2.0),
397
+ damage: PROJECTILE_DAMAGE,
398
+ suppressionFearMul: q(0.5),
399
+ },
400
+ {
401
+ id: "rng_shortbow",
402
+ kind: "ranged",
403
+ name: "Short bow",
404
+ category: "bow",
405
+ mass_kg: Math.round(0.8 * SCALE.kg),
406
+ bulk: q(1.3),
407
+ launchEnergy_J: 60,
408
+ projectileMass_kg: Math.round(0.025 * SCALE.kg),
409
+ dragCoeff_perM: q(0.007), // 0.7% loss/m
410
+ dispersionQ: q(0.012),
411
+ recycleTime_s: to.s(1.5),
412
+ damage: PROJECTILE_DAMAGE,
413
+ suppressionFearMul: q(1.0),
414
+ },
415
+ {
416
+ id: "rng_longbow",
417
+ kind: "ranged",
418
+ name: "Long bow",
419
+ category: "bow",
420
+ mass_kg: Math.round(1.2 * SCALE.kg),
421
+ bulk: q(1.6),
422
+ launchEnergy_J: 90,
423
+ projectileMass_kg: Math.round(0.025 * SCALE.kg),
424
+ dragCoeff_perM: q(0.005), // 0.5% loss/m
425
+ dispersionQ: q(0.008),
426
+ recycleTime_s: to.s(2.0),
427
+ damage: PROJECTILE_DAMAGE,
428
+ suppressionFearMul: q(1.5),
429
+ },
430
+ {
431
+ id: "rng_crossbow",
432
+ kind: "ranged",
433
+ name: "Crossbow",
434
+ category: "bow",
435
+ mass_kg: Math.round(3.5 * SCALE.kg),
436
+ bulk: q(2.0),
437
+ launchEnergy_J: 120,
438
+ projectileMass_kg: Math.round(0.040 * SCALE.kg),
439
+ dragCoeff_perM: q(0.004),
440
+ dispersionQ: q(0.006),
441
+ recycleTime_s: to.s(5.0),
442
+ damage: PROJECTILE_DAMAGE,
443
+ suppressionFearMul: q(1.5),
444
+ },
445
+ {
446
+ id: "rng_pistol",
447
+ kind: "ranged",
448
+ name: "Pistol",
449
+ category: "firearm",
450
+ mass_kg: Math.round(1.2 * SCALE.kg),
451
+ bulk: q(1.1),
452
+ requiredCapabilities: ["FirearmsPropellant"],
453
+ launchEnergy_J: 400,
454
+ projectileMass_kg: Math.round(0.015 * SCALE.kg),
455
+ dragCoeff_perM: q(0.002),
456
+ dispersionQ: q(0.015),
457
+ recycleTime_s: to.s(12.0),
458
+ damage: PROJECTILE_DAMAGE,
459
+ suppressionFearMul: q(2.0),
460
+ },
461
+ {
462
+ id: "rng_musket",
463
+ kind: "ranged",
464
+ name: "Musket",
465
+ category: "firearm",
466
+ mass_kg: Math.round(4.5 * SCALE.kg),
467
+ bulk: q(2.2),
468
+ requiredCapabilities: ["FirearmsPropellant"],
469
+ launchEnergy_J: 600,
470
+ projectileMass_kg: Math.round(0.030 * SCALE.kg),
471
+ dragCoeff_perM: q(0.0015), // 0.15% loss/m
472
+ dispersionQ: q(0.010),
473
+ recycleTime_s: to.s(18.0),
474
+ damage: PROJECTILE_DAMAGE,
475
+ suppressionFearMul: q(3.0),
476
+ },
477
+ {
478
+ id: "rng_plasma_rifle",
479
+ kind: "ranged",
480
+ name: "Plasma rifle",
481
+ category: "firearm",
482
+ mass_kg: Math.round(3.8 * SCALE.kg),
483
+ bulk: q(1.8),
484
+ requiredCapabilities: ["EnergyWeapons"],
485
+ launchEnergy_J: 2000,
486
+ projectileMass_kg: Math.round(0.001 * SCALE.kg),
487
+ dragCoeff_perM: q(0.0005), // near-negligible beam divergence
488
+ dispersionQ: q(0.004),
489
+ recycleTime_s: to.s(2.0),
490
+ energyType: "plasma", // Phase 11C: Energy channel; resisted by reflectivity
491
+ suppressionFearMul: q(1.0),
492
+ damage: {
493
+ surfaceFrac: q(0.35),
494
+ internalFrac: q(0.45),
495
+ structuralFrac: q(0.20),
496
+ bleedFactor: q(0.15), // plasma cauterises, low bleed
497
+ penetrationBias: q(0.90),
498
+ },
499
+ },
500
+ ];
501
+ // ── Phase 11C starter items ────────────────────────────────────────────────────
502
+ /** Reflective/ablative armour items for energy and kinetic threats. */
503
+ export const STARTER_ARMOUR_11C = [
504
+ {
505
+ id: "arm_reflective",
506
+ kind: "armour",
507
+ name: "Reflective coating",
508
+ mass_kg: Math.round(0.5 * SCALE.kg),
509
+ bulk: q(0.3),
510
+ requiredCapabilities: ["EnergyWeapons"],
511
+ protects: channelMask(DamageChannel.Energy),
512
+ coverageByRegion: {
513
+ head: q(0.50), torso: q(0.80),
514
+ leftArm: q(0.50), rightArm: q(0.50),
515
+ leftLeg: q(0.30), rightLeg: q(0.30),
516
+ },
517
+ resist_J: 0,
518
+ protectedDamageMul: q(1.0),
519
+ reflectivity: q(0.40), // deflects 40% of energy weapon damage
520
+ },
521
+ {
522
+ id: "arm_reactive",
523
+ kind: "armour",
524
+ name: "Reactive plating",
525
+ mass_kg: Math.round(3.0 * SCALE.kg),
526
+ bulk: q(1.5),
527
+ requiredCapabilities: ["ReactivePlating"],
528
+ protects: channelMask(DamageChannel.Kinetic),
529
+ coverageByRegion: {
530
+ head: q(0.40), torso: q(0.85),
531
+ leftArm: q(0.40), rightArm: q(0.40),
532
+ leftLeg: q(0.20), rightLeg: q(0.20),
533
+ },
534
+ resist_J: 1500,
535
+ protectedDamageMul: q(0.65),
536
+ ablative: true, // degrades with use; tracked in entity.armourState
537
+ },
538
+ ];
539
+ /** Phase 11C: sensor suites that boost vision and hearing range. */
540
+ export const STARTER_SENSORS = [
541
+ {
542
+ id: "sens_nightvision",
543
+ kind: "sensor",
544
+ name: "Night-vision goggles",
545
+ mass_kg: Math.round(0.3 * SCALE.kg),
546
+ bulk: q(1.1),
547
+ requiredCapabilities: ["BallisticArmour"],
548
+ visionRangeMul: q(1.5), // +50% vision range
549
+ hearingRangeMul: q(1.0),
550
+ },
551
+ {
552
+ id: "sens_tactical",
553
+ kind: "sensor",
554
+ name: "Tactical sensor suite",
555
+ mass_kg: Math.round(0.8 * SCALE.kg),
556
+ bulk: q(1.3),
557
+ requiredCapabilities: ["PoweredExoskeleton"],
558
+ visionRangeMul: q(2.0), // double vision range
559
+ hearingRangeMul: q(1.5), // +50% hearing range
560
+ },
561
+ ];
562
+ // ── Phase 3 extension: starter ammo types ─────────────────────────────────────
563
+ /** Armour-piercing projectile damage profile: increased penetration, lower energy. */
564
+ const AP_DAMAGE = {
565
+ surfaceFrac: q(0.10),
566
+ internalFrac: q(0.60),
567
+ structuralFrac: q(0.30),
568
+ bleedFactor: q(0.50),
569
+ penetrationBias: q(0.95),
570
+ };
571
+ /** Hollow-point projectile damage profile: maximum bleeding, lower penetration. */
572
+ const HP_DAMAGE = {
573
+ surfaceFrac: q(0.40),
574
+ internalFrac: q(0.55),
575
+ structuralFrac: q(0.05),
576
+ bleedFactor: q(0.95),
577
+ penetrationBias: q(0.20),
578
+ };
579
+ export const STARTER_AMMO = [
580
+ {
581
+ id: "ammo_ap",
582
+ name: "Armour-Piercing",
583
+ damage: AP_DAMAGE,
584
+ launchEnergyMul: q(0.90), // slightly lower velocity than ball
585
+ },
586
+ {
587
+ id: "ammo_hv",
588
+ name: "High-Velocity",
589
+ launchEnergyMul: q(1.20), // +20% muzzle energy
590
+ dragCoeff_perM: q(0.002), // streamlined projectile
591
+ },
592
+ {
593
+ id: "ammo_hollow",
594
+ name: "Hollow-Point",
595
+ damage: HP_DAMAGE,
596
+ launchEnergyMul: q(0.95), // slightly heavier
597
+ },
598
+ ];
@@ -0,0 +1,102 @@
1
+ import type { Q } from "./units.js";
2
+ import type { Entity } from "./sim/entity.js";
3
+ import type { WorldState } from "./sim/world.js";
4
+ import type { TraceEvent } from "./sim/trace.js";
5
+ /** A named group of entities with defined relationships to other factions. */
6
+ export interface Faction {
7
+ id: string;
8
+ name: string;
9
+ /** Faction ids with default hostile standing (q(0.20)). */
10
+ rivals: Set<string>;
11
+ /** Faction ids with default friendly standing (q(0.70)). */
12
+ allies: Set<string>;
13
+ }
14
+ /** A reputation-relevant event witnessed by a faction member. */
15
+ export interface WitnessEvent {
16
+ actorId: number;
17
+ eventType: "kill" | "assault" | "theft" | "aid" | "surrender";
18
+ targetId: number;
19
+ /** Faction that cares about this event (typically the target's faction). */
20
+ factionId: string;
21
+ /** Signed reputation delta for the actor within `factionId`. */
22
+ delta: Q;
23
+ tick: number;
24
+ }
25
+ /**
26
+ * Persistent faction state for a scenario or campaign.
27
+ *
28
+ * `globalStanding` — faction-to-faction base standing (initialised from rival/ally sets).
29
+ * `entityReputations` — entity-level standing within factions; updated by `applyWitnessEvent`.
30
+ */
31
+ export interface FactionRegistry {
32
+ factions: Map<string, Faction>;
33
+ globalStanding: Map<string, Map<string, Q>>;
34
+ entityReputations: Map<number, Map<string, Q>>;
35
+ }
36
+ export declare const STANDING_EXALTED: Q;
37
+ export declare const STANDING_ALLY: Q;
38
+ export declare const STANDING_NEUTRAL: Q;
39
+ export declare const STANDING_RIVAL: Q;
40
+ export declare const STANDING_KOS: Q;
41
+ /** Standing below this → AI treats target as hostile. */
42
+ export declare const STANDING_HOSTILE_THRESHOLD: Q;
43
+ /** Standing above this → AI will not initiate combat. */
44
+ export declare const STANDING_FRIENDLY_THRESHOLD: Q;
45
+ /** Minimum detection quality for an entity to witness an event. */
46
+ export declare const WITNESS_DETECTION_THRESHOLD: Q;
47
+ /**
48
+ * Create a FactionRegistry pre-populated with rival/ally default standings.
49
+ *
50
+ * Only direct relations need to be specified; symmetric standings are NOT
51
+ * applied automatically (enemy of A is not necessarily enemy of B).
52
+ */
53
+ export declare function createFactionRegistry(factions: Faction[]): FactionRegistry;
54
+ /**
55
+ * Compute effective standing of entity `a` toward entity `b`.
56
+ *
57
+ * Priority (highest first):
58
+ * 1. Same faction → STANDING_EXALTED
59
+ * 2. Entity-level reputation (`registry.entityReputations.get(a.id)?.get(b.faction)`)
60
+ * combined with faction default — max of the two is used.
61
+ * 3. Global faction-to-faction standing
62
+ * 4. Rival / ally default from faction definition
63
+ * 5. STANDING_NEUTRAL (q(0.50)) for all unknown combinations
64
+ */
65
+ export declare function effectiveStanding(registry: FactionRegistry, a: Entity, b: Entity): Q;
66
+ /**
67
+ * Apply a witness event: adjust the actor's standing within the specified faction.
68
+ *
69
+ * Deltas are clamped to [0, SCALE.Q]. A kill of a faction member reduces the
70
+ * actor's standing with that faction; aiding a member increases it.
71
+ */
72
+ export declare function applyWitnessEvent(registry: FactionRegistry, event: WitnessEvent): void;
73
+ /**
74
+ * Scan a TraceEvent stream and produce WitnessEvents for reputation-relevant
75
+ * actions (kills, assaults, aid).
76
+ *
77
+ * Only events where at least one bystander entity (not the actor or target) can
78
+ * detect the actor (`detectionQ ≥ WITNESS_DETECTION_THRESHOLD`) are included.
79
+ *
80
+ * Deduplication: at most one event per (actorId, eventType) per tick.
81
+ *
82
+ * @param factions Map of entityId → factionId for the current scenario.
83
+ */
84
+ export declare function extractWitnessEvents(events: TraceEvent[], world: WorldState, factions: Map<number, string>): WitnessEvent[];
85
+ /**
86
+ * Adjust the global faction-to-faction standing of `factionAId` toward
87
+ * `factionBId` by `delta`, clamped to [0, SCALE.Q].
88
+ *
89
+ * Used by the Polity diplomacy system (Phase 61) to apply `standingDelta`
90
+ * from `resolveDiplomacy`. The relation is one-directional; call twice with
91
+ * swapped arguments for a symmetric update.
92
+ */
93
+ export declare function applyFactionStanding(registry: FactionRegistry, factionAId: string, factionBId: string, delta: Q): void;
94
+ /**
95
+ * Serialise a FactionRegistry to a JSON string.
96
+ * Handles all nested Map and Set fields (rivals, allies, globalStanding, entityReputations).
97
+ */
98
+ export declare function serialiseFactionRegistry(registry: FactionRegistry): string;
99
+ /**
100
+ * Deserialise a FactionRegistry from a JSON string produced by `serialiseFactionRegistry`.
101
+ */
102
+ export declare function deserialiseFactionRegistry(json: string): FactionRegistry;