@mindot/will 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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,432 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/attachment.evaluator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * AttachmentEvaluator — models relationship bonds with other agents.
7
+ *
8
+ * Evaluates:
9
+ * - Interaction frequency and recency
10
+ * - Positive vs. negative interaction ratio
11
+ * - Dependency (how much the agent relies on this other)
12
+ * - Trust history (reliability over time)
13
+ * - Shared experience depth
14
+ *
15
+ * Produces: love, trust, belonging, loneliness
16
+ *
17
+ * Love = strong positive bond with specific other
18
+ * Trust = confidence in another's reliability and benevolence
19
+ * Belonging = sense of connection to a social group
20
+ * Loneliness = absence of meaningful attachment
21
+ *
22
+ * Attachment builds gradually (repeated positive interactions) and
23
+ * decays slowly (bonds persist through absence, but fade over time).
24
+ *
25
+ * Part of Shard 1 (Affective Layer) — runs every tick, synchronous.
26
+ */
27
+
28
+ import type {
29
+ Duration,
30
+ Tick,
31
+ SimulationContext,
32
+ ReadonlySimulationState,
33
+ StateCommands,
34
+ SimulationEvent,
35
+ } from '#core/types'
36
+ import type { CognitiveEngine, SimulationEngine, EngineResult } from '#cognition/types'
37
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
38
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
39
+ import { GenerativeModel } from '#cognition/generative.model'
40
+ import { readEffectiveParams } from '#cognition/persona.prior'
41
+
42
+ export interface AttachmentEvaluatorConfig {
43
+ /** How quickly attachment builds from positive interactions */
44
+ attachmentGrowthRate?: number
45
+ /** How slowly attachment decays during absence */
46
+ attachmentDecayRate?: number
47
+ /** Threshold for loneliness to become significant */
48
+ lonelinessThreshold?: number
49
+ /** Minimum interactions to consider a bond meaningful */
50
+ minInteractionsForBond?: number
51
+ /**
52
+ * Baseline belonging from self-awareness (0–1).
53
+ * Prevents loneliness from maxing out at boot with no social context.
54
+ * 0.35 ≈ "I know who I am" — connected to self but still meaningfully alone.
55
+ */
56
+ selfBelonging?: number
57
+ bus?: CognitiveBus
58
+ }
59
+
60
+ interface BondModel {
61
+ keid: string
62
+ attachmentStrength: number // 0-1: overall bond strength
63
+ trustLevel: number // 0-1: reliability assessment
64
+ positiveRatio: number // 0-1: proportion of positive interactions
65
+ interactionCount: number
66
+ lastInteractionTick: number
67
+ sharedExperiences: number
68
+ dependency: number // 0-1: how much agent relies on this other
69
+ }
70
+
71
+ export class AttachmentEvaluator implements SimulationEngine, CognitiveEngine {
72
+ readonly name = 'attachment-evaluator'
73
+
74
+ private _growthRate: number
75
+ private _decayRate: number
76
+ private _lonelinessThreshold: number
77
+ private _minInteractionsForBond: number
78
+ private _selfBelonging: number
79
+
80
+ // Persistent bond models across ticks
81
+ private _bonds = new Map<string, BondModel>()
82
+ // True after bonds have been rehydrated from snapshot state on first tick
83
+ private _restored = false
84
+
85
+ // Inputs from cognitive events
86
+ private _pendingInteractions: Array<{
87
+ keid: string; valence: number; intensity: number
88
+ directedAtSelf: boolean; sharedExperience: boolean
89
+ }> = []
90
+ private _cachedActiveAgents: number = 0
91
+ private _cachedBelonging: number = 0
92
+
93
+ private _bus: CognitiveBus | null = null
94
+
95
+ private readonly _model = new GenerativeModel()
96
+
97
+
98
+ constructor( config: AttachmentEvaluatorConfig = {} ){
99
+ this._bus = config.bus ?? null
100
+ this._growthRate = config.attachmentGrowthRate ?? 0.05
101
+ this._decayRate = config.attachmentDecayRate ?? 0.002
102
+ this._lonelinessThreshold = config.lonelinessThreshold ?? 0.4
103
+ this._minInteractionsForBond = config.minInteractionsForBond ?? 3
104
+ this._selfBelonging = config.selfBelonging ?? 0.35
105
+ }
106
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
107
+
108
+ /**
109
+ * Attachment strength (0–1) toward a specific agent, or 0 if no bond exists.
110
+ * Read-only accessor used by the AuditionEngine to weight conversational
111
+ * salience by relationship closeness.
112
+ */
113
+ getAttachmentScore( keid: string ): number {
114
+ return this._bonds.get( keid )?.attachmentStrength ?? 0
115
+ }
116
+
117
+ // ── Engine interface ─────────────────────────────────────
118
+
119
+ subscribes(): string[] {
120
+ return [
121
+ 'executive.prediction.formed',
122
+ 'interaction.occurred',
123
+ 'social.agents.present',
124
+ ]
125
+ }
126
+
127
+ publishes(): CognitiveEventSchema[] { return [] }
128
+
129
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
130
+ this._model.observe( e.type, e.salience )
131
+ if( e.type === 'executive.prediction.formed' ){
132
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
133
+ if( p.predictedDomains.includes('social') )
134
+ this._model.setPrecision( 'emotion.love', 1.0 + p.confidence * 0.5 )
135
+ }
136
+ if( e.type === 'interaction.occurred' ){
137
+ const p = e.payload as {
138
+ keid: string; valence: number; intensity: number
139
+ directedAtSelf: boolean; interactionType: string
140
+ }
141
+ this._pendingInteractions.push({
142
+ keid: p.keid,
143
+ valence: p.valence,
144
+ intensity: p.intensity,
145
+ directedAtSelf: p.directedAtSelf,
146
+ sharedExperience: false,
147
+ })
148
+ }
149
+ if( e.type === 'social.agents.present' ){
150
+ const p = e.payload as { activeAgents: number }
151
+ this._cachedActiveAgents = p.activeAgents
152
+ }
153
+ }
154
+
155
+ snapshot(): Record<string, unknown> { return {} }
156
+
157
+ async react(
158
+ delta: Duration,
159
+ tick: Tick,
160
+ state: ReadonlySimulationState,
161
+ _context: SimulationContext
162
+ ): Promise<EngineResult> {
163
+ const
164
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
165
+ commands: StateCommands = { metrics: [] }
166
+
167
+ // Channel A (agreeableness → attachment): how fast bonds form is refreshed each tick as
168
+ // base ⊕ persona-prior. An agreeable Will develops a higher growth rate and bonds more
169
+ // readily. (Seed existed but was ignored.)
170
+ this._growthRate = readEffectiveParams( state, 'engine-config-attachment' ).attachmentGrowthRate ?? this._growthRate
171
+
172
+ // On the very first tick after construction (including snapshot restores),
173
+ // rehydrate _bonds from attachment.bond entities written by _persistBonds().
174
+ // Without this, a restored Will starts with an empty bond Map and immediately
175
+ // overwrites all saved attachment data with zeros on the first persist cycle.
176
+ if( !this._restored ){
177
+ this._restoreFromState( state )
178
+ this._restored = true
179
+ }
180
+
181
+ // 1. Drain buffered interactions and update bond models
182
+ const interactions = this._pendingInteractions.splice( 0 )
183
+ this._processSocialSignals( interactions, tick )
184
+
185
+ // 2. Decay bonds for absent agents
186
+ this._decayBonds( delta, tick )
187
+
188
+ // 3. Compute attachment emotions from bond models
189
+ const positiveCount = interactions.filter( i => i.valence > 0.3 ).length
190
+ const
191
+ love = this._computeLove(),
192
+ trust = this._computeTrust(),
193
+ belonging = this._computeBelonging( positiveCount ),
194
+ loneliness = this._computeLoneliness( belonging )
195
+ this._cachedBelonging = belonging
196
+
197
+ commands.metrics!.push(
198
+ [ 'emotion.love', love ],
199
+ [ 'emotion.trust', trust ],
200
+ [ 'emotion.belonging', belonging ],
201
+ [ 'emotion.loneliness', loneliness ],
202
+ [ 'attachment.bond_count', this._bonds.size ],
203
+ [ 'attachment.strongest_bond', this._strongestBondStrength() ],
204
+ )
205
+
206
+ // Persist bond models as entities for other engines to read
207
+ this._persistBonds( commands, tick )
208
+
209
+ // Significant bond events
210
+ const strongestBond = this._strongestBond()
211
+ if( strongestBond && strongestBond.attachmentStrength > 0.7 )
212
+ events.push({
213
+ type: 'attachment.strong_bond',
214
+ source: this.name,
215
+ payload: {
216
+ keid: strongestBond.keid,
217
+ strength: strongestBond.attachmentStrength,
218
+ trust: strongestBond.trustLevel,
219
+ },
220
+ })
221
+
222
+ if( loneliness > this._lonelinessThreshold )
223
+ events.push({
224
+ type: 'emotion.loneliness.significant',
225
+ source: this.name,
226
+ payload: { loneliness, belonging, bondCount: this._bonds.size },
227
+ })
228
+
229
+
230
+ // Phase C: publish cognitive event
231
+ const _bus = this._bus
232
+ if( _bus && love > 0.5 ){
233
+ const predErr = this._model.observe( 'emotion.love', love )
234
+ if( !predErr.gated )
235
+ _bus.publish({ type: 'emotion.love.significant', version: 1, sourceEngine: this.name, salience: Math.min(1, love), payload: { love } })
236
+ }
237
+ return { events: events.length > 0 ? events : undefined, commands }
238
+ }
239
+
240
+ // ── Bond model management ────────────────────────────────
241
+
242
+ private _processSocialSignals(
243
+ interactions: Array<{ keid: string; valence: number; intensity: number; directedAtSelf: boolean; sharedExperience: boolean }>,
244
+ tick: Tick
245
+ ): void {
246
+ for( const interaction of interactions ){
247
+ const
248
+ keid = interaction.keid,
249
+ valence = interaction.valence,
250
+ directedAtSelf = interaction.directedAtSelf,
251
+ isSharedExp = interaction.sharedExperience
252
+
253
+ if( !keid || keid === 'agent-self') continue
254
+
255
+ let bond = this._bonds.get( keid )
256
+
257
+ if( !bond ){
258
+ bond = {
259
+ keid,
260
+ attachmentStrength: 0,
261
+ trustLevel: 0.5, // Neutral starting trust
262
+ positiveRatio: 0.5,
263
+ interactionCount: 0,
264
+ lastInteractionTick: tick,
265
+ sharedExperiences: 0,
266
+ dependency: 0,
267
+ }
268
+ this._bonds.set( keid, bond )
269
+ }
270
+
271
+ // Update bond metrics
272
+ bond.interactionCount++
273
+ bond.lastInteractionTick = tick
274
+
275
+ // Positive ratio — exponential moving average
276
+ const isPositive = valence > 0
277
+ bond.positiveRatio += this._growthRate * ( ( isPositive ? 1 : 0 ) - bond.positiveRatio )
278
+
279
+ // Attachment strength grows with positive interactions
280
+ if( directedAtSelf ){
281
+ const valenceContribution = valence * 0.1
282
+ bond.attachmentStrength = Math.min( 1,
283
+ bond.attachmentStrength + valenceContribution * this._growthRate
284
+ )
285
+ }
286
+
287
+ // Trust builds from consistent positive interactions over time
288
+ if( bond.interactionCount >= this._minInteractionsForBond ){
289
+ const reliabilitySignal = isPositive ? 0.1 : -0.15 // Betrayal hurts trust more than reliability builds it
290
+ bond.trustLevel = Math.min( 1, Math.max( 0,
291
+ bond.trustLevel + reliabilitySignal * this._growthRate
292
+ ))
293
+ }
294
+
295
+ // Shared experiences deepen bonds
296
+ if( isSharedExp ){
297
+ bond.sharedExperiences++
298
+ bond.attachmentStrength = Math.min( 1,
299
+ bond.attachmentStrength + 0.03 * this._growthRate
300
+ )
301
+ }
302
+
303
+ // Dependency increases with interaction frequency
304
+ bond.dependency = Math.min( 1, bond.interactionCount / 100 )
305
+ }
306
+ }
307
+
308
+ private _decayBonds( delta: Duration, currentTick: Tick ): void {
309
+ const deltaSeconds = delta / 1000
310
+
311
+ for( const [ id, bond ] of this._bonds ){
312
+ const ticksSinceLastInteraction = currentTick - bond.lastInteractionTick
313
+
314
+ // Attachment decays slowly during absence
315
+ if( ticksSinceLastInteraction > 10 ){
316
+ const decayFactor = this._decayRate * deltaSeconds * ( ticksSinceLastInteraction / 100 )
317
+ bond.attachmentStrength = Math.max( 0, bond.attachmentStrength - decayFactor )
318
+ }
319
+
320
+ // Very old bonds with no interactions can be pruned
321
+ if( ticksSinceLastInteraction > 1000 && bond.attachmentStrength < 0.05 )
322
+ this._bonds.delete( id )
323
+ }
324
+ }
325
+
326
+ // ── Emotion computation ──────────────────────────────────
327
+
328
+ private _computeLove(): number {
329
+ // Love = strongest attachment bond, thresholded
330
+ const strongest = this._strongestBondStrength()
331
+ return strongest > 0.4 ? strongest : 0
332
+ }
333
+
334
+ private _computeTrust(): number {
335
+ // Trust = average trust level across bonds, weighted by attachment
336
+ if( this._bonds.size === 0 ) return 0.5 // Neutral baseline
337
+
338
+ let weightedTrust = 0
339
+ let totalWeight = 0
340
+
341
+ for( const bond of this._bonds.values() ){
342
+ const weight = bond.attachmentStrength + 0.1
343
+ weightedTrust += bond.trustLevel * weight
344
+ totalWeight += weight
345
+ }
346
+
347
+ return totalWeight > 0 ? weightedTrust / totalWeight : 0.5
348
+ }
349
+
350
+ private _computeBelonging( positiveSignalCount: number ): number {
351
+ const
352
+ bondCount = this._bonds.size,
353
+ meanStrength = bondCount > 0
354
+ ? Array.from( this._bonds.values() ).reduce( ( s, b ) => s + b.attachmentStrength, 0 ) / bondCount
355
+ : 0,
356
+ socialPresence = this._cachedActiveAgents,
357
+ positiveSignals = Math.min( 1, positiveSignalCount / 5 )
358
+
359
+ const computed = Math.min( 1,
360
+ Math.min( bondCount / 3, 1 ) * 0.4
361
+ + meanStrength * 0.3
362
+ + Math.min( socialPresence / 5, 1 ) * 0.15
363
+ + positiveSignals * 0.15
364
+ )
365
+
366
+ // Self-awareness provides a baseline — the Will is never entirely without
367
+ // connection to itself. Social bonds can only add to this floor, not replace it.
368
+ return Math.max( this._selfBelonging, computed )
369
+ }
370
+
371
+ private _computeLoneliness( belonging: number ): number {
372
+ // Loneliness spikes when belonging is very low
373
+ return belonging > 0.6 ? 0
374
+ : belonging > 0.3 ? ( 0.6 - belonging ) * 1.5
375
+ : 1 - belonging * 0.5
376
+ }
377
+
378
+ // ── Helpers ──────────────────────────────────────────────
379
+
380
+ private _strongestBond(): BondModel | null {
381
+ let strongest: BondModel | null = null
382
+ for( const bond of this._bonds.values() ){
383
+ if( !strongest || bond.attachmentStrength > strongest.attachmentStrength )
384
+ strongest = bond
385
+ }
386
+ return strongest
387
+ }
388
+
389
+ private _strongestBondStrength(): number {
390
+ return this._strongestBond()?.attachmentStrength ?? 0
391
+ }
392
+
393
+ private _restoreFromState( state: ReadonlySimulationState ): void {
394
+ for( const entity of state.entities.values() ){
395
+ if( entity.type !== 'attachment.bond') continue
396
+ const m = entity.metadata
397
+ if( !m?.keid ) continue
398
+
399
+ this._bonds.set( m.keid as string, {
400
+ keid: m.keid as string,
401
+ attachmentStrength: m.strength as number ?? 0,
402
+ trustLevel: m.trust as number ?? 0.5,
403
+ positiveRatio: m.positiveRatio as number ?? 0.5,
404
+ interactionCount: m.interactionCount as number ?? 0,
405
+ lastInteractionTick: m.lastInteractionTick as number ?? 0,
406
+ sharedExperiences: m.sharedExperiences as number ?? 0,
407
+ dependency: m.dependency as number ?? 0,
408
+ })
409
+ }
410
+ }
411
+
412
+ private _persistBonds( commands: StateCommands, tick: Tick ): void {
413
+ for( const bond of this._bonds.values() ){
414
+ commands.set ??= []
415
+ commands.set.push({
416
+ id: `bond-${bond.keid}`,
417
+ type: 'attachment.bond',
418
+ metadata: {
419
+ keid: bond.keid,
420
+ strength: bond.attachmentStrength,
421
+ trust: bond.trustLevel,
422
+ positiveRatio: bond.positiveRatio,
423
+ interactionCount: bond.interactionCount,
424
+ lastInteractionTick: bond.lastInteractionTick,
425
+ sharedExperiences: bond.sharedExperiences,
426
+ dependency: bond.dependency,
427
+ tick,
428
+ },
429
+ })
430
+ }
431
+ }
432
+ }