@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,562 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/affective.blender.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * AffectiveBlender — integrates all discrete emotions into a unified
7
+ * affective state.
8
+ *
9
+ * Reads the output of all other affective evaluators and produces:
10
+ * - Valence (-1 to +1): overall pleasantness/unpleasantness
11
+ * - Arousal (0-1): activation/intensity level
12
+ * - Dominance (0-1): sense of control/agency
13
+ *
14
+ * Handles:
15
+ * - Emotion blending (bittersweet = joy + sadness coexist)
16
+ * - Emotional inertia (emotions carry over between ticks)
17
+ * - Affective amplification (some emotions amplify others)
18
+ * - Somatic integration (interoceptive signals modulate emotions)
19
+ *
20
+ * This is the final output of the affective layer — the unified
21
+ * "how I feel" that feeds into decision-making and conscious experience.
22
+ *
23
+ * Part of Shard 1 (Affective Layer) — runs every tick, synchronous.
24
+ * Must run LAST among affective engines (priority 17).
25
+ */
26
+
27
+ import type {
28
+ Duration,
29
+ Tick,
30
+ SimulationContext,
31
+ ReadonlySimulationState,
32
+ StateCommands,
33
+ SimulationEvent,
34
+ } from '#core/types'
35
+ import type { CognitiveEngine, SimulationEngine, EngineResult } from '#cognition/types'
36
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
37
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
38
+ import { GenerativeModel } from '#cognition/generative.model'
39
+
40
+ /**
41
+ * Numeric encoding for dominant emotion metric.
42
+ * Consumers decode using DOMINANT_EMOTION_LABELS[code].
43
+ */
44
+ const DOMINANT_EMOTION_CODES: Record<string, number> = {
45
+ 'neutral': 0,
46
+ 'fear': 1,
47
+ 'anxiety': 2,
48
+ 'vigilance': 3,
49
+ 'joy': 4,
50
+ 'satisfaction': 5,
51
+ 'excitement': 6,
52
+ 'sadness': 7,
53
+ 'disappointment': 8,
54
+ 'grief': 9,
55
+ 'frustration': 10,
56
+ 'anger': 11,
57
+ 'irritability': 12,
58
+ 'love': 13,
59
+ 'trust': 14,
60
+ 'belonging': 15,
61
+ 'loneliness': 16,
62
+ 'awe': 17,
63
+ 'curiosity': 18,
64
+ 'interest': 19,
65
+ 'boredom': 20,
66
+ 'guilt': 21,
67
+ 'shame': 22,
68
+ 'pride': 23,
69
+ 'indignation': 24,
70
+ 'disgust': 25,
71
+ }
72
+
73
+ /**
74
+ * Reverse lookup for debugging/display.
75
+ */
76
+ export const DOMINANT_EMOTION_LABELS: Record<number, string> = {}
77
+ for( const [ label, code ] of Object.entries( DOMINANT_EMOTION_CODES ) ){
78
+ DOMINANT_EMOTION_LABELS[ code ] = label
79
+ }
80
+
81
+ export interface AffectiveBlenderConfig {
82
+ /** Inertia factor — how much previous affect carries over (0-1) */
83
+ inertia?: number
84
+ /** Whether to emit detailed blend events */
85
+ emitBlendEvents?: boolean
86
+ bus?: CognitiveBus
87
+ }
88
+
89
+ /**
90
+ * PAD state — Pleasure-Arousal-Dominance model.
91
+ * One of the most validated dimensional models of affect.
92
+ */
93
+ interface PADState {
94
+ valence: number // -1 (unpleasant) to +1 (pleasant)
95
+ arousal: number // 0 (calm) to 1 (excited)
96
+ dominance: number // 0 (submissive/out of control) to 1 (dominant/in control)
97
+ }
98
+
99
+ /**
100
+ * Mapping from discrete emotions to their PAD coordinates.
101
+ * Based on established affective science (Russell, Mehrabian, Scherer).
102
+ */
103
+ const EMOTION_PAD: Record<string, PADState> = {
104
+ // Threat-related
105
+ fear: { valence: -0.65, arousal: 0.80, dominance: -0.40 },
106
+ anxiety: { valence: -0.55, arousal: 0.60, dominance: -0.50 },
107
+ vigilance: { valence: 0.00, arousal: 0.70, dominance: 0.20 },
108
+
109
+ // Reward-related
110
+ joy: { valence: 0.85, arousal: 0.65, dominance: 0.45 },
111
+ satisfaction: { valence: 0.70, arousal: 0.25, dominance: 0.55 },
112
+ excitement: { valence: 0.75, arousal: 0.85, dominance: 0.35 },
113
+
114
+ // Loss-related
115
+ sadness: { valence: -0.70, arousal: 0.20, dominance: -0.30 },
116
+ disappointment: { valence: -0.45, arousal: 0.30, dominance: -0.20 },
117
+ grief: { valence: -0.80, arousal: 0.40, dominance: -0.60 },
118
+
119
+ // Frustration-related
120
+ frustration: { valence: -0.55, arousal: 0.65, dominance: -0.35 },
121
+ anger: { valence: -0.60, arousal: 0.85, dominance: 0.50 },
122
+ irritability: { valence: -0.40, arousal: 0.50, dominance: -0.10 },
123
+
124
+ // Attachment-related
125
+ love: { valence: 0.90, arousal: 0.55, dominance: 0.30 },
126
+ trust: { valence: 0.65, arousal: 0.15, dominance: 0.10 },
127
+ belonging: { valence: 0.75, arousal: 0.30, dominance: 0.20 },
128
+ loneliness: { valence: -0.55, arousal: 0.35, dominance: -0.45 },
129
+
130
+ // Aesthetic-related
131
+ awe: { valence: 0.80, arousal: 0.70, dominance: -0.40 },
132
+ curiosity: { valence: 0.50, arousal: 0.55, dominance: 0.30 },
133
+ interest: { valence: 0.45, arousal: 0.40, dominance: 0.35 },
134
+ boredom: { valence: -0.35, arousal: 0.10, dominance: -0.25 },
135
+
136
+ // Moral-related
137
+ guilt: { valence: -0.55, arousal: 0.45, dominance: -0.50 },
138
+ shame: { valence: -0.65, arousal: 0.55, dominance: -0.65 },
139
+ pride: { valence: 0.80, arousal: 0.50, dominance: 0.70 },
140
+ indignation: { valence: -0.50, arousal: 0.75, dominance: 0.55 },
141
+ disgust: { valence: -0.70, arousal: 0.55, dominance: 0.10 },
142
+ }
143
+
144
+ /**
145
+ * All emotion metric keys the blender integrates.
146
+ */
147
+ const EMOTION_KEYS = [
148
+ 'emotion.fear',
149
+ 'emotion.anxiety',
150
+ 'emotion.vigilance',
151
+ 'emotion.joy',
152
+ 'emotion.satisfaction',
153
+ 'emotion.excitement',
154
+ 'emotion.sadness',
155
+ 'emotion.disappointment',
156
+ 'emotion.grief',
157
+ 'emotion.frustration',
158
+ 'emotion.anger',
159
+ 'emotion.irritability',
160
+ 'emotion.love',
161
+ 'emotion.trust',
162
+ 'emotion.belonging',
163
+ 'emotion.loneliness',
164
+ 'emotion.awe',
165
+ 'emotion.curiosity',
166
+ 'emotion.interest',
167
+ 'emotion.boredom',
168
+ 'emotion.guilt',
169
+ 'emotion.shame',
170
+ 'emotion.pride',
171
+ 'emotion.indignation',
172
+ 'emotion.disgust',
173
+ ]
174
+
175
+ // How strongly empathy's vicarious affect (resonating with others' emotions) bleeds into
176
+ // the Will's own blended PAD. Modest — empathy already dampens by resonance × closeness.
177
+ const EMPATHY_CONTAGION_WEIGHT = 0.4
178
+
179
+ export class AffectiveBlender implements SimulationEngine, CognitiveEngine {
180
+ readonly name = 'affective-blender'
181
+
182
+ private _inertia: number = 0.5
183
+ private _temperamentValence: number = 0
184
+ private _emitBlendEvents: boolean = false
185
+
186
+ private _previousPAD: PADState = { valence: 0, arousal: 0.3, dominance: 0.5 }
187
+
188
+ private _emotionHistory: Map<string, number[]> = new Map()
189
+
190
+ private _moodBaseline: number = 0.5
191
+ private _energyLevel: number = 100
192
+ private _comfort: number = 0.5
193
+ private _sleepPressure: number = 0
194
+ private _stressLoad: number = 0
195
+
196
+ private _socialArousalSurge: number = 0
197
+ private _socialValenceBump: number = 0
198
+
199
+ private _bus: CognitiveBus | null = null
200
+
201
+ private readonly _model = new GenerativeModel()
202
+
203
+ constructor( config: AffectiveBlenderConfig = {} ){
204
+ this._bus = config.bus ?? null
205
+ this._inertia = config.inertia ?? 0.5
206
+ this._emitBlendEvents = config.emitBlendEvents ?? false
207
+ }
208
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
209
+
210
+ // ── Engine interface ─────────────────────────────────────
211
+
212
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
213
+ const cfg = state.entities.get('engine-config-affective-blender')
214
+ if( !cfg ) return
215
+
216
+ const p = cfg.metadata?.params as Record<string, number> | undefined
217
+ if( !p ) return
218
+
219
+ if( p.inertia != null ) this._inertia = p.inertia
220
+ if( p.temperamentValence != null ) this._temperamentValence = p.temperamentValence
221
+ }
222
+
223
+
224
+ subscribes(): string[] {
225
+ return [
226
+ 'circadian.state.changed',
227
+ 'energy.state.changed',
228
+ 'interoception.state.changed',
229
+ 'sleep.state.changed',
230
+ 'stress.state.changed',
231
+ 'executive.prediction.formed',
232
+ 'interaction.occurred', // 2.1: social stimuli drive immediate arousal/valence shifts
233
+ ]
234
+ }
235
+ publishes(): CognitiveEventSchema[] { return [] }
236
+
237
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
238
+ this._model.observe( e.type, e.salience )
239
+ switch( e.type ){
240
+ case 'circadian.state.changed':
241
+ this._moodBaseline = (e.payload as Record<string,number>)['moodBaseline'] ?? this._moodBaseline
242
+ break
243
+ case 'energy.state.changed':
244
+ this._energyLevel = (e.payload as Record<string,number>)['level'] ?? this._energyLevel
245
+ break
246
+ case 'interoception.state.changed':
247
+ this._comfort = (e.payload as Record<string,number>)['comfort'] ?? this._comfort
248
+ break
249
+ case 'sleep.state.changed':
250
+ this._sleepPressure = (e.payload as Record<string,number>)['pressure'] ?? this._sleepPressure
251
+ break
252
+ case 'stress.state.changed':
253
+ this._stressLoad = (e.payload as Record<string,number>)['load'] ?? this._stressLoad
254
+ break
255
+ case 'executive.prediction.formed': {
256
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
257
+ if (p.predictedDomains.includes('energy'))
258
+ this._model.setPrecision('affect.arousal', 1.0 + p.confidence * 0.3)
259
+ break
260
+ }
261
+ case 'interaction.occurred': {
262
+ // 2.1: Wire social events to immediate affective response.
263
+ // Any directed interaction triggers an orienting response (arousal spike)
264
+ // regardless of valence. Valence shifts toward the interaction's tone.
265
+ // Both signals decay automatically each tick in react().
266
+ const p = e.payload as { valence: number; intensity: number; directedAtSelf: boolean }
267
+ if( p.directedAtSelf || e.salience > 0.5 ){
268
+ const intensity = Math.max( 0.3, p.intensity )
269
+ // Arousal: social presence is activating — always rises, decays to 0
270
+ this._socialArousalSurge = Math.min( 1, this._socialArousalSurge + intensity * 0.35 )
271
+ // Valence: shifts toward interaction tone; capped to ±0.8 to stay blendable
272
+ const shift = p.valence * intensity * 0.30
273
+ this._socialValenceBump = Math.max( -0.8, Math.min( 0.8,
274
+ this._socialValenceBump + shift
275
+ ))
276
+ }
277
+ break
278
+ }
279
+ }
280
+ }
281
+
282
+ snapshot(): Record<string, unknown> {
283
+ return {
284
+ moodBaseline: this._moodBaseline,
285
+ energyLevel: this._energyLevel,
286
+ comfort: this._comfort,
287
+ sleepPressure: this._sleepPressure,
288
+ stressLoad: this._stressLoad,
289
+ }
290
+ }
291
+
292
+ async react(
293
+ _delta: Duration,
294
+ tick: Tick,
295
+ state: ReadonlySimulationState,
296
+ context: SimulationContext
297
+ ): Promise<EngineResult> {
298
+ this._readConfigFromState( state )
299
+
300
+ const
301
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
302
+ commands: StateCommands = { set: [], metrics: [] }
303
+
304
+ // 1. Read all discrete emotions
305
+ const emotionIntensities = new Map<string, number>()
306
+ for( const key of EMOTION_KEYS ){
307
+ const
308
+ shortName = key.replace('emotion.', ''),
309
+ intensity = state.metrics.get( key ) ?? 0
310
+
311
+ emotionIntensities.set( shortName, intensity )
312
+ }
313
+
314
+ // 2. Compute raw PAD from weighted emotion contributions
315
+ const rawPAD = this._computeRawPAD( emotionIntensities )
316
+
317
+ // 2b. Overlay transient social modulation — interaction.occurred events boost
318
+ // arousal (orienting response) and nudge valence toward the interaction tone.
319
+ // Applied before inertia so the surge is smoothed naturally, not applied cold.
320
+ // Both signals decay each tick toward zero.
321
+ const socialPAD: PADState = {
322
+ valence: Math.max( -1, Math.min( 1, rawPAD.valence + this._socialValenceBump )),
323
+ arousal: Math.min( 1, rawPAD.arousal + this._socialArousalSurge ),
324
+ dominance: rawPAD.dominance,
325
+ }
326
+ this._socialArousalSurge = Math.max( 0, this._socialArousalSurge - 0.05 )
327
+ this._socialValenceBump *= 0.88
328
+
329
+ // 2c. Overlay vicarious affect from empathy (emotion contagion) — resonating with
330
+ // others pulls the Will's own affect toward theirs. empathy.simulator already
331
+ // dampens by resonance × closeness, so it folds in with a modest weight. Before
332
+ // inertia so the contagion smooths naturally rather than being applied cold.
333
+ const empathicPAD: PADState = {
334
+ valence: Math.max( -1, Math.min( 1, socialPAD.valence + ( state.metrics.get( 'empathy.vicarious_valence' ) ?? 0 ) * EMPATHY_CONTAGION_WEIGHT )),
335
+ arousal: Math.max( 0, Math.min( 1, socialPAD.arousal + ( state.metrics.get( 'empathy.vicarious_arousal' ) ?? 0 ) * EMPATHY_CONTAGION_WEIGHT )),
336
+ dominance: socialPAD.dominance,
337
+ }
338
+
339
+ // 3. Apply emotional inertia (carryover from previous state)
340
+ const blendedPAD = this._applyInertia( empathicPAD )
341
+
342
+ // 4. Apply interoceptive modulation (body state colors emotion)
343
+ const modulatedPAD = this._applyInteroceptiveModulation( blendedPAD, state )
344
+
345
+ // 5. Store PAD state
346
+ commands.metrics!.push(
347
+ [ 'affect.valence', modulatedPAD.valence ],
348
+ [ 'affect.arousal', modulatedPAD.arousal ],
349
+ [ 'affect.dominance', modulatedPAD.dominance ],
350
+ )
351
+
352
+ // 6. Detect blending phenomena
353
+ const blends = this._detectBlends( emotionIntensities )
354
+
355
+ // 7. Identify dominant emotion
356
+ const dominant = this._dominantEmotion( emotionIntensities )
357
+ const dominantCode = DOMINANT_EMOTION_CODES[ dominant ?? 'neutral' ] ?? 0
358
+ commands.metrics!.push([ 'affect.dominant_emotion', dominantCode ])
359
+
360
+ // Persist active blends as a state entity so the executive bridge can read them
361
+ commands.set!.push({
362
+ id: 'affect-blends',
363
+ type: 'affect.blends',
364
+ metadata: { blends },
365
+ })
366
+
367
+ // 8. Track emotion history for blending detection
368
+ this._updateHistory( emotionIntensities )
369
+
370
+ // 9. Snapshot previous PAD BEFORE overwriting — needed for shift-event below
371
+ const prevPAD = this._previousPAD
372
+ this._previousPAD = { ...modulatedPAD }
373
+
374
+ // Events
375
+ if( blends.length > 0 && this._emitBlendEvents )
376
+ events.push({
377
+ type: 'affect.blend',
378
+ source: this.name,
379
+ payload: { blends, pad: modulatedPAD },
380
+ })
381
+
382
+ // Significant valence shift event — compare against the PAD from the last tick
383
+ // (prevPAD), not the just-overwritten _previousPAD.
384
+ if( Math.abs( modulatedPAD.valence - prevPAD.valence ) > 0.3 )
385
+ events.push({
386
+ type: 'affect.valence_shift',
387
+ source: this.name,
388
+ payload: {
389
+ previous: prevPAD.valence,
390
+ current: modulatedPAD.valence,
391
+ },
392
+ })
393
+
394
+
395
+ // Phase C: publish cognitive event
396
+ const _bus = this._bus
397
+ if( _bus )
398
+ _bus.publish({ type: 'affect.state.shifted', version: 1, sourceEngine: this.name, salience: Math.min(1, Math.abs(modulatedPAD.valence) + modulatedPAD.arousal * 0.5), payload: { valence: modulatedPAD.valence, arousal: modulatedPAD.arousal, dominantEmotion: dominant } })
399
+ // Phase D: rich state-change event
400
+ if( _bus )
401
+ _bus.publish({ type: 'affect.state.changed', version: 1, sourceEngine: this.name, salience: this._model.observe( 'affect.arousal', modulatedPAD.arousal ).salience, payload: { valence: modulatedPAD.valence, arousal: modulatedPAD.arousal, dominance: modulatedPAD.dominance } })
402
+ return { events: events.length > 0 ? events : undefined, commands }
403
+ }
404
+
405
+ // ── PAD computation ──────────────────────────────────────
406
+
407
+ /**
408
+ * Compute raw PAD state from discrete emotion intensities.
409
+ * Each emotion contributes to PAD proportional to its intensity
410
+ * and its established PAD coordinates.
411
+ */
412
+ private _computeRawPAD( emotions: Map<string, number> ): PADState {
413
+ let totalValence = 0
414
+ let totalArousal = 0
415
+ let totalDominance = 0
416
+ let totalWeight = 0
417
+
418
+ for( const [ name, intensity ] of emotions ){
419
+ if( intensity < 0.01 ) continue
420
+
421
+ const pad = EMOTION_PAD[ name ]
422
+ if( !pad ) continue
423
+
424
+ const weight = intensity
425
+ totalValence += pad.valence * weight
426
+ totalArousal += pad.arousal * weight
427
+ totalDominance += pad.dominance * weight
428
+ totalWeight += weight
429
+ }
430
+
431
+ if( totalWeight === 0 )
432
+ return { valence: 0, arousal: 0.2, dominance: 0.5 }
433
+
434
+ return {
435
+ valence: Math.max( -1, Math.min( 1, totalValence / totalWeight ) ),
436
+ arousal: Math.max( 0, Math.min( 1, totalArousal / totalWeight ) ),
437
+ dominance: Math.max( 0, Math.min( 1, totalDominance / totalWeight ) ),
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Apply emotional inertia — emotions don't flip instantly.
443
+ * The higher the inertia, the more the previous state carries over.
444
+ */
445
+ private _applyInertia( raw: PADState ): PADState {
446
+ // Gentle pull toward the long-term temperament set-point before inertia smoothing.
447
+ // This models stable personality hedonic baseline (some Wills are naturally more
448
+ // positive/negative) without overriding session affect.
449
+ const TEMPERAMENT_PULL = 0.05
450
+ const targetValence = raw.valence + TEMPERAMENT_PULL * ( this._temperamentValence - raw.valence )
451
+
452
+ return {
453
+ valence: this._previousPAD.valence + ( 1 - this._inertia ) * ( targetValence - this._previousPAD.valence ),
454
+ arousal: this._previousPAD.arousal + ( 1 - this._inertia ) * ( raw.arousal - this._previousPAD.arousal ),
455
+ dominance: this._previousPAD.dominance + ( 1 - this._inertia ) * ( raw.dominance - this._previousPAD.dominance ),
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Modulate PAD by interoceptive state.
461
+ * The body state colors emotional experience:
462
+ * - High sleep pressure dampens positive valence
463
+ * - Low energy reduces arousal capacity
464
+ * - High stress amplifies negative valence
465
+ */
466
+ private _applyInteroceptiveModulation( pad: PADState, state: ReadonlySimulationState ): PADState {
467
+ const
468
+ comfort = this._comfort,
469
+ sleepPressure = this._sleepPressure,
470
+ stressLoad = this._stressLoad,
471
+ energyLevel = this._energyLevel,
472
+ moodBaseline = this._moodBaseline
473
+
474
+ // Comfort pulls valence toward center (discomfort dampens both joy and sadness)
475
+ const comfortModulation = ( comfort - 0.5 ) * 0.3
476
+
477
+ // High sleep pressure shifts valence negative
478
+ const sleepModulation = -Math.max( 0, ( sleepPressure - 40 ) / 60 ) * 0.2
479
+
480
+ // Circadian mood baseline shifts valence
481
+ const moodModulation = ( moodBaseline - 0.5 ) * 0.25
482
+
483
+ // Stress amplifies arousal and shifts valence negative
484
+ const stressModulation = -( stressLoad / 100 ) * 0.3
485
+
486
+ // Energy caps maximum arousal (can't be highly aroused when exhausted)
487
+ const energyArousalCap = Math.min( 1, energyLevel / 50 )
488
+
489
+ return {
490
+ valence: Math.max( -1, Math.min( 1,
491
+ pad.valence + comfortModulation + sleepModulation + moodModulation + stressModulation
492
+ )),
493
+ arousal: Math.min( energyArousalCap, pad.arousal + ( stressLoad / 100 ) * 0.2 ),
494
+ dominance: pad.dominance,
495
+ }
496
+ }
497
+
498
+ // ── Blending detection ───────────────────────────────────
499
+
500
+ /**
501
+ * Detect co-occurring emotions that produce blend phenomena.
502
+ */
503
+ private _detectBlends( emotions: Map<string, number> ): string[] {
504
+ const blends: string[] = []
505
+
506
+ // Bittersweet: joy + sadness both present
507
+ if( ( emotions.get('joy') ?? 0 ) > 0.3 && ( emotions.get('sadness') ?? 0 ) > 0.3 )
508
+ blends.push('bittersweet')
509
+
510
+ // Anxious excitement: excitement + anxiety
511
+ if( ( emotions.get('excitement') ?? 0 ) > 0.4 && ( emotions.get('anxiety') ?? 0 ) > 0.3 )
512
+ blends.push('anxious_excitement')
513
+
514
+ // Guilty pleasure: joy + guilt
515
+ if( ( emotions.get('joy') ?? 0 ) > 0.4 && ( emotions.get('guilt') ?? 0 ) > 0.3 )
516
+ blends.push('guilty_pleasure')
517
+
518
+ // Awed fear: awe + fear (sublime experience)
519
+ if( ( emotions.get('awe') ?? 0 ) > 0.5 && ( emotions.get('fear') ?? 0 ) > 0.3 )
520
+ blends.push('sublime')
521
+
522
+ // Righteous anger: anger + pride (indignation already captures this)
523
+ if( ( emotions.get('anger') ?? 0 ) > 0.4 && ( emotions.get('pride') ?? 0 ) > 0.4 )
524
+ blends.push('righteous_anger')
525
+
526
+ // Nostalgic sadness: sadness + love (missing something cherished)
527
+ if( ( emotions.get('sadness') ?? 0 ) > 0.3 && ( emotions.get('love') ?? 0 ) > 0.4 )
528
+ blends.push('nostalgia')
529
+
530
+ // Curiosity-fear: curiosity + fear (cautious exploration)
531
+ if( ( emotions.get('curiosity') ?? 0 ) > 0.4 && ( emotions.get('fear') ?? 0 ) > 0.3 )
532
+ blends.push('cautious_curiosity')
533
+
534
+ return blends
535
+ }
536
+
537
+ /**
538
+ * Find the most intense emotion.
539
+ */
540
+ private _dominantEmotion( emotions: Map<string, number> ): string | null {
541
+ let maxIntensity = 0.1 // Threshold — below this, no clear dominant
542
+ let dominant: string | null = null
543
+
544
+ for( const [ name, intensity ] of emotions ){
545
+ if( intensity > maxIntensity ){
546
+ maxIntensity = intensity
547
+ dominant = name
548
+ }
549
+ }
550
+
551
+ return dominant
552
+ }
553
+
554
+ private _updateHistory( emotions: Map<string, number> ): void {
555
+ for( const [ name, intensity ] of emotions ){
556
+ const history = this._emotionHistory.get( name ) ?? []
557
+ history.push( intensity )
558
+ if( history.length > 10 ) history.shift()
559
+ this._emotionHistory.set( name, history )
560
+ }
561
+ }
562
+ }