@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,520 @@
1
+ // src/quest.ts — Phase 41: Quest & Mission System
2
+ //
3
+ // Structured quest system with objectives, state tracking, and simulation event hooks.
4
+ // Integrates with Phase 40 (Competence), Phase 24 (Factions), Phase 23 (Dialogue).
5
+ /** Create a new empty quest registry. */
6
+ export function createQuestRegistry() {
7
+ return {
8
+ templates: new Map(),
9
+ logs: new Map(),
10
+ history: [],
11
+ };
12
+ }
13
+ /** Get or create a quest log for an entity. */
14
+ export function getQuestLog(registry, entityId) {
15
+ let log = registry.logs.get(entityId);
16
+ if (!log) {
17
+ log = {
18
+ entityId,
19
+ active: new Map(),
20
+ completed: new Map(),
21
+ failed: new Map(),
22
+ };
23
+ registry.logs.set(entityId, log);
24
+ }
25
+ return log;
26
+ }
27
+ // ── Quest Lifecycle ───────────────────────────────────────────────────────────
28
+ /** Register a quest template in the registry. */
29
+ export function registerQuestTemplate(registry, quest) {
30
+ registry.templates.set(quest.questId, quest);
31
+ }
32
+ /**
33
+ * Offer a quest to an entity.
34
+ * Returns false if entity already has this quest (in any state).
35
+ */
36
+ export function offerQuest(registry, entityId, questTemplateId, tick) {
37
+ const template = registry.templates.get(questTemplateId);
38
+ if (!template) {
39
+ return { accepted: false, reason: `Unknown quest template: ${questTemplateId}` };
40
+ }
41
+ const log = getQuestLog(registry, entityId);
42
+ // Check if already has this quest
43
+ if (log.active.has(questTemplateId) ||
44
+ log.completed.has(questTemplateId) ||
45
+ log.failed.has(questTemplateId)) {
46
+ return { accepted: false, reason: "Quest already in log" };
47
+ }
48
+ // Deep clone the quest
49
+ const quest = {
50
+ ...template,
51
+ objectives: template.objectives.map((o) => ({ ...o })),
52
+ state: "active",
53
+ acceptedAtTick: tick,
54
+ };
55
+ // Initialize objective states
56
+ for (const obj of quest.objectives) {
57
+ if (!obj.requires || obj.requires.length === 0) {
58
+ obj.state = "available";
59
+ obj.activatedAtTick = tick;
60
+ }
61
+ else {
62
+ obj.state = "locked";
63
+ }
64
+ }
65
+ log.active.set(quest.questId, quest);
66
+ registry.history.push({
67
+ questId: quest.questId,
68
+ oldState: "inactive",
69
+ newState: "active",
70
+ trigger: { type: "accepted", entityId },
71
+ tick,
72
+ });
73
+ return { accepted: true, quest };
74
+ }
75
+ /** Abandon an active quest (moves to failed). */
76
+ export function abandonQuest(registry, entityId, questId, tick) {
77
+ const log = getQuestLog(registry, entityId);
78
+ const quest = log.active.get(questId);
79
+ if (!quest)
80
+ return false;
81
+ const oldState = quest.state;
82
+ quest.state = "failed";
83
+ log.active.delete(questId);
84
+ log.failed.set(questId, quest);
85
+ registry.history.push({
86
+ questId,
87
+ oldState,
88
+ newState: "failed",
89
+ trigger: { type: "manual", reason: "abandoned" },
90
+ tick,
91
+ });
92
+ return true;
93
+ }
94
+ // ── Objective Progression ─────────────────────────────────────────────────────
95
+ /**
96
+ * Check if an objective is complete based on its type and progress.
97
+ */
98
+ export function checkObjectiveComplete(objective) {
99
+ if (objective.state === "completed" || objective.state === "failed") {
100
+ return true;
101
+ }
102
+ switch (objective.type) {
103
+ case "reach_location":
104
+ case "dialogue_choice":
105
+ case "escort_entity":
106
+ case "use_competence":
107
+ case "deliver_item":
108
+ case "wait_duration":
109
+ // Boolean completion - state must be "completed"
110
+ return false; // Not yet completed
111
+ case "defeat_entity":
112
+ case "collect_item":
113
+ // Count-based completion
114
+ if (objective.count !== undefined) {
115
+ return objective.progress >= objective.count;
116
+ }
117
+ return false;
118
+ default:
119
+ return false;
120
+ }
121
+ }
122
+ /**
123
+ * Unlock objectives whose prerequisites are met.
124
+ */
125
+ export function unlockObjectives(quest, tick) {
126
+ const unlocked = [];
127
+ for (const obj of quest.objectives) {
128
+ if (obj.state !== "locked")
129
+ continue;
130
+ if (!obj.requires || obj.requires.length === 0)
131
+ continue;
132
+ const allPrereqsComplete = obj.requires.every((reqId) => {
133
+ const prereq = quest.objectives.find((o) => o.objectiveId === reqId);
134
+ return prereq?.state === "completed";
135
+ });
136
+ if (allPrereqsComplete) {
137
+ obj.state = "available";
138
+ obj.activatedAtTick = tick;
139
+ unlocked.push(obj);
140
+ }
141
+ }
142
+ return unlocked;
143
+ }
144
+ /**
145
+ * Check if quest is complete (all non-hidden objectives completed).
146
+ */
147
+ export function checkQuestComplete(quest) {
148
+ const requiredObjectives = quest.objectives.filter((o) => !o.hidden);
149
+ if (requiredObjectives.length === 0)
150
+ return false;
151
+ return requiredObjectives.every((o) => o.state === "completed");
152
+ }
153
+ /**
154
+ * Check if any objective has failed.
155
+ */
156
+ /**
157
+ * Check if any objective has failed.
158
+ */
159
+ export function checkQuestFailed(quest) {
160
+ return quest.objectives.some((o) => o.state === "failed");
161
+ }
162
+ /**
163
+ * Handle location reached event.
164
+ */
165
+ export function handleLocationReached(quest, entityId, position, tick) {
166
+ for (const obj of quest.objectives) {
167
+ if (obj.state !== "available" && obj.state !== "in_progress")
168
+ continue;
169
+ if (obj.type !== "reach_location")
170
+ continue;
171
+ if (!obj.target?.location)
172
+ continue;
173
+ const dx = position.x - obj.target.location.x;
174
+ const dy = position.y - obj.target.location.y;
175
+ const distSq = dx * dx + dy * dy;
176
+ const radius = obj.target.location.radius_m;
177
+ if (distSq <= radius * radius) {
178
+ const oldState = obj.state;
179
+ obj.state = "completed";
180
+ // Check for quest completion
181
+ unlockObjectives(quest, tick);
182
+ const complete = checkQuestComplete(quest);
183
+ if (complete) {
184
+ quest.state = "completed";
185
+ }
186
+ return {
187
+ updated: true,
188
+ questId: quest.questId,
189
+ objectiveId: obj.objectiveId,
190
+ oldState,
191
+ newState: "completed",
192
+ questComplete: complete,
193
+ };
194
+ }
195
+ }
196
+ return { updated: false };
197
+ }
198
+ /**
199
+ * Handle entity defeated event (from combat).
200
+ */
201
+ export function handleEntityDefeated(quest, impact, tick) {
202
+ for (const obj of quest.objectives) {
203
+ if (obj.state !== "available" && obj.state !== "in_progress")
204
+ continue;
205
+ if (obj.type !== "defeat_entity")
206
+ continue;
207
+ // Check if this is the target entity
208
+ if (obj.target?.entityId !== undefined) {
209
+ if (obj.target.entityId !== impact.targetId)
210
+ continue;
211
+ }
212
+ obj.progress++;
213
+ if (obj.state === "available") {
214
+ obj.state = "in_progress";
215
+ }
216
+ const complete = checkObjectiveComplete(obj);
217
+ if (complete) {
218
+ const oldState = obj.state;
219
+ obj.state = "completed";
220
+ unlockObjectives(quest, tick);
221
+ const questComplete = checkQuestComplete(quest);
222
+ if (questComplete) {
223
+ quest.state = "completed";
224
+ }
225
+ return {
226
+ updated: true,
227
+ questId: quest.questId,
228
+ objectiveId: obj.objectiveId,
229
+ oldState,
230
+ newState: "completed",
231
+ questComplete,
232
+ };
233
+ }
234
+ return {
235
+ updated: true,
236
+ questId: quest.questId,
237
+ objectiveId: obj.objectiveId,
238
+ oldState: "in_progress",
239
+ newState: "in_progress",
240
+ };
241
+ }
242
+ return { updated: false };
243
+ }
244
+ /**
245
+ * Handle competence use event (from Phase 40).
246
+ */
247
+ export function handleCompetenceUsed(quest, domain, quality_Q, taskId, tick) {
248
+ for (const obj of quest.objectives) {
249
+ if (obj.state !== "available" && obj.state !== "in_progress")
250
+ continue;
251
+ if (obj.type !== "use_competence")
252
+ continue;
253
+ if (!obj.target?.competence)
254
+ continue;
255
+ const req = obj.target.competence;
256
+ // Check domain match
257
+ if (req.domain !== domain)
258
+ continue;
259
+ // Check task match if specified
260
+ if (req.taskId && req.taskId !== taskId)
261
+ continue;
262
+ // Check quality threshold
263
+ if (quality_Q < req.minQuality_Q) {
264
+ // Quality too low - doesn't count but doesn't fail
265
+ continue;
266
+ }
267
+ const oldState = obj.state;
268
+ obj.state = "completed";
269
+ unlockObjectives(quest, tick);
270
+ const complete = checkQuestComplete(quest);
271
+ if (complete) {
272
+ quest.state = "completed";
273
+ }
274
+ return {
275
+ updated: true,
276
+ questId: quest.questId,
277
+ objectiveId: obj.objectiveId,
278
+ oldState,
279
+ newState: "completed",
280
+ questComplete: complete,
281
+ };
282
+ }
283
+ return { updated: false };
284
+ }
285
+ /**
286
+ * Handle item collection.
287
+ */
288
+ export function handleItemCollected(quest, itemId, quantity, tick) {
289
+ for (const obj of quest.objectives) {
290
+ if (obj.state !== "available" && obj.state !== "in_progress")
291
+ continue;
292
+ if (obj.type !== "collect_item")
293
+ continue;
294
+ if (obj.target?.itemId !== itemId)
295
+ continue;
296
+ obj.progress += quantity;
297
+ if (obj.state === "available") {
298
+ obj.state = "in_progress";
299
+ }
300
+ const complete = checkObjectiveComplete(obj);
301
+ if (complete) {
302
+ const oldState = obj.state;
303
+ obj.state = "completed";
304
+ unlockObjectives(quest, tick);
305
+ const questComplete = checkQuestComplete(quest);
306
+ if (questComplete) {
307
+ quest.state = "completed";
308
+ }
309
+ return {
310
+ updated: true,
311
+ questId: quest.questId,
312
+ objectiveId: obj.objectiveId,
313
+ oldState,
314
+ newState: "completed",
315
+ questComplete,
316
+ };
317
+ }
318
+ return {
319
+ updated: true,
320
+ questId: quest.questId,
321
+ objectiveId: obj.objectiveId,
322
+ oldState: "in_progress",
323
+ newState: "in_progress",
324
+ };
325
+ }
326
+ return { updated: false };
327
+ }
328
+ /**
329
+ * Handle dialogue choice selection (Phase 23 integration).
330
+ */
331
+ export function handleDialogueChoice(quest, choiceId, tick) {
332
+ for (const obj of quest.objectives) {
333
+ if (obj.state !== "available" && obj.state !== "in_progress")
334
+ continue;
335
+ if (obj.type !== "dialogue_choice")
336
+ continue;
337
+ if (obj.target?.dialogueChoice !== choiceId)
338
+ continue;
339
+ const oldState = obj.state;
340
+ obj.state = "completed";
341
+ unlockObjectives(quest, tick);
342
+ const complete = checkQuestComplete(quest);
343
+ if (complete) {
344
+ quest.state = "completed";
345
+ }
346
+ return {
347
+ updated: true,
348
+ questId: quest.questId,
349
+ objectiveId: obj.objectiveId,
350
+ oldState,
351
+ newState: "completed",
352
+ questComplete: complete,
353
+ };
354
+ }
355
+ return { updated: false };
356
+ }
357
+ // ── Time-based Objectives ─────────────────────────────────────────────────────
358
+ /**
359
+ * Check time limits and update wait_duration objectives.
360
+ * Call this periodically (e.g., once per second or per tick).
361
+ */
362
+ export function processTimeBasedObjectives(quest, currentTick, tickHz) {
363
+ const results = [];
364
+ const elapsed_s = (currentTick - (quest.acceptedAtTick ?? 0)) / tickHz;
365
+ // Check overall quest time limit
366
+ if (quest.timeLimit_s && elapsed_s >= quest.timeLimit_s) {
367
+ if (quest.state === "active") {
368
+ const oldState = quest.state;
369
+ quest.state = "failed";
370
+ quest.objectives.forEach((o) => {
371
+ if (o.state === "available" || o.state === "in_progress") {
372
+ o.state = "failed";
373
+ }
374
+ });
375
+ return [
376
+ {
377
+ updated: true,
378
+ questId: quest.questId,
379
+ oldState,
380
+ newState: "failed",
381
+ questFailed: true,
382
+ },
383
+ ];
384
+ }
385
+ }
386
+ // Check individual objective time limits and wait_duration
387
+ for (const obj of quest.objectives) {
388
+ if (obj.state !== "available" && obj.state !== "in_progress")
389
+ continue;
390
+ // Check objective time limit
391
+ if (obj.timeLimit_s && obj.activatedAtTick !== undefined) {
392
+ const objElapsed_s = (currentTick - obj.activatedAtTick) / tickHz;
393
+ if (objElapsed_s > obj.timeLimit_s) {
394
+ const oldState = obj.state;
395
+ obj.state = "failed";
396
+ // Fail the quest if a required objective fails
397
+ if (!obj.hidden) {
398
+ quest.state = "failed";
399
+ results.push({
400
+ updated: true,
401
+ questId: quest.questId,
402
+ objectiveId: obj.objectiveId,
403
+ oldState,
404
+ newState: "failed",
405
+ questFailed: true,
406
+ });
407
+ continue;
408
+ }
409
+ results.push({
410
+ updated: true,
411
+ questId: quest.questId,
412
+ objectiveId: obj.objectiveId,
413
+ oldState,
414
+ newState: "failed",
415
+ });
416
+ continue;
417
+ }
418
+ }
419
+ // Handle wait_duration
420
+ if (obj.type === "wait_duration" && obj.activatedAtTick !== undefined) {
421
+ const objElapsed_s = (currentTick - obj.activatedAtTick) / tickHz;
422
+ if (objElapsed_s >= (obj.count ?? 0)) {
423
+ const oldState = obj.state;
424
+ obj.state = "completed";
425
+ unlockObjectives(quest, currentTick);
426
+ const complete = checkQuestComplete(quest);
427
+ if (complete) {
428
+ quest.state = "completed";
429
+ }
430
+ results.push({
431
+ updated: true,
432
+ questId: quest.questId,
433
+ objectiveId: obj.objectiveId,
434
+ oldState,
435
+ newState: "completed",
436
+ questComplete: complete,
437
+ });
438
+ }
439
+ }
440
+ }
441
+ return results;
442
+ }
443
+ // ── Utility Functions ─────────────────────────────────────────────────────────
444
+ /** Get all active quests for an entity. */
445
+ export function getActiveQuests(registry, entityId) {
446
+ const log = registry.logs.get(entityId);
447
+ if (!log)
448
+ return [];
449
+ return Array.from(log.active.values());
450
+ }
451
+ /** Get quest by ID from any category. */
452
+ export function getQuest(log, questId) {
453
+ if (log.active.has(questId)) {
454
+ return { quest: log.active.get(questId), category: "active" };
455
+ }
456
+ if (log.completed.has(questId)) {
457
+ return { quest: log.completed.get(questId), category: "completed" };
458
+ }
459
+ if (log.failed.has(questId)) {
460
+ return { quest: log.failed.get(questId), category: "failed" };
461
+ }
462
+ return undefined;
463
+ }
464
+ /** Get progress summary for a quest. */
465
+ export function getQuestProgress(quest) {
466
+ const visible = quest.objectives.filter((o) => !o.hidden);
467
+ const total = visible.length;
468
+ const completed = visible.filter((o) => o.state === "completed").length;
469
+ const failed = visible.filter((o) => o.state === "failed").length;
470
+ return {
471
+ total,
472
+ completed,
473
+ failed,
474
+ percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
475
+ };
476
+ }
477
+ /** Serialize quest registry to JSON-friendly format. */
478
+ export function serializeQuestRegistry(registry) {
479
+ return {
480
+ templates: Array.from(registry.templates.entries()),
481
+ logs: Array.from(registry.logs.entries()).map(([entityId, log]) => [
482
+ entityId,
483
+ {
484
+ entityId: log.entityId,
485
+ active: Array.from(log.active.entries()),
486
+ completed: Array.from(log.completed.entries()),
487
+ failed: Array.from(log.failed.entries()),
488
+ },
489
+ ]),
490
+ history: registry.history,
491
+ };
492
+ }
493
+ /** Deserialize quest registry. */
494
+ export function deserializeQuestRegistry(data) {
495
+ const registry = createQuestRegistry();
496
+ if (typeof data !== "object" || data === null) {
497
+ return registry;
498
+ }
499
+ const d = data;
500
+ if (Array.isArray(d.templates)) {
501
+ for (const [id, quest] of d.templates) {
502
+ registry.templates.set(id, quest);
503
+ }
504
+ }
505
+ if (Array.isArray(d.logs)) {
506
+ for (const [entityId, logData] of d.logs) {
507
+ const log = logData;
508
+ registry.logs.set(entityId, {
509
+ entityId: log.entityId,
510
+ active: new Map(log.active),
511
+ completed: new Map(log.completed),
512
+ failed: new Map(log.failed),
513
+ });
514
+ }
515
+ }
516
+ if (Array.isArray(d.history)) {
517
+ registry.history = d.history;
518
+ }
519
+ return registry;
520
+ }
@@ -0,0 +1,75 @@
1
+ import type { Q } from "./units.js";
2
+ import type { WorldState } from "./sim/world.js";
3
+ import type { ImpactEvent } from "./sim/events.js";
4
+ import type { RelationshipGraph } from "./relationships.js";
5
+ /** Morale impact when witnessing an event involving someone you know. */
6
+ export interface MoraleImpact {
7
+ observerId: number;
8
+ targetId: number;
9
+ event: "friend_injured" | "friend_killed" | "enemy_defeated" | "enemy_injured" | "betrayal";
10
+ delta_Q: Q;
11
+ }
12
+ /**
13
+ * Compute morale impacts for all observers of a combat event.
14
+ */
15
+ export declare function computeCombatMoraleImpacts(graph: RelationshipGraph, world: WorldState, impact: ImpactEvent): MoraleImpact[];
16
+ /**
17
+ * Compute morale impact from betrayal detection.
18
+ */
19
+ export declare function computeBetrayalMoraleImpacts(graph: RelationshipGraph, world: WorldState, attackerId: number, victimId: number, tick: number): MoraleImpact[];
20
+ /**
21
+ * Compute total teaching effectiveness multiplier.
22
+ * Combines base skill with relationship factors.
23
+ */
24
+ export declare function computeTeachingEffectiveness(graph: RelationshipGraph, teacherId: number, learnerId: number, baseEffectiveness: number): number;
25
+ /** Factors affecting combat AI decisions. */
26
+ export interface CombatDecisionFactors {
27
+ /** Whether entity will protect the target */
28
+ willProtect: boolean;
29
+ /** Whether entity will avoid harming the target */
30
+ willAvoidHarm: boolean;
31
+ /** Aggression modifier toward target */
32
+ aggressionModifier: number;
33
+ /** Coordination bonus when fighting alongside */
34
+ coordinationBonus: number;
35
+ }
36
+ /**
37
+ * Compute combat decision factors based on relationship.
38
+ */
39
+ export declare function getCombatDecisionFactors(graph: RelationshipGraph, entityId: number, targetId: number): CombatDecisionFactors;
40
+ /**
41
+ * Determine if entity should switch targets to protect an ally.
42
+ */
43
+ export declare function shouldProtectAlly(graph: RelationshipGraph, protectorId: number, allyId: number, threatId: number): boolean;
44
+ /** Dialogue availability based on relationship. */
45
+ export interface DialogueAvailability {
46
+ canAskFavors: boolean;
47
+ canNegotiate: boolean;
48
+ canIntimidate: boolean;
49
+ persuasionBonus: number;
50
+ }
51
+ /**
52
+ * Compute what dialogue options are available.
53
+ */
54
+ export declare function getDialogueAvailability(graph: RelationshipGraph, speakerId: number, listenerId: number): DialogueAvailability;
55
+ /**
56
+ * Record events from combat resolution.
57
+ */
58
+ export declare function recordCombatOutcome(graph: RelationshipGraph, impact: ImpactEvent, outcome: "hit" | "blocked" | "parried" | "missed", tick: number): void;
59
+ /**
60
+ * Record cooperation between entities.
61
+ */
62
+ export declare function recordCooperation(graph: RelationshipGraph, entityA: number, entityB: number, success: boolean, tick: number): void;
63
+ /**
64
+ * Record that one entity saved another.
65
+ */
66
+ export declare function recordRescue(graph: RelationshipGraph, rescuerId: number, rescuedId: number, tick: number): void;
67
+ /** Find entities that form a cohesive group based on relationships. */
68
+ export declare function findCohesiveGroup(graph: RelationshipGraph, seedEntityId: number, minAffinity_Q?: Q): number[];
69
+ /**
70
+ * Check if a group of entities can work together effectively.
71
+ */
72
+ export declare function computeGroupCohesion(graph: RelationshipGraph, groupIds: number[]): {
73
+ cohesion_Q: Q;
74
+ trust_Q: Q;
75
+ };