@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,219 @@
1
+ // src/relationships-effects.ts — Phase 42: Relationship Effects Integration
2
+ //
3
+ // Integration of relationship graph with morale, teaching, and combat systems.
4
+ import { q, SCALE } from "./units.js";
5
+ import { getRelationship, recordRelationshipEvent, recordBetrayal, computeTeachingRelationshipMultiplier, } from "./relationships.js";
6
+ /**
7
+ * Compute morale impacts for all observers of a combat event.
8
+ */
9
+ export function computeCombatMoraleImpacts(graph, world, impact) {
10
+ const impacts = [];
11
+ // Get all potential observers (simplified: all entities in world)
12
+ // In practice, this would use spatial queries for entities within perception range
13
+ const observers = world.entities;
14
+ for (const observer of observers) {
15
+ if (observer.id === impact.attackerId || observer.id === impact.targetId)
16
+ continue;
17
+ // Check relationship with victim
18
+ const rVictim = getRelationship(graph, observer.id, impact.targetId);
19
+ if (rVictim && rVictim.affinity_Q > q(0.3)) {
20
+ // Friend was hit
21
+ const severity = impact.energy_J > 500 ? "friend_killed" : "friend_injured";
22
+ impacts.push({
23
+ observerId: observer.id,
24
+ targetId: impact.targetId,
25
+ event: severity,
26
+ delta_Q: Math.round(-rVictim.affinity_Q * (severity === "friend_killed" ? 0.5 : 0.2)),
27
+ });
28
+ }
29
+ // Check relationship with attacker
30
+ const rAttacker = getRelationship(graph, observer.id, impact.attackerId);
31
+ if (rAttacker && rAttacker.affinity_Q < -q(0.3)) {
32
+ // Enemy succeeded in hitting someone
33
+ impacts.push({
34
+ observerId: observer.id,
35
+ targetId: impact.attackerId,
36
+ event: "enemy_defeated",
37
+ delta_Q: Math.round(rAttacker.affinity_Q * 0.1), // Negative = bad for morale
38
+ });
39
+ }
40
+ }
41
+ return impacts;
42
+ }
43
+ /**
44
+ * Compute morale impact from betrayal detection.
45
+ */
46
+ export function computeBetrayalMoraleImpacts(graph, world, attackerId, victimId, tick) {
47
+ const impacts = [];
48
+ // Record betrayal in relationship graph
49
+ const betrayalResult = recordBetrayal(graph, attackerId, victimId, tick);
50
+ if (!betrayalResult.isBetrayal)
51
+ return impacts;
52
+ // Find all witnesses who care about the victim
53
+ const witnesses = world.entities.filter((e) => e.id !== attackerId && e.id !== victimId);
54
+ for (const witness of witnesses) {
55
+ const r = getRelationship(graph, witness.id, victimId);
56
+ if (r && r.affinity_Q > q(0.2)) {
57
+ impacts.push({
58
+ observerId: witness.id,
59
+ targetId: victimId,
60
+ event: "betrayal",
61
+ delta_Q: betrayalResult.witnessMoralePenalty_Q,
62
+ });
63
+ }
64
+ }
65
+ return impacts;
66
+ }
67
+ // ── Teaching Integration ──────────────────────────────────────────────────────
68
+ /**
69
+ * Compute total teaching effectiveness multiplier.
70
+ * Combines base skill with relationship factors.
71
+ */
72
+ export function computeTeachingEffectiveness(graph, teacherId, learnerId, baseEffectiveness) {
73
+ const relationshipMul = computeTeachingRelationshipMultiplier(graph, teacherId, learnerId);
74
+ return baseEffectiveness * relationshipMul;
75
+ }
76
+ /**
77
+ * Compute combat decision factors based on relationship.
78
+ */
79
+ export function getCombatDecisionFactors(graph, entityId, targetId) {
80
+ const r = getRelationship(graph, entityId, targetId);
81
+ if (!r) {
82
+ return {
83
+ willProtect: false,
84
+ willAvoidHarm: false,
85
+ aggressionModifier: 0,
86
+ coordinationBonus: 0,
87
+ };
88
+ }
89
+ const affinityNorm = r.affinity_Q / SCALE.Q; // -1 to 1
90
+ const trustNorm = r.trust_Q / SCALE.Q; // 0 to 1
91
+ return {
92
+ willProtect: r.affinity_Q > q(0.5) && r.trust_Q > q(0.3),
93
+ willAvoidHarm: r.affinity_Q > q(0.2),
94
+ aggressionModifier: -affinityNorm * 0.5, // Negative affinity = more aggressive
95
+ coordinationBonus: r.bond === "mentor" || r.bond === "student"
96
+ ? 0.2
97
+ : trustNorm * 0.1,
98
+ };
99
+ }
100
+ /**
101
+ * Determine if entity should switch targets to protect an ally.
102
+ */
103
+ export function shouldProtectAlly(graph, protectorId, allyId, threatId) {
104
+ const rAlly = getRelationship(graph, protectorId, allyId);
105
+ const rThreat = getRelationship(graph, protectorId, threatId);
106
+ if (!rAlly)
107
+ return false;
108
+ // Will protect if ally is friend and threat is not a closer friend
109
+ const allyValue = rAlly.affinity_Q + rAlly.trust_Q;
110
+ const threatValue = rThreat
111
+ ? rThreat.affinity_Q + rThreat.trust_Q
112
+ : -SCALE.Q; // Unknown = neutral/negative
113
+ return allyValue > q(0.5) && allyValue > threatValue;
114
+ }
115
+ /**
116
+ * Compute what dialogue options are available.
117
+ */
118
+ export function getDialogueAvailability(graph, speakerId, listenerId) {
119
+ const r = getRelationship(graph, speakerId, listenerId);
120
+ if (!r) {
121
+ return {
122
+ canAskFavors: false,
123
+ canNegotiate: true,
124
+ canIntimidate: false,
125
+ persuasionBonus: 0,
126
+ };
127
+ }
128
+ const affinityNorm = r.affinity_Q / SCALE.Q;
129
+ const trustNorm = r.trust_Q / SCALE.Q;
130
+ return {
131
+ canAskFavors: r.affinity_Q > q(0.4) && r.trust_Q > q(0.3),
132
+ canNegotiate: r.affinity_Q > -q(0.3), // Can negotiate unless enemies
133
+ canIntimidate: r.trust_Q < q(0.3) || r.affinity_Q < -q(0.2),
134
+ persuasionBonus: affinityNorm * 0.2 + trustNorm * 0.1,
135
+ };
136
+ }
137
+ // ── Event Recording ───────────────────────────────────────────────────────────
138
+ /**
139
+ * Record events from combat resolution.
140
+ */
141
+ export function recordCombatOutcome(graph, impact, outcome, tick) {
142
+ if (outcome === "hit") {
143
+ // If significant damage, record as negative event for relationship
144
+ if (impact.energy_J > 100) {
145
+ recordRelationshipEvent(graph, impact.attackerId, impact.targetId, {
146
+ tick,
147
+ type: "insult", // Attacking is insulting
148
+ magnitude_Q: Math.min(q(0.1), Math.round(impact.energy_J / 1000)),
149
+ description: `Attacked in combat (${outcome})`,
150
+ });
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Record cooperation between entities.
156
+ */
157
+ export function recordCooperation(graph, entityA, entityB, success, tick) {
158
+ recordRelationshipEvent(graph, entityA, entityB, {
159
+ tick,
160
+ type: "fought_alongside",
161
+ magnitude_Q: success ? q(0.15) : q(0.05),
162
+ description: success ? "Successful cooperation" : "Attempted cooperation",
163
+ });
164
+ }
165
+ /**
166
+ * Record that one entity saved another.
167
+ */
168
+ export function recordRescue(graph, rescuerId, rescuedId, tick) {
169
+ recordRelationshipEvent(graph, rescuerId, rescuedId, {
170
+ tick,
171
+ type: "saved",
172
+ magnitude_Q: q(0.4),
173
+ description: "Saved from danger",
174
+ });
175
+ }
176
+ // ── Group Formation ────────────────────────────────────────────────────────────
177
+ /** Find entities that form a cohesive group based on relationships. */
178
+ export function findCohesiveGroup(graph, seedEntityId, minAffinity_Q = q(0.2)) {
179
+ const group = new Set([seedEntityId]);
180
+ const toCheck = [seedEntityId];
181
+ while (toCheck.length > 0) {
182
+ const current = toCheck.pop();
183
+ const relationships = Array.from(graph.relationships.values()).filter((r) => (r.entityA === current || r.entityB === current) && r.affinity_Q >= minAffinity_Q);
184
+ for (const r of relationships) {
185
+ const other = r.entityA === current ? r.entityB : r.entityA;
186
+ if (!group.has(other)) {
187
+ group.add(other);
188
+ toCheck.push(other);
189
+ }
190
+ }
191
+ }
192
+ return Array.from(group);
193
+ }
194
+ /**
195
+ * Check if a group of entities can work together effectively.
196
+ */
197
+ export function computeGroupCohesion(graph, groupIds) {
198
+ if (groupIds.length < 2)
199
+ return { cohesion_Q: q(0.5), trust_Q: q(0.5) };
200
+ let totalAffinity = 0;
201
+ let totalTrust = 0;
202
+ let pairCount = 0;
203
+ for (let i = 0; i < groupIds.length; i++) {
204
+ for (let j = i + 1; j < groupIds.length; j++) {
205
+ const rel = getRelationship(graph, groupIds[i], groupIds[j]);
206
+ if (rel) {
207
+ totalAffinity += rel.affinity_Q;
208
+ totalTrust += rel.trust_Q;
209
+ pairCount++;
210
+ }
211
+ }
212
+ }
213
+ if (pairCount === 0)
214
+ return { cohesion_Q: q(0.5), trust_Q: q(0.5) };
215
+ return {
216
+ cohesion_Q: Math.round(totalAffinity / pairCount),
217
+ trust_Q: Math.round(totalTrust / pairCount),
218
+ };
219
+ }
@@ -0,0 +1,104 @@
1
+ import type { Q } from "./units.js";
2
+ /** Types of events that can affect relationships. */
3
+ export type RelationshipEventType = "met" | "fought_alongside" | "betrayed" | "saved" | "deceived" | "gift_given" | "insult" | "bonded" | "separated";
4
+ /** A single event in the relationship history. */
5
+ export interface RelationshipEvent {
6
+ tick: number;
7
+ type: RelationshipEventType;
8
+ magnitude_Q: Q;
9
+ description?: string;
10
+ }
11
+ /** The nature of the social bond between two entities. */
12
+ export type SocialBond = "none" | "acquaintance" | "friend" | "close_friend" | "rival" | "enemy" | "mentor" | "student" | "family" | "romantic_partner";
13
+ /** Relationship between two entities. */
14
+ export interface Relationship {
15
+ entityA: number;
16
+ entityB: number;
17
+ /** Affinity: -1.0 (hatred) to +1.0 (love) */
18
+ affinity_Q: Q;
19
+ /** Trust: 0.0 (none) to 1.0 (absolute) */
20
+ trust_Q: Q;
21
+ /** Current social bond classification */
22
+ bond: SocialBond;
23
+ /** Chronicle of interactions */
24
+ history: RelationshipEvent[];
25
+ /** Tick when relationship was established */
26
+ establishedAtTick: number;
27
+ /** Last interaction tick */
28
+ lastInteractionTick: number;
29
+ }
30
+ /** Global relationship graph storage. */
31
+ export interface RelationshipGraph {
32
+ relationships: Map<string, Relationship>;
33
+ /** Entity-specific indices for quick lookup */
34
+ entityIndex: Map<number, Set<string>>;
35
+ }
36
+ /** Create a new empty relationship graph. */
37
+ export declare function createRelationshipGraph(): RelationshipGraph;
38
+ /**
39
+ * Get relationship between two entities.
40
+ * Returns undefined if no relationship exists.
41
+ */
42
+ export declare function getRelationship(graph: RelationshipGraph, entityA: number, entityB: number): Relationship | undefined;
43
+ /**
44
+ * Check if two entities have an existing relationship.
45
+ */
46
+ export declare function hasRelationship(graph: RelationshipGraph, entityA: number, entityB: number): boolean;
47
+ /**
48
+ * Get all relationships for an entity.
49
+ */
50
+ export declare function getEntityRelationshipsList(graph: RelationshipGraph, entityId: number): Relationship[];
51
+ /**
52
+ * Get all entities related to a given entity.
53
+ */
54
+ export declare function getRelatedEntities(graph: RelationshipGraph, entityId: number): number[];
55
+ /**
56
+ * Establish a new relationship between two entities.
57
+ */
58
+ export declare function establishRelationship(graph: RelationshipGraph, entityA: number, entityB: number, tick: number, initialAffinity_Q?: Q, initialTrust_Q?: Q): Relationship;
59
+ /**
60
+ * Record a relationship event and update affinity/trust.
61
+ */
62
+ export declare function recordRelationshipEvent(graph: RelationshipGraph, entityA: number, entityB: number, event: Omit<RelationshipEvent, "tick"> & {
63
+ tick: number;
64
+ }): Relationship | undefined;
65
+ /**
66
+ * Classify the social bond based on affinity, trust, and history.
67
+ */
68
+ export declare function classifyBond(affinity_Q: Q, trust_Q: Q, history: RelationshipEvent[]): SocialBond;
69
+ /**
70
+ * Apply time-based decay to relationships.
71
+ * Call periodically (e.g., daily or weekly in simulation time).
72
+ */
73
+ export declare function decayRelationships(graph: RelationshipGraph, currentTick: number, decayRatePerTick?: number): void;
74
+ /** Check if entity A would consider entity B a friend. */
75
+ export declare function isFriend(graph: RelationshipGraph, entityA: number, entityB: number): boolean;
76
+ /** Check if entity A would consider entity B an enemy. */
77
+ export declare function isEnemy(graph: RelationshipGraph, entityA: number, entityB: number): boolean;
78
+ /** Check if entity A trusts entity B enough for combat cooperation. */
79
+ export declare function hasCombatTrust(graph: RelationshipGraph, entityA: number, entityB: number): boolean;
80
+ /** Get the effective relationship modifier for morale effects. */
81
+ export declare function getMoraleModifier(graph: RelationshipGraph, observer: number, target: number): Q;
82
+ /** Result of betrayal check. */
83
+ export interface BetrayalResult {
84
+ isBetrayal: boolean;
85
+ severity_Q: Q;
86
+ /** Morale penalty for witnesses who care about the victim */
87
+ witnessMoralePenalty_Q: Q;
88
+ }
89
+ /**
90
+ * Check if harming someone constitutes betrayal.
91
+ */
92
+ export declare function checkBetrayal(graph: RelationshipGraph, attackerId: number, victimId: number): BetrayalResult;
93
+ /**
94
+ * Record a betrayal event and update relationships.
95
+ */
96
+ export declare function recordBetrayal(graph: RelationshipGraph, attackerId: number, victimId: number, tick: number): BetrayalResult;
97
+ /**
98
+ * Compute teaching effectiveness multiplier based on relationship.
99
+ */
100
+ export declare function computeTeachingRelationshipMultiplier(graph: RelationshipGraph, teacherId: number, learnerId: number): number;
101
+ /** Serialize relationship graph to JSON-friendly format. */
102
+ export declare function serializeRelationshipGraph(graph: RelationshipGraph): unknown;
103
+ /** Deserialize relationship graph. */
104
+ export declare function deserializeRelationshipGraph(data: unknown): RelationshipGraph;
@@ -0,0 +1,347 @@
1
+ // src/relationships.ts — Phase 42: Personal Relationship Graph
2
+ //
3
+ // Individual-to-individual relationships — the social fabric that makes RPGs feel alive.
4
+ // Affects morale, teaching effectiveness, betrayal probability, and dialogue options.
5
+ import { SCALE, q, clampQ } from "./units.js";
6
+ /** Key for storing relationships (ordered pair). */
7
+ function relationshipKey(entityA, entityB) {
8
+ // Always store with smaller ID first for consistency
9
+ return entityA < entityB ? `${entityA}:${entityB}` : `${entityB}:${entityA}`;
10
+ }
11
+ /** Create a new empty relationship graph. */
12
+ export function createRelationshipGraph() {
13
+ return {
14
+ relationships: new Map(),
15
+ entityIndex: new Map(),
16
+ };
17
+ }
18
+ /** Get or create entity index entry. */
19
+ function getEntityRelationships(graph, entityId) {
20
+ let set = graph.entityIndex.get(entityId);
21
+ if (!set) {
22
+ set = new Set();
23
+ graph.entityIndex.set(entityId, set);
24
+ }
25
+ return set;
26
+ }
27
+ /**
28
+ * Get relationship between two entities.
29
+ * Returns undefined if no relationship exists.
30
+ */
31
+ export function getRelationship(graph, entityA, entityB) {
32
+ if (entityA === entityB)
33
+ return undefined;
34
+ const key = relationshipKey(entityA, entityB);
35
+ return graph.relationships.get(key);
36
+ }
37
+ /**
38
+ * Check if two entities have an existing relationship.
39
+ */
40
+ export function hasRelationship(graph, entityA, entityB) {
41
+ return getRelationship(graph, entityA, entityB) !== undefined;
42
+ }
43
+ /**
44
+ * Get all relationships for an entity.
45
+ */
46
+ export function getEntityRelationshipsList(graph, entityId) {
47
+ const keys = graph.entityIndex.get(entityId);
48
+ if (!keys)
49
+ return [];
50
+ return Array.from(keys)
51
+ .map((key) => graph.relationships.get(key))
52
+ .filter((r) => r !== undefined);
53
+ }
54
+ /**
55
+ * Get all entities related to a given entity.
56
+ */
57
+ export function getRelatedEntities(graph, entityId) {
58
+ const relationships = getEntityRelationshipsList(graph, entityId);
59
+ return relationships.map((r) => (r.entityA === entityId ? r.entityB : r.entityA));
60
+ }
61
+ // ── Relationship Creation & Modification ──────────────────────────────────────
62
+ /**
63
+ * Establish a new relationship between two entities.
64
+ */
65
+ export function establishRelationship(graph, entityA, entityB, tick, initialAffinity_Q = q(0), initialTrust_Q = q(0.1)) {
66
+ if (entityA === entityB) {
67
+ throw new Error("Cannot establish relationship with self");
68
+ }
69
+ const key = relationshipKey(entityA, entityB);
70
+ // Check if relationship already exists
71
+ const existing = graph.relationships.get(key);
72
+ if (existing)
73
+ return existing;
74
+ const affinity_Q = clampQ(initialAffinity_Q, -SCALE.Q, SCALE.Q);
75
+ const trust_Q = clampQ(initialTrust_Q, q(0), SCALE.Q);
76
+ const relationship = {
77
+ entityA,
78
+ entityB,
79
+ affinity_Q,
80
+ trust_Q,
81
+ bond: classifyBond(affinity_Q, trust_Q, []),
82
+ history: [],
83
+ establishedAtTick: tick,
84
+ lastInteractionTick: tick,
85
+ };
86
+ graph.relationships.set(key, relationship);
87
+ getEntityRelationships(graph, entityA).add(key);
88
+ getEntityRelationships(graph, entityB).add(key);
89
+ return relationship;
90
+ }
91
+ /**
92
+ * Record a relationship event and update affinity/trust.
93
+ */
94
+ export function recordRelationshipEvent(graph, entityA, entityB, event) {
95
+ if (entityA === entityB)
96
+ return undefined;
97
+ const key = relationshipKey(entityA, entityB);
98
+ let relationship = graph.relationships.get(key);
99
+ // Auto-establish relationship if it doesn't exist
100
+ if (!relationship) {
101
+ relationship = establishRelationship(graph, entityA, entityB, event.tick);
102
+ }
103
+ // Add event to history
104
+ const evt = {
105
+ tick: event.tick,
106
+ type: event.type,
107
+ magnitude_Q: event.magnitude_Q,
108
+ };
109
+ if (event.description !== undefined)
110
+ evt.description = event.description;
111
+ relationship.history.push(evt);
112
+ // Update affinity and trust based on event type
113
+ const { affinityDelta, trustDelta } = computeDeltasFromEvent(event.type, event.magnitude_Q);
114
+ relationship.affinity_Q = clampQ((relationship.affinity_Q + affinityDelta), -SCALE.Q, SCALE.Q);
115
+ relationship.trust_Q = clampQ((relationship.trust_Q + trustDelta), q(0), SCALE.Q);
116
+ relationship.lastInteractionTick = event.tick;
117
+ // Reclassify bond
118
+ relationship.bond = classifyBond(relationship.affinity_Q, relationship.trust_Q, relationship.history);
119
+ return relationship;
120
+ }
121
+ /**
122
+ * Compute affinity and trust deltas from event type.
123
+ */
124
+ function computeDeltasFromEvent(type, magnitude_Q) {
125
+ // Scale: magnitude_Q (0-10000) * factor -> delta_Q
126
+ // Multipliers are scaled to produce meaningful changes (0.5-1.0 range possible)
127
+ const mul = (n) => Math.round(magnitude_Q * n);
128
+ switch (type) {
129
+ case "met":
130
+ return { affinityDelta: mul(0.1), trustDelta: mul(0.05) };
131
+ case "fought_alongside":
132
+ return { affinityDelta: mul(0.3), trustDelta: mul(0.6) };
133
+ case "saved":
134
+ return { affinityDelta: mul(0.8), trustDelta: mul(0.7) };
135
+ case "betrayed":
136
+ // Severe penalty - can swing positive to negative
137
+ return { affinityDelta: mul(-1.8), trustDelta: mul(-2.0) };
138
+ case "deceived":
139
+ return { affinityDelta: mul(-0.6), trustDelta: mul(-1.0) };
140
+ case "gift_given":
141
+ return { affinityDelta: mul(0.2), trustDelta: mul(0.1) };
142
+ case "insult":
143
+ return { affinityDelta: mul(-0.3), trustDelta: mul(-0.1) };
144
+ case "bonded":
145
+ return { affinityDelta: mul(0.6), trustDelta: mul(0.5) };
146
+ case "separated":
147
+ // Gradual decay - handled by decay function instead
148
+ return { affinityDelta: q(0), trustDelta: q(0) };
149
+ default:
150
+ return { affinityDelta: q(0), trustDelta: q(0) };
151
+ }
152
+ }
153
+ /**
154
+ * Classify the social bond based on affinity, trust, and history.
155
+ */
156
+ export function classifyBond(affinity_Q, trust_Q, history) {
157
+ // Check for negative bonds first
158
+ if (affinity_Q < -q(0.6) && trust_Q < q(0.3))
159
+ return "enemy";
160
+ if (affinity_Q < -q(0.3) && affinity_Q >= -q(0.6))
161
+ return "rival";
162
+ // Check for special bonds in history
163
+ const hasMentorEvent = history.some((e) => e.type === "bonded" && e.magnitude_Q > q(0.7));
164
+ if (hasMentorEvent) {
165
+ return affinity_Q > q(0.5) ? "mentor" : "student";
166
+ }
167
+ // Check for romantic bond
168
+ const romanticEvent = history.some((e) => e.type === "bonded" && e.description?.includes("romantic"));
169
+ if (romanticEvent && affinity_Q > q(0.7) && trust_Q > q(0.6))
170
+ return "romantic_partner";
171
+ // Check for family
172
+ const familyEvent = history.some((e) => e.description?.includes("family"));
173
+ if (familyEvent)
174
+ return "family";
175
+ // Standard positive bonds
176
+ if (affinity_Q >= q(0.7) && trust_Q >= q(0.5))
177
+ return "close_friend";
178
+ if (affinity_Q >= q(0.3))
179
+ return "friend";
180
+ // Acquaintance covers neutral to slightly positive, and slightly negative
181
+ if (affinity_Q >= -q(0.3))
182
+ return "acquaintance";
183
+ return "none";
184
+ }
185
+ // ── Relationship Decay ────────────────────────────────────────────────────────
186
+ /**
187
+ * Apply time-based decay to relationships.
188
+ * Call periodically (e.g., daily or weekly in simulation time).
189
+ */
190
+ export function decayRelationships(graph, currentTick, decayRatePerTick = 0.0001) {
191
+ for (const relationship of graph.relationships.values()) {
192
+ const timeSinceInteraction = currentTick - relationship.lastInteractionTick;
193
+ if (timeSinceInteraction > 1000) {
194
+ // Start decay after 1000 ticks of no interaction
195
+ const decayAmount = Math.round(timeSinceInteraction * decayRatePerTick * SCALE.Q);
196
+ // Decay affinity toward neutral (0)
197
+ if (relationship.affinity_Q > 0) {
198
+ relationship.affinity_Q = Math.max(0, relationship.affinity_Q - decayAmount);
199
+ }
200
+ else if (relationship.affinity_Q < 0) {
201
+ relationship.affinity_Q = Math.min(0, relationship.affinity_Q + decayAmount);
202
+ }
203
+ // Decay trust slightly (people forget trust too)
204
+ relationship.trust_Q = Math.max(0, relationship.trust_Q - decayAmount / 2);
205
+ // Reclassify bond
206
+ relationship.bond = classifyBond(relationship.affinity_Q, relationship.trust_Q, relationship.history);
207
+ }
208
+ }
209
+ }
210
+ // ── Relationship Queries ──────────────────────────────────────────────────────
211
+ /** Check if entity A would consider entity B a friend. */
212
+ export function isFriend(graph, entityA, entityB) {
213
+ const r = getRelationship(graph, entityA, entityB);
214
+ if (!r)
215
+ return false;
216
+ return r.affinity_Q >= q(0.3) && ["acquaintance", "friend", "close_friend", "mentor", "student", "romantic_partner", "family"].includes(r.bond);
217
+ }
218
+ /** Check if entity A would consider entity B an enemy. */
219
+ export function isEnemy(graph, entityA, entityB) {
220
+ const r = getRelationship(graph, entityA, entityB);
221
+ if (!r)
222
+ return false;
223
+ return r.affinity_Q < -q(0.3) || r.bond === "enemy" || r.bond === "rival";
224
+ }
225
+ /** Check if entity A trusts entity B enough for combat cooperation. */
226
+ export function hasCombatTrust(graph, entityA, entityB) {
227
+ const r = getRelationship(graph, entityA, entityB);
228
+ if (!r)
229
+ return false;
230
+ return r.trust_Q >= q(0.4);
231
+ }
232
+ /** Get the effective relationship modifier for morale effects. */
233
+ export function getMoraleModifier(graph, observer, target) {
234
+ const r = getRelationship(graph, observer, target);
235
+ if (!r)
236
+ return q(0);
237
+ // Positive affinity = morale boost when target succeeds
238
+ // Negative affinity = morale boost when target fails
239
+ return r.affinity_Q;
240
+ }
241
+ /**
242
+ * Check if harming someone constitutes betrayal.
243
+ */
244
+ export function checkBetrayal(graph, attackerId, victimId) {
245
+ const r = getRelationship(graph, attackerId, victimId);
246
+ if (!r) {
247
+ return {
248
+ isBetrayal: false,
249
+ severity_Q: q(0),
250
+ witnessMoralePenalty_Q: q(0),
251
+ };
252
+ }
253
+ // Betrayal occurs when affinity is positive but attacker harms victim
254
+ const isBetrayal = r.affinity_Q > q(0.5);
255
+ if (!isBetrayal) {
256
+ return {
257
+ isBetrayal: false,
258
+ severity_Q: q(0),
259
+ witnessMoralePenalty_Q: q(0),
260
+ };
261
+ }
262
+ // Severity based on how positive the relationship was (0.6 to 1.0 range, as Q)
263
+ // Higher affinity = more severe betrayal = larger magnitude
264
+ const severity_Q = Math.round(SCALE.Q * 0.6 + r.affinity_Q * 0.4);
265
+ // Witnesses who cared about the victim take morale hit (0.3 to 0.7 range, as Q)
266
+ const witnessMoralePenalty_Q = Math.round(SCALE.Q * 0.3 + r.affinity_Q * 0.4);
267
+ return {
268
+ isBetrayal: true,
269
+ severity_Q,
270
+ witnessMoralePenalty_Q,
271
+ };
272
+ }
273
+ /**
274
+ * Record a betrayal event and update relationships.
275
+ */
276
+ export function recordBetrayal(graph, attackerId, victimId, tick) {
277
+ const result = checkBetrayal(graph, attackerId, victimId);
278
+ if (result.isBetrayal) {
279
+ recordRelationshipEvent(graph, attackerId, victimId, {
280
+ tick,
281
+ type: "betrayed",
282
+ magnitude_Q: result.severity_Q,
283
+ description: "Betrayed during combat",
284
+ });
285
+ // Also damage attacker's reputation with victim's friends
286
+ const victimFriends = getEntityRelationshipsList(graph, victimId)
287
+ .filter((r) => r.affinity_Q > q(0.3) && r.entityA !== attackerId && r.entityB !== attackerId);
288
+ for (const friendRel of victimFriends) {
289
+ const friendId = friendRel.entityA === victimId ? friendRel.entityB : friendRel.entityA;
290
+ recordRelationshipEvent(graph, attackerId, friendId, {
291
+ tick,
292
+ type: "betrayed",
293
+ magnitude_Q: Math.round(result.severity_Q * 0.5),
294
+ description: `Betrayed ${victimId}`,
295
+ });
296
+ }
297
+ }
298
+ return result;
299
+ }
300
+ // ── Teaching Integration ──────────────────────────────────────────────────────
301
+ /**
302
+ * Compute teaching effectiveness multiplier based on relationship.
303
+ */
304
+ export function computeTeachingRelationshipMultiplier(graph, teacherId, learnerId) {
305
+ const r = getRelationship(graph, teacherId, learnerId);
306
+ if (!r)
307
+ return 1.0;
308
+ // Base multiplier from affinity
309
+ let multiplier = 1.0 + (r.affinity_Q / SCALE.Q) * 0.3;
310
+ // Bonus for mentor/student bond
311
+ if (r.bond === "mentor" || r.bond === "student") {
312
+ multiplier += 0.2;
313
+ }
314
+ // Trust affects how well learner accepts teaching
315
+ multiplier += (r.trust_Q / SCALE.Q) * 0.2;
316
+ return Math.max(0.5, Math.min(1.5, multiplier));
317
+ }
318
+ // ── Serialization ─────────────────────────────────────────────────────────────
319
+ /** Serialize relationship graph to JSON-friendly format. */
320
+ export function serializeRelationshipGraph(graph) {
321
+ return {
322
+ relationships: Array.from(graph.relationships.entries()),
323
+ entityIndex: Array.from(graph.entityIndex.entries()).map(([entityId, keys]) => [
324
+ entityId,
325
+ Array.from(keys),
326
+ ]),
327
+ };
328
+ }
329
+ /** Deserialize relationship graph. */
330
+ export function deserializeRelationshipGraph(data) {
331
+ const graph = createRelationshipGraph();
332
+ if (typeof data !== "object" || data === null) {
333
+ return graph;
334
+ }
335
+ const d = data;
336
+ if (Array.isArray(d.relationships)) {
337
+ for (const [key, rel] of d.relationships) {
338
+ graph.relationships.set(key, rel);
339
+ }
340
+ }
341
+ if (Array.isArray(d.entityIndex)) {
342
+ for (const [entityId, keys] of d.entityIndex) {
343
+ graph.entityIndex.set(entityId, new Set(keys));
344
+ }
345
+ }
346
+ return graph;
347
+ }