@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.
- package/LICENSE +202 -0
- package/README.md +777 -0
- package/dist/index.d.ts +8144 -0
- package/dist/index.js +26231 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/cognition/agency/access.grants.ts +59 -0
- package/src/cognition/agency/competence.codec.ts +89 -0
- package/src/cognition/agency/engines/action.selector.ts +462 -0
- package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
- package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
- package/src/cognition/agency/engines/instruction.intake.ts +54 -0
- package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
- package/src/cognition/agency/engines/reafference.engine.ts +241 -0
- package/src/cognition/agency/execution.primitives.ts +118 -0
- package/src/cognition/agency/index.ts +58 -0
- package/src/cognition/agency/proactive.communicator.ts +274 -0
- package/src/cognition/agency/reconcile.learning.ts +82 -0
- package/src/cognition/agency/schemas/external.ts +59 -0
- package/src/cognition/agency/schemas/innate.ts +111 -0
- package/src/cognition/agency/schemas/repertoire.ts +246 -0
- package/src/cognition/agency/selection.scoring.ts +191 -0
- package/src/cognition/agency/types.ts +165 -0
- package/src/cognition/bus.ts +391 -0
- package/src/cognition/completion.inbox.ts +86 -0
- package/src/cognition/config.mirror.entities.ts +475 -0
- package/src/cognition/conversation.memory.ts +81 -0
- package/src/cognition/event.log.ts +116 -0
- package/src/cognition/event.schemas.ts +568 -0
- package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
- package/src/cognition/faculties/affective.blender.ts +562 -0
- package/src/cognition/faculties/attachment.evaluator.ts +432 -0
- package/src/cognition/faculties/attention.allocator.ts +422 -0
- package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
- package/src/cognition/faculties/bias.detector.ts +363 -0
- package/src/cognition/faculties/circadian.oscillator.ts +294 -0
- package/src/cognition/faculties/confidence.calibrator.ts +324 -0
- package/src/cognition/faculties/dream.simulator.ts +251 -0
- package/src/cognition/faculties/empathy.simulator.ts +253 -0
- package/src/cognition/faculties/energy.regulator.ts +320 -0
- package/src/cognition/faculties/episodic.consolidator.ts +728 -0
- package/src/cognition/faculties/executive.engine/commands.ts +459 -0
- package/src/cognition/faculties/executive.engine/config.ts +46 -0
- package/src/cognition/faculties/executive.engine/context.ts +605 -0
- package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
- package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
- package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
- package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
- package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
- package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
- package/src/cognition/faculties/executive.engine/facet.ts +628 -0
- package/src/cognition/faculties/executive.engine/gating.ts +222 -0
- package/src/cognition/faculties/executive.engine/index.ts +6 -0
- package/src/cognition/faculties/executive.engine/messages.ts +102 -0
- package/src/cognition/faculties/executive.engine/parser.ts +380 -0
- package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
- package/src/cognition/faculties/executive.engine/types.ts +323 -0
- package/src/cognition/faculties/exteroception.ts +338 -0
- package/src/cognition/faculties/forgetting.curve.ts +202 -0
- package/src/cognition/faculties/frustration.evaluator.ts +280 -0
- package/src/cognition/faculties/goal.manager.ts +1008 -0
- package/src/cognition/faculties/inhibition.controller.ts +351 -0
- package/src/cognition/faculties/interoception.ts +417 -0
- package/src/cognition/faculties/introspection.engine.ts +246 -0
- package/src/cognition/faculties/known.entity.tracker.ts +439 -0
- package/src/cognition/faculties/loss.evaluator.ts +224 -0
- package/src/cognition/faculties/moral.evaluator.ts +437 -0
- package/src/cognition/faculties/novelty.detector.ts +254 -0
- package/src/cognition/faculties/persona.consolidator.ts +845 -0
- package/src/cognition/faculties/planning.engine/engine.ts +859 -0
- package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
- package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
- package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
- package/src/cognition/faculties/planning.engine/types.ts +143 -0
- package/src/cognition/faculties/reputation.tracker.ts +294 -0
- package/src/cognition/faculties/reward.evaluator.ts +304 -0
- package/src/cognition/faculties/self.model.updater.ts +677 -0
- package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
- package/src/cognition/faculties/semantic.engine/index.ts +6 -0
- package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
- package/src/cognition/faculties/semantic.engine/types.ts +53 -0
- package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
- package/src/cognition/faculties/social.perception.ts +333 -0
- package/src/cognition/faculties/spaced.repetition.ts +605 -0
- package/src/cognition/faculties/stress.regulator.ts +375 -0
- package/src/cognition/faculties/task.switcher.ts +239 -0
- package/src/cognition/faculties/theory.of.mind.ts +333 -0
- package/src/cognition/faculties/threat.evaluator.ts +312 -0
- package/src/cognition/faculties/working.memory.ts +406 -0
- package/src/cognition/generative.model.ts +344 -0
- package/src/cognition/heartbeat.ts +70 -0
- package/src/cognition/index.ts +291 -0
- package/src/cognition/instruction.handler.ts +146 -0
- package/src/cognition/memory/index.ts +35 -0
- package/src/cognition/memory/vector.adapter.ts +316 -0
- package/src/cognition/memory/vector.content.ts +47 -0
- package/src/cognition/memory/vector.embedder.ts +212 -0
- package/src/cognition/memory/vector.index.ts +459 -0
- package/src/cognition/memory/vector.types.ts +65 -0
- package/src/cognition/orchestrator.ts +142 -0
- package/src/cognition/persona.prior.ts +265 -0
- package/src/cognition/schema.registry.ts +145 -0
- package/src/cognition/senses/audition.engine/engine.ts +976 -0
- package/src/cognition/senses/audition.engine/salience.ts +59 -0
- package/src/cognition/senses/base.sense.engine.ts +117 -0
- package/src/cognition/senses/gustation.engine.ts +24 -0
- package/src/cognition/senses/index.ts +172 -0
- package/src/cognition/senses/olfaction.engine.ts +24 -0
- package/src/cognition/senses/somatosensation.engine.ts +23 -0
- package/src/cognition/senses/vision.engine.ts +23 -0
- package/src/cognition/types.ts +63 -0
- package/src/cognition/utilities/token.tracker.ts +492 -0
- package/src/core/abstracts.ts +60 -0
- package/src/core/async.engine.ts +539 -0
- package/src/core/clock.ts +196 -0
- package/src/core/completion.recorder.ts +122 -0
- package/src/core/conflict.detector.ts +140 -0
- package/src/core/distributed.ts +626 -0
- package/src/core/event.bus.ts +292 -0
- package/src/core/inbound.recorder.ts +100 -0
- package/src/core/index.ts +167 -0
- package/src/core/logger.ts +85 -0
- package/src/core/metrics.ts +107 -0
- package/src/core/orchestrator.ts +625 -0
- package/src/core/replay.ts +589 -0
- package/src/core/scenario.ts +120 -0
- package/src/core/serialization.ts +448 -0
- package/src/core/simulation.ts +197 -0
- package/src/core/snapshot.manager.ts +293 -0
- package/src/core/state.manager.ts +283 -0
- package/src/core/types.ts +274 -0
- package/src/core/utils.ts +82 -0
- package/src/core/wall.clock.ts +23 -0
- package/src/extensions/livestream.ext.ts +570 -0
- package/src/extensions/time.ext.ts +339 -0
- package/src/index.ts +91 -0
- package/src/llm/gate.ts +140 -0
- package/src/llm/index.ts +716 -0
- package/src/llm/summarizer.ts +170 -0
- package/src/llm/wire.contracts.ts +67 -0
- package/src/pma/eval.ts +651 -0
- package/src/pma/index.ts +1082 -0
- package/src/profiles/built-in.ts +7 -0
- package/src/profiles/companion.ts +28 -0
- package/src/profiles/company-brain.ts +54 -0
- package/src/profiles/customer-service.ts +30 -0
- package/src/profiles/game-npc.ts +20 -0
- package/src/profiles/index.ts +42 -0
- package/src/profiles/smart-home.ts +34 -0
- package/src/runners/coherence.runner.ts +49 -0
- package/src/runners/outreach.runner.ts +158 -0
- package/src/runners/social.runner.ts +182 -0
- package/src/runners/thin-shim.runner.ts +195 -0
- package/src/sdk/will.ts +334 -0
- package/src/stem/assembly.audit.ts +88 -0
- package/src/stem/distribution.ts +203 -0
- package/src/stem/guards/identity.coherence.ts +152 -0
- package/src/stem/guards/identity.guard.ts +227 -0
- package/src/stem/index.ts +1145 -0
- package/src/stem/mind.ts +1090 -0
- package/src/stem/tracts/ack.reconciler.ts +55 -0
- package/src/stem/tracts/biography.writer.ts +190 -0
- package/src/stem/tracts/effector.controller.ts +143 -0
- package/src/stem/tracts/health.reporter.ts +113 -0
- package/src/stem/tracts/inbound.queue.ts +54 -0
- package/src/stem/tracts/outbox.controller.ts +167 -0
- package/src/stem/tracts/outbox.writer.ts +133 -0
- package/src/stem/tracts/pma.controller.ts +109 -0
- package/src/stem/tracts/replay.controller.ts +177 -0
- package/src/stem/tracts/sensory.controller.ts +168 -0
- package/src/stem/tracts/session.logger.ts +157 -0
- package/src/stem/tracts/transport/index.ts +38 -0
- package/src/stem/tracts/transport/loopback.transport.ts +86 -0
- package/src/stem/tracts/transport/socketio.transport.ts +200 -0
- package/src/stem/tracts/transport/stream.transport.ts +152 -0
- package/src/stem/tracts/transport/types.ts +193 -0
- package/src/stem/tracts/transport.controller.ts +380 -0
- 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
|
+
}
|