@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,419 @@
1
+ // src/settlement.ts — Phase 44: Settlement & Base Building
2
+ //
3
+ // Persistent locations that can be constructed, upgraded, and populated.
4
+ // Settlements provide services, storage, and serve as quest hubs.
5
+ import { q, clampQ, SCALE, mulDiv } from "./units.js";
6
+ import { createInventory } from "./inventory.js";
7
+ export const SETTLEMENT_TIER_NAMES = {
8
+ 0: "Camp",
9
+ 1: "Hamlet",
10
+ 2: "Village",
11
+ 3: "Town",
12
+ 4: "City",
13
+ };
14
+ // ── Settlement Creation ───────────────────────────────────────────────────────
15
+ /** Create a new settlement. */
16
+ export function createSettlement(settlementId, name, position, tick, tier = 0, factionId) {
17
+ const settlement = {
18
+ settlementId,
19
+ name,
20
+ position,
21
+ tier,
22
+ facilities: {
23
+ forge: 0,
24
+ medical: tier >= 1 ? 1 : 0,
25
+ market: tier >= 1 ? 1 : 0,
26
+ barracks: 0,
27
+ temple: 0,
28
+ },
29
+ population: tier === 0 ? 5 : tier * 50,
30
+ populationCap: calculatePopulationCap(tier, { forge: 0, medical: 0, market: 0, barracks: 0, temple: 0 }),
31
+ factionId,
32
+ sharedStorage: undefined,
33
+ activeProjects: [],
34
+ history: [{
35
+ tick,
36
+ type: "founded",
37
+ description: `${name} was founded as a ${SETTLEMENT_TIER_NAMES[tier]}`,
38
+ }],
39
+ safetyStatus: {
40
+ ticksSinceLastRaid: 1000,
41
+ hasDefenses: false,
42
+ recentCasualties: 0,
43
+ },
44
+ foodSurplus_Q: q(0.5),
45
+ foundedAtTick: tick,
46
+ lastUpdateTick: tick,
47
+ };
48
+ // Add shared storage for villages and above
49
+ if (tier >= 2) {
50
+ settlement.sharedStorage = createInventory(-1); // -1 indicates shared/npc inventory
51
+ }
52
+ return settlement;
53
+ }
54
+ /** Calculate population cap based on tier and facilities. */
55
+ export function calculatePopulationCap(tier, facilities) {
56
+ // Base capacity by tier
57
+ const baseCap = {
58
+ 0: 10, // Camp
59
+ 1: 50, // Hamlet
60
+ 2: 200, // Village
61
+ 3: 1000, // Town
62
+ 4: 5000, // City
63
+ };
64
+ let cap = baseCap[tier];
65
+ // Barracks increases cap (housing)
66
+ cap += facilities.barracks * 50;
67
+ // Medical facility allows healthier population density
68
+ cap += facilities.medical * 25;
69
+ return cap;
70
+ }
71
+ // ── Settlement Registry ───────────────────────────────────────────────────────
72
+ /** Create a new settlement registry. */
73
+ export function createSettlementRegistry() {
74
+ return {
75
+ settlements: new Map(),
76
+ byPosition: new Map(),
77
+ byFaction: new Map(),
78
+ };
79
+ }
80
+ /** Register a settlement in the registry. */
81
+ export function registerSettlement(registry, settlement) {
82
+ registry.settlements.set(settlement.settlementId, settlement);
83
+ const posKey = `${settlement.position.x},${settlement.position.y}`;
84
+ registry.byPosition.set(posKey, settlement.settlementId);
85
+ if (settlement.factionId !== undefined) {
86
+ let factionSet = registry.byFaction.get(settlement.factionId);
87
+ if (!factionSet) {
88
+ factionSet = new Set();
89
+ registry.byFaction.set(settlement.factionId, factionSet);
90
+ }
91
+ factionSet.add(settlement.settlementId);
92
+ }
93
+ }
94
+ /** Remove a settlement from the registry. */
95
+ export function unregisterSettlement(registry, settlementId) {
96
+ const settlement = registry.settlements.get(settlementId);
97
+ if (!settlement)
98
+ return false;
99
+ registry.settlements.delete(settlementId);
100
+ const posKey = `${settlement.position.x},${settlement.position.y}`;
101
+ registry.byPosition.delete(posKey);
102
+ if (settlement.factionId !== undefined) {
103
+ const factionSet = registry.byFaction.get(settlement.factionId);
104
+ if (factionSet) {
105
+ factionSet.delete(settlementId);
106
+ }
107
+ }
108
+ return true;
109
+ }
110
+ /** Get settlement by position. */
111
+ export function getSettlementAtPosition(registry, x, y) {
112
+ const posKey = `${x},${y}`;
113
+ const settlementId = registry.byPosition.get(posKey);
114
+ if (settlementId) {
115
+ return registry.settlements.get(settlementId);
116
+ }
117
+ return undefined;
118
+ }
119
+ /** Get all settlements for a faction. */
120
+ export function getFactionSettlements(registry, factionId) {
121
+ const settlementIds = registry.byFaction.get(factionId);
122
+ if (!settlementIds)
123
+ return [];
124
+ return Array.from(settlementIds)
125
+ .map((id) => registry.settlements.get(id))
126
+ .filter((s) => s !== undefined);
127
+ }
128
+ /** Find nearest settlement to a position. */
129
+ export function findNearestSettlement(registry, x, y) {
130
+ let nearest;
131
+ let nearestDist = Infinity;
132
+ for (const settlement of registry.settlements.values()) {
133
+ const dx = settlement.position.x - x;
134
+ const dy = settlement.position.y - y;
135
+ const dist = Math.sqrt(dx * dx + dy * dy);
136
+ if (dist < nearestDist) {
137
+ nearest = settlement;
138
+ nearestDist = dist;
139
+ }
140
+ }
141
+ if (!nearest)
142
+ return undefined;
143
+ return { settlement: nearest, distance: nearestDist };
144
+ }
145
+ // ── Construction Projects ──────────────────────────────────────────────────────
146
+ /** Facility upgrade costs and requirements. */
147
+ export const FACILITY_UPGRADE_COSTS = {
148
+ forge: {
149
+ 0: { materials: {}, laborHours: 0 },
150
+ 1: { materials: { stone: 50, wood: 100 }, laborHours: 100 },
151
+ 2: { materials: { stone: 150, iron: 50, wood: 200 }, laborHours: 300 },
152
+ 3: { materials: { stone: 400, iron: 150, steel: 50 }, laborHours: 800 },
153
+ 4: { materials: { stone: 1000, steel: 200, mithril: 20 }, laborHours: 2000 },
154
+ },
155
+ medical: {
156
+ 0: { materials: {}, laborHours: 0 },
157
+ 1: { materials: { wood: 50, cloth: 30 }, laborHours: 80 },
158
+ 2: { materials: { wood: 150, stone: 100, cloth: 100 }, laborHours: 250 },
159
+ 3: { materials: { stone: 300, glass: 100, iron: 50 }, laborHours: 600 },
160
+ 4: { materials: { stone: 800, glass: 300, steel: 100 }, laborHours: 1500 },
161
+ },
162
+ market: {
163
+ 0: { materials: {}, laborHours: 0 },
164
+ 1: { materials: { wood: 80 }, laborHours: 60 },
165
+ 2: { materials: { wood: 200, stone: 100 }, laborHours: 200 },
166
+ 3: { materials: { stone: 400, wood: 300, iron: 30 }, laborHours: 500 },
167
+ 4: { materials: { stone: 1200, marble: 200, steel: 50 }, laborHours: 1200 },
168
+ },
169
+ barracks: {
170
+ 0: { materials: {}, laborHours: 0 },
171
+ 1: { materials: { wood: 100 }, laborHours: 80 },
172
+ 2: { materials: { wood: 300, stone: 100 }, laborHours: 250 },
173
+ 3: { materials: { stone: 500, wood: 200, iron: 50 }, laborHours: 600 },
174
+ 4: { materials: { stone: 1500, steel: 200 }, laborHours: 1500 },
175
+ },
176
+ temple: {
177
+ 0: { materials: {}, laborHours: 0 },
178
+ 1: { materials: { wood: 60, cloth: 20 }, laborHours: 100 },
179
+ 2: { materials: { stone: 200, wood: 100, cloth: 50 }, laborHours: 300 },
180
+ 3: { materials: { stone: 600, glass: 100, gold: 10 }, laborHours: 800 },
181
+ 4: { materials: { stone: 2000, marble: 500, gold: 100, crystal: 50 }, laborHours: 2500 },
182
+ },
183
+ };
184
+ /** Start a construction project. */
185
+ export function startConstructionProject(settlement, facility, targetLevel, tick) {
186
+ const currentLevel = settlement.facilities[facility];
187
+ if (targetLevel <= currentLevel) {
188
+ return { success: false, reason: "already_at_or_above_level" };
189
+ }
190
+ if (targetLevel > currentLevel + 1) {
191
+ return { success: false, reason: "must_upgrade_sequentially" };
192
+ }
193
+ // Check if project already exists for this facility
194
+ const existing = settlement.activeProjects.find((p) => p.targetFacility === facility);
195
+ if (existing) {
196
+ return { success: false, reason: "project_already_active" };
197
+ }
198
+ const costs = FACILITY_UPGRADE_COSTS[facility][targetLevel];
199
+ if (!costs) {
200
+ return { success: false, reason: "invalid_target_level" };
201
+ }
202
+ const project = {
203
+ projectId: `${settlement.settlementId}_${facility}_${targetLevel}_${tick}`,
204
+ targetFacility: facility,
205
+ targetLevel,
206
+ requiredResources: { ...costs.materials },
207
+ progress_Q: q(0),
208
+ contributors: [],
209
+ startedAtTick: tick,
210
+ };
211
+ settlement.activeProjects.push(project);
212
+ settlement.history.push({
213
+ tick,
214
+ type: "project_started",
215
+ description: `Started construction of ${facility} level ${targetLevel}`,
216
+ data: { facility, targetLevel, projectId: project.projectId },
217
+ });
218
+ return { success: true, project };
219
+ }
220
+ /** Contribute work to a construction project. */
221
+ export function contributeToProject(settlement, projectId, entityId, competenceQuality_Q, // From Phase 40 competence system
222
+ hoursWorked, tick) {
223
+ const project = settlement.activeProjects.find((p) => p.projectId === projectId);
224
+ if (!project) {
225
+ return { success: false, reason: "project_not_found" };
226
+ }
227
+ // Add contributor
228
+ if (!project.contributors.includes(entityId)) {
229
+ project.contributors.push(entityId);
230
+ }
231
+ // Calculate progress contribution
232
+ // Total required = laborHours from costs
233
+ const costs = FACILITY_UPGRADE_COSTS[project.targetFacility][project.targetLevel];
234
+ const totalLaborRequired = costs.laborHours * SCALE.Q;
235
+ // Progress = competenceQuality * hoursWorked / totalRequired
236
+ const contribution = mulDiv(competenceQuality_Q, Math.round(hoursWorked * SCALE.Q), totalLaborRequired);
237
+ project.progress_Q = clampQ((project.progress_Q + contribution), q(0), SCALE.Q);
238
+ // Check completion
239
+ if (project.progress_Q >= SCALE.Q) {
240
+ completeConstructionProject(settlement, project, tick);
241
+ return { success: true, completed: true };
242
+ }
243
+ return { success: true, completed: false };
244
+ }
245
+ /** Complete a construction project. */
246
+ function completeConstructionProject(settlement, project, tick) {
247
+ // Upgrade the facility
248
+ settlement.facilities[project.targetFacility] = project.targetLevel;
249
+ // Recalculate population cap
250
+ settlement.populationCap = calculatePopulationCap(settlement.tier, settlement.facilities);
251
+ // Remove from active projects
252
+ const idx = settlement.activeProjects.indexOf(project);
253
+ if (idx >= 0) {
254
+ settlement.activeProjects.splice(idx, 1);
255
+ }
256
+ // Record history
257
+ settlement.history.push({
258
+ tick,
259
+ type: "facility_upgraded",
260
+ description: `Completed ${project.targetFacility} level ${project.targetLevel}`,
261
+ entityIds: project.contributors,
262
+ data: { facility: project.targetFacility, level: project.targetLevel },
263
+ });
264
+ // Check for tier upgrade
265
+ checkTierUpgrade(settlement, tick);
266
+ }
267
+ /** Check if settlement qualifies for tier upgrade. */
268
+ function checkTierUpgrade(settlement, tick) {
269
+ const facilitySum = Object.values(settlement.facilities).reduce((a, b) => a + b, 0);
270
+ const requiredFacilities = (settlement.tier + 1) * 2;
271
+ if (facilitySum >= requiredFacilities && settlement.population >= settlement.populationCap * 0.8) {
272
+ const oldTier = settlement.tier;
273
+ settlement.tier = Math.min(4, (settlement.tier + 1));
274
+ if (settlement.tier > oldTier) {
275
+ settlement.populationCap = calculatePopulationCap(settlement.tier, settlement.facilities);
276
+ settlement.history.push({
277
+ tick,
278
+ type: "tier_upgraded",
279
+ description: `${settlement.name} has grown from ${SETTLEMENT_TIER_NAMES[oldTier]} to ${SETTLEMENT_TIER_NAMES[settlement.tier]}`,
280
+ data: { oldTier, newTier: settlement.tier },
281
+ });
282
+ // Add shared storage if newly qualified
283
+ if (settlement.tier >= 2 && !settlement.sharedStorage) {
284
+ settlement.sharedStorage = createInventory(-1);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ // ── Population Dynamics ───────────────────────────────────────────────────────
290
+ /** Update settlement population based on conditions. */
291
+ export function updateSettlementPopulation(settlement, tick) {
292
+ if (settlement.population >= settlement.populationCap) {
293
+ return { growth: 0, reason: "at_capacity" };
294
+ }
295
+ // Base growth rate
296
+ let growthChance = 0.001; // 0.1% per update
297
+ // Modifiers
298
+ if (settlement.foodSurplus_Q > q(0.5)) {
299
+ growthChance *= 1.5; // Food surplus bonus
300
+ }
301
+ else if (settlement.foodSurplus_Q < q(0.2)) {
302
+ growthChance *= 0.5; // Food shortage penalty
303
+ }
304
+ if (settlement.facilities.medical >= 2) {
305
+ growthChance *= 1.3; // Good medical facilities
306
+ }
307
+ if (settlement.safetyStatus.ticksSinceLastRaid < 100) {
308
+ growthChance *= 0.3; // Recent raid penalty
309
+ }
310
+ if (settlement.safetyStatus.hasDefenses) {
311
+ growthChance *= 1.2; // Defenses provide security
312
+ }
313
+ // Apply growth
314
+ const growthRoll = Math.random(); // In real system, use seeded RNG
315
+ if (growthRoll < growthChance) {
316
+ const growth = Math.max(1, Math.floor(settlement.population * 0.01));
317
+ const actualGrowth = Math.min(growth, settlement.populationCap - settlement.population);
318
+ if (actualGrowth > 0) {
319
+ settlement.population += actualGrowth;
320
+ settlement.history.push({
321
+ tick,
322
+ type: "population_changed",
323
+ description: `Population grew by ${actualGrowth} to ${settlement.population}`,
324
+ data: { change: actualGrowth, newPopulation: settlement.population },
325
+ });
326
+ return { growth: actualGrowth, reason: "natural_growth" };
327
+ }
328
+ }
329
+ return { growth: 0, reason: "no_growth" };
330
+ }
331
+ /** Get available services for a settlement. */
332
+ export function getAvailableServices(settlement) {
333
+ return {
334
+ repair: settlement.facilities.forge >= 1,
335
+ repairQualityBonus_Q: (settlement.facilities.forge * 500), // +5% per level
336
+ medicalCare: getMedicalCareLevel(settlement.facilities.medical),
337
+ training: settlement.facilities.barracks >= 1,
338
+ trainingBonus_Q: (settlement.facilities.barracks * 500),
339
+ market: settlement.facilities.market >= 1,
340
+ marketDiscount_Q: (settlement.facilities.market * 300), // -3% per level
341
+ questGeneration: settlement.tier >= 1,
342
+ };
343
+ }
344
+ function getMedicalCareLevel(level) {
345
+ switch (level) {
346
+ case 0: return "none";
347
+ case 1: return "basic";
348
+ case 2: return "skilled";
349
+ case 3: return "expert";
350
+ case 4: return "master";
351
+ default: return "none";
352
+ }
353
+ }
354
+ // ── Settlement Defense ─────────────────────────────────────────────────────────
355
+ /** Record a raid/siege on a settlement. */
356
+ export function recordRaid(settlement, attackerFactionId, casualties, tick) {
357
+ settlement.safetyStatus.ticksSinceLastRaid = 0;
358
+ settlement.safetyStatus.recentCasualties = casualties;
359
+ settlement.history.push({
360
+ tick,
361
+ type: "raid",
362
+ description: `Raid by faction ${attackerFactionId}, ${casualties} casualties`,
363
+ data: { attackerFactionId, casualties },
364
+ });
365
+ // Population loss from casualties
366
+ if (casualties > 0) {
367
+ settlement.population = Math.max(0, settlement.population - casualties);
368
+ }
369
+ }
370
+ /** Update settlement defenses. */
371
+ export function updateDefenses(settlement, hasDefenses) {
372
+ settlement.safetyStatus.hasDefenses = hasDefenses;
373
+ }
374
+ // ── Serialization ─────────────────────────────────────────────────────────────
375
+ /** Serialize settlement to JSON-friendly format. */
376
+ export function serializeSettlement(settlement) {
377
+ return {
378
+ settlementId: settlement.settlementId,
379
+ name: settlement.name,
380
+ position: settlement.position,
381
+ tier: settlement.tier,
382
+ facilities: settlement.facilities,
383
+ population: settlement.population,
384
+ populationCap: settlement.populationCap,
385
+ factionId: settlement.factionId,
386
+ sharedStorage: settlement.sharedStorage ? undefined : undefined, // Simplified
387
+ activeProjects: settlement.activeProjects,
388
+ history: settlement.history,
389
+ safetyStatus: settlement.safetyStatus,
390
+ foodSurplus_Q: settlement.foodSurplus_Q,
391
+ foundedAtTick: settlement.foundedAtTick,
392
+ lastUpdateTick: settlement.lastUpdateTick,
393
+ };
394
+ }
395
+ /** Deserialize settlement. */
396
+ export function deserializeSettlement(data) {
397
+ const d = data;
398
+ return {
399
+ settlementId: d.settlementId ?? "",
400
+ name: d.name ?? "Unknown",
401
+ position: d.position ?? { x: 0, y: 0 },
402
+ tier: d.tier ?? 0,
403
+ facilities: d.facilities ?? {
404
+ forge: 0, medical: 0, market: 0, barracks: 0, temple: 0,
405
+ },
406
+ population: d.population ?? 0,
407
+ populationCap: d.populationCap ?? 0,
408
+ factionId: d.factionId,
409
+ sharedStorage: undefined,
410
+ activeProjects: Array.isArray(d.activeProjects) ? d.activeProjects : [],
411
+ history: Array.isArray(d.history) ? d.history : [],
412
+ safetyStatus: d.safetyStatus ?? {
413
+ ticksSinceLastRaid: 1000, hasDefenses: false, recentCasualties: 0,
414
+ },
415
+ foodSurplus_Q: d.foodSurplus_Q ?? q(0.5),
416
+ foundedAtTick: d.foundedAtTick ?? 0,
417
+ lastUpdateTick: d.lastUpdateTick ?? 0,
418
+ };
419
+ }
@@ -0,0 +1,28 @@
1
+ import type { I32, Q } from "../units.js";
2
+ export interface ActionState {
3
+ attackCooldownTicks: I32;
4
+ defenceCooldownTicks: I32;
5
+ grappleCooldownTicks: I32;
6
+ facingDirQ: {
7
+ x: I32;
8
+ y: I32;
9
+ z: I32;
10
+ };
11
+ weaponBindPartnerId: number;
12
+ weaponBindTicks: number;
13
+ swingMomentumQ: Q;
14
+ shootCooldownTicks: I32;
15
+ aimTicks: number;
16
+ aimTargetId: number;
17
+ roundsInMag?: number;
18
+ capabilityCooldowns?: Map<string, number>;
19
+ lastCellKey?: string;
20
+ staggerTicks?: number;
21
+ sustainedEmission?: {
22
+ sourceId: string;
23
+ effectId: string;
24
+ targetId?: number;
25
+ remainingTicks: number;
26
+ };
27
+ }
28
+ export declare const defaultAction: () => ActionState;
@@ -0,0 +1,12 @@
1
+ export const defaultAction = () => ({
2
+ attackCooldownTicks: 0,
3
+ defenceCooldownTicks: 0,
4
+ grappleCooldownTicks: 0,
5
+ facingDirQ: { x: 10_000, y: 0, z: 0 },
6
+ weaponBindPartnerId: 0,
7
+ weaponBindTicks: 0,
8
+ swingMomentumQ: 0,
9
+ shootCooldownTicks: 0,
10
+ aimTicks: 0,
11
+ aimTargetId: 0,
12
+ });
@@ -0,0 +1,95 @@
1
+ import { type Q } from "../units.js";
2
+ import type { IndividualAttributes } from "../types.js";
3
+ import type { Entity } from "./entity.js";
4
+ /**
5
+ * Life-stage classification derived from normalized age fraction.
6
+ * Species-agnostic: boundaries are proportional to lifespan, not absolute years.
7
+ */
8
+ export type AgePhase = "infant" | "child" | "adolescent" | "young_adult" | "adult" | "elder" | "ancient";
9
+ /** Q-valued multipliers for each aging dimension. */
10
+ export interface AgeMultipliers {
11
+ /** Multiplier for peakForce_N, peakPower_W, continuousPower_W [Q]. */
12
+ muscularStrength_Q: Q;
13
+ /** Multiplier on reactionTime_s — > q(1.0) means slower reaction [Q]. */
14
+ reactionTime_Q: Q;
15
+ /** Multiplier for controlQuality, stability, fineControl [Q]. */
16
+ motorControl_Q: Q;
17
+ /** Multiplier for stature_m [Q]. */
18
+ stature_Q: Q;
19
+ /** Multiplier for logicalMathematical, spatial, bodilyKinesthetic, musical [Q]. */
20
+ cognitionFluid_Q: Q;
21
+ /** Multiplier for linguistic, interpersonal, intrapersonal [Q]. */
22
+ cognitionCrystal_Q: Q;
23
+ /** Multiplier for distressTolerance [Q]. */
24
+ distressTolerance_Q: Q;
25
+ }
26
+ /** Per-entity age accumulator stored on `entity.age`. */
27
+ export interface AgeState {
28
+ /** Elapsed seconds of this entity's life (from birth). */
29
+ ageSeconds: number;
30
+ }
31
+ /** Seconds in one year (non-leap). */
32
+ export declare const SECONDS_PER_YEAR: number;
33
+ /** Default lifespan for entities without a species override [years]. */
34
+ export declare const HUMAN_LIFESPAN_YEARS = 80;
35
+ /**
36
+ * Compute normalized age fraction [0..SCALE.Q] for a given age and lifespan.
37
+ *
38
+ * A 25-year-old human (lifespan 80) → q(0.3125).
39
+ * A 187-year-old elf (lifespan 600) → q(0.312) — effectively the same developmental stage.
40
+ *
41
+ * @param ageYears Current age in years.
42
+ * @param lifespanYears Expected lifespan (default: HUMAN_LIFESPAN_YEARS).
43
+ */
44
+ export declare function computeAgeFrac(ageYears: number, lifespanYears?: number): Q;
45
+ /**
46
+ * Classify the entity's life stage from their normalized age fraction.
47
+ *
48
+ * Boundaries (ageFrac):
49
+ * infant 0–0.05 | child 0.05–0.15 | adolescent 0.15–0.22 |
50
+ * young_adult 0.22–0.38 | adult 0.38–0.62 | elder 0.62–0.88 | ancient 0.88+
51
+ */
52
+ export declare function getAgePhase(ageYears: number, lifespanYears?: number): AgePhase;
53
+ /**
54
+ * Derive age-based attribute multipliers from normalized age and lifespan.
55
+ *
56
+ * All returned Q values except `reactionTime_Q` are in [0, SCALE.Q].
57
+ * `reactionTime_Q` may exceed SCALE.Q (values > q(1.0) indicate slower reaction
58
+ * than the archetype baseline).
59
+ */
60
+ export declare function deriveAgeMultipliers(ageYears: number, lifespanYears?: number): AgeMultipliers;
61
+ /**
62
+ * Apply age multipliers to a base attribute set, returning a new object.
63
+ *
64
+ * The input `base` is treated as the archetype peak (typically from `generateIndividual`).
65
+ * The caller is responsible for caching the base and recomputing aged attributes when
66
+ * age advances (e.g. once per in-game month for campaign simulation).
67
+ *
68
+ * Attributes affected:
69
+ * - morphology.stature_m
70
+ * - performance.peakForce_N, peakPower_W, continuousPower_W
71
+ * - control.reactionTime_s, controlQuality, stability, fineControl
72
+ * - resilience.distressTolerance
73
+ * - cognition (if present): fluid dims + crystal dims scaled independently
74
+ *
75
+ * All Q outputs are clamped to [0, SCALE.Q]; reactionTime_s is clamped to ≥ 1.
76
+ *
77
+ * @param base Archetype-peak attributes (unmodified).
78
+ * @param ageYears Current age in years.
79
+ * @param lifespanYears Expected lifespan (default: HUMAN_LIFESPAN_YEARS).
80
+ */
81
+ export declare function applyAgingToAttributes(base: IndividualAttributes, ageYears: number, lifespanYears?: number): IndividualAttributes;
82
+ /**
83
+ * Advance an entity's age by `elapsedSeconds`.
84
+ *
85
+ * Initializes `entity.age` if absent. Does NOT recompute attributes — the host
86
+ * should call `applyAgingToAttributes` when it needs current aged stats.
87
+ *
88
+ * Mutates: `entity.age`.
89
+ */
90
+ export declare function stepAging(entity: Entity, elapsedSeconds: number): void;
91
+ /**
92
+ * Convenience helper: return the current age in fractional years from entity.age.
93
+ * Returns 0 if `entity.age` is absent.
94
+ */
95
+ export declare function entityAgeYears(entity: Entity): number;