@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,202 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/forgetting.curve.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * ForgettingCurve — applies spaced repetition dynamics to episodic memories.
7
+ *
8
+ * Models the Ebbinghaus forgetting curve:
9
+ * - Memories decay exponentially without reactivation
10
+ * - Each retrieval resets and strengthens the memory
11
+ * - Emotionally intense memories decay slower
12
+ * - Sleep consolidates and protects memories
13
+ *
14
+ * Operates on the episodic memory store, decaying activation
15
+ * strength over time and removing memories that fall below
16
+ * the retrieval threshold.
17
+ *
18
+ * Part of Shard 2 (Memory Layer) — runs every tick, synchronous.
19
+ */
20
+
21
+ import type {
22
+ Duration,
23
+ Tick,
24
+ SimulationContext,
25
+ ReadonlySimulationState,
26
+ StateCommands,
27
+ } from '#core/types'
28
+ import type { EpisodicConsolidator, EpisodicMemory } from '#faculties/episodic.consolidator'
29
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
30
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
31
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
32
+ import { GenerativeModel, type GenerativeModelSnapshot } from '#cognition/generative.model'
33
+
34
+ export interface ForgettingCurveConfig {
35
+ /** Base forgetting rate (Ebbinghaus: ~0.3 per log-time unit) */
36
+ baseForgettingRate?: number
37
+ /** How much emotional intensity slows forgetting (0-1, 1 = no forgetting) */
38
+ emotionProtection?: number
39
+ /** Minimum activation before a memory is pruned */
40
+ pruningThreshold?: number
41
+ /** Maximum memories to prune per tick (avoids stalls) */
42
+ maxPrunePerTick?: number
43
+ bus?: CognitiveBus
44
+ }
45
+
46
+ export class ForgettingCurve implements SimulationEngine, CognitiveEngine {
47
+ readonly name = 'forgetting-curve'
48
+
49
+ private _baseForgettingRate: number
50
+ private _emotionProtection: number
51
+ private _pruningThreshold: number
52
+ private _maxPrunePerTick: number
53
+
54
+ private _episodicConsolidator: EpisodicConsolidator | null = null
55
+
56
+ private _bus: CognitiveBus | null = null
57
+
58
+ private readonly _model = new GenerativeModel()
59
+
60
+
61
+ constructor( config: ForgettingCurveConfig = {} ){
62
+ this._bus = config.bus ?? null
63
+ this._baseForgettingRate = config.baseForgettingRate ?? 0.02
64
+ this._emotionProtection = config.emotionProtection ?? 0.7
65
+ this._pruningThreshold = config.pruningThreshold ?? 0.01
66
+ this._maxPrunePerTick = config.maxPrunePerTick ?? 10
67
+ }
68
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
69
+
70
+ // ── Reference to episodic consolidator ───────────────────
71
+
72
+ attachConsolidator( consolidator: EpisodicConsolidator ): void {
73
+ this._episodicConsolidator = consolidator
74
+ }
75
+
76
+ // ── Engine interface ─────────────────────────────────────
77
+
78
+ private _readConfigFromState( state: ReadonlySimulationState ): void {
79
+ const cfg = state.entities.get('engine-config-forgetting')
80
+ if( !cfg ) return
81
+
82
+ const p = cfg.metadata?.params as Record<string, number> | undefined
83
+ if( !p ) return
84
+
85
+ if( p.baseForgettingRate != null ) this._baseForgettingRate = p.baseForgettingRate
86
+ if( p.emotionProtection != null ) this._emotionProtection = p.emotionProtection
87
+ if( p.pruningThreshold != null ) this._pruningThreshold = p.pruningThreshold
88
+ if( p.maxPrunePerTick != null ) this._maxPrunePerTick = p.maxPrunePerTick
89
+ }
90
+
91
+
92
+ subscribes(): string[] { return ['executive.prediction.formed'] }
93
+ publishes(): CognitiveEventSchema[] { return [] }
94
+
95
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
96
+ this._model.observe( e.type, e.salience )
97
+ if( e.type === 'executive.prediction.formed' ){
98
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
99
+ if( p.predictedDomains.includes('memory') )
100
+ this._model.setPrecision( 'memory.decay_rate', 1.0 + p.confidence * 0.5 )
101
+ }
102
+ }
103
+
104
+ snapshot(): Record<string, unknown> {
105
+ // FN9: previously returned {} — the salience/generative-model sub-states were
106
+ // accumulating mutable state invisible to the event-sourced store, so a
107
+ // restore/replay zeroed them out. Capture them so the curve resumes identically.
108
+ return {
109
+ model: this._model.snapshot(),
110
+ }
111
+ }
112
+
113
+ restore( snap: Record<string, unknown> ): void {
114
+ if( !snap ) return
115
+ if( snap.model ) this._model.restore( snap.model as GenerativeModelSnapshot )
116
+ }
117
+
118
+ async react(
119
+ delta: Duration,
120
+ tick: Tick,
121
+ state: ReadonlySimulationState,
122
+ context: SimulationContext
123
+ ): Promise<EngineResult> {
124
+ this._readConfigFromState( state )
125
+
126
+ const commands: StateCommands = { metrics: [] }
127
+ if( !this._episodicConsolidator )
128
+ return { commands }
129
+
130
+ const
131
+ allEpisodes = this._episodicConsolidator.getAllEpisodes(),
132
+ deltaSeconds = delta / 1000,
133
+ isSleeping = state.metrics.get('state.sleeping') ?? 0
134
+
135
+ let decayedCount = 0
136
+ const toPrune: string[] = []
137
+ // Compute decayed activations without mutating the consolidator's live
138
+ // objects: those references may already be held by other engines this
139
+ // tick. We hand the results to the owning consolidator, which applies
140
+ // them as an immutable update (single writer, no read-phase reach-in).
141
+ const decay = new Map<string, number>()
142
+
143
+ for( let i = allEpisodes.length - 1; i >= 0; i-- ){
144
+ const episode = allEpisodes[i]!
145
+ if( !episode ) continue
146
+
147
+ // Compute effective forgetting rate
148
+ const
149
+ emotionalIntensity = this._computeEmotionalIntensity( episode ),
150
+ emotionProtection = emotionalIntensity * this._emotionProtection,
151
+ retrievalBoost = episode.retrievalCount > 0
152
+ ? Math.min( 0.5, episode.retrievalCount * 0.1 )
153
+ : 0,
154
+ sleepProtection = isSleeping > 0 ? 0.3 : 0, // Sleep protects memories
155
+ effectiveRate = this._baseForgettingRate
156
+ * ( 1 - emotionProtection )
157
+ * ( 1 - retrievalBoost )
158
+ * ( 1 - sleepProtection )
159
+
160
+ // Compute decay (pure — no mutation of the shared episode object).
161
+ const nextStrength = Math.max( 0, episode.activationStrength - effectiveRate * deltaSeconds )
162
+ decay.set( episode.id, nextStrength )
163
+ decayedCount++
164
+
165
+ // Collect for pruning if below threshold — the consolidator owns the
166
+ // store, so it performs the actual removal after the scan.
167
+ if( nextStrength < this._pruningThreshold && toPrune.length < this._maxPrunePerTick )
168
+ toPrune.push( episode.id )
169
+ }
170
+
171
+ // Commit decay through the store owner (immutable replace), then evict
172
+ // decayed memories from the store, vector index, and sim state.
173
+ this._episodicConsolidator.applyDecay( decay )
174
+
175
+ let prunedCount = 0
176
+ if( toPrune.length > 0 ){
177
+ const removed = await this._episodicConsolidator.pruneEpisodes( toPrune )
178
+ prunedCount = removed.length
179
+ if( removed.length > 0 )
180
+ commands.delete = ( commands.delete ?? [] ).concat( removed )
181
+ }
182
+
183
+ commands.metrics!.push(
184
+ [ 'memory.decayed_this_tick', decayedCount ],
185
+ [ 'memory.pruned_this_tick', prunedCount ],
186
+ [ 'memory.total_episodes', allEpisodes.length - prunedCount ],
187
+ )
188
+
189
+ // Phase C: publish cognitive event
190
+ const _bus = this._bus
191
+ if( _bus && decayedCount > 0 )
192
+ _bus.publish({ type: 'memory.decay.applied', version: 1, sourceEngine: this.name, salience: this._model.observe( 'memory.decay_rate', decayedCount ).salience, payload: { decayed: decayedCount } })
193
+
194
+ return { commands }
195
+ }
196
+
197
+ private _computeEmotionalIntensity( episode: EpisodicMemory ): number {
198
+ const values = Object.values( episode.emotionalTags )
199
+ if( values.length === 0 ) return 0
200
+ return values.reduce( ( s, v ) => s + Math.abs( v ), 0 ) / values.length
201
+ }
202
+ }
@@ -0,0 +1,280 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/frustration.evaluator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * FrustrationEvaluator — detects blocked goals and repeated failures.
7
+ *
8
+ * Evaluates:
9
+ * - Goals stuck without progress (via goal.blocked events)
10
+ * - Repeated blocked events on same goal
11
+ * - Violated expectations (cached confidence × goal-loss signals)
12
+ * - Perceived unfairness (via interaction.occurred events)
13
+ *
14
+ * Produces: frustration, anger, irritability
15
+ *
16
+ * Frustration = goal blockage without clear target
17
+ * Anger = goal blockage with identifiable external cause
18
+ * Irritability = accumulated low-grade frustration lowering threshold
19
+ *
20
+ * Part of Shard 1 (Affective Layer) — hybrid: event-driven inputs, temporal react.
21
+ */
22
+
23
+ import type {
24
+ Duration,
25
+ Tick,
26
+ SimulationContext,
27
+ ReadonlySimulationState,
28
+ StateCommands,
29
+ SimulationEvent,
30
+ } from '#core/types'
31
+ import type { SimulationEngine, EngineResult, CognitiveEngine } from '#cognition/types'
32
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
33
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
34
+ import { GenerativeModel } from '#cognition/generative.model'
35
+ import { readEffectiveParams } from '#cognition/persona.prior'
36
+
37
+ export interface FrustrationEvaluatorConfig {
38
+ /** Ticks without progress before frustration begins */
39
+ stuckThreshold?: number
40
+ /** How quickly irritability accumulates */
41
+ irritabilityRate?: number
42
+ /** Decay rate when frustration resolves */
43
+ decayRate?: number
44
+ /**
45
+ * Habituation rate — proportional to current irritability level.
46
+ * Prevents the one-way ratchet to 1.0 under chronic goal blockage.
47
+ * Natural ceiling: irritabilityRate / habituationRate (e.g. 0.02/0.03 ≈ 0.67).
48
+ */
49
+ habituationRate?: number
50
+ bus?: CognitiveBus
51
+ }
52
+
53
+ export class FrustrationEvaluator implements SimulationEngine, CognitiveEngine {
54
+ readonly name = 'frustration-evaluator'
55
+
56
+ private _stuckThreshold: number
57
+ private _irritabilityRate: number
58
+ private _decayRate: number
59
+ private _habituationRate: number
60
+
61
+ // Own output — cached so react() doesn't read state.metrics
62
+ private _cachedFrustration: number = 0
63
+ private _cachedAnger: number = 0
64
+ private _cachedIrritability: number = 0
65
+
66
+ // Inputs from cognitive events
67
+ private _blockedGoals = new Map<string, { ticksStuck: number; priority: number; refreshedAt: number }>()
68
+ private _goalBlockedCounts = new Map<string, number>() // repeated-failure proxy
69
+ private _reactCount = 0 // incremented each react() for expiry
70
+ private _unfairnessSignal: number = 0 // from interaction.occurred
71
+ private _cachedConfidence: number = 0.5 // from executive.prediction.formed
72
+ private _cachedGoalLoss: number = 0 // future: loss.goal event
73
+ private _cachedDisappointment: number = 0 // future: emotion.disappointment event
74
+
75
+ private _bus: CognitiveBus | null = null
76
+
77
+ private readonly _model = new GenerativeModel()
78
+
79
+ constructor( config: FrustrationEvaluatorConfig = {} ){
80
+ this._bus = config.bus ?? null
81
+ this._stuckThreshold = config.stuckThreshold ?? 5
82
+ this._irritabilityRate = config.irritabilityRate ?? 0.02
83
+ this._decayRate = config.decayRate ?? 0.08
84
+ this._habituationRate = config.habituationRate ?? 0.03
85
+ }
86
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
87
+
88
+ // ── Engine interface ─────────────────────────────────────
89
+
90
+ subscribes(): string[] {
91
+ return [
92
+ 'executive.prediction.formed',
93
+ 'goal.blocked',
94
+ 'interaction.occurred',
95
+ ]
96
+ }
97
+
98
+ publishes(): CognitiveEventSchema[] { return [] }
99
+
100
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
101
+ this._model.observe( e.type, e.salience )
102
+
103
+ if( e.type === 'executive.prediction.formed' ){
104
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
105
+ if( p.predictedDomains.includes('affect') )
106
+ this._model.setPrecision( 'emotion.frustration', 1.0 + p.confidence * 0.5 )
107
+ this._cachedConfidence = p.confidence
108
+ }
109
+
110
+ if( e.type === 'goal.blocked' ){
111
+ const p = e.payload as { goalId: string; ticksStuck: number; priority: number }
112
+ this._blockedGoals.set( p.goalId, { ticksStuck: p.ticksStuck, priority: p.priority, refreshedAt: this._reactCount } )
113
+ this._goalBlockedCounts.set( p.goalId, ( this._goalBlockedCounts.get( p.goalId ) ?? 0 ) + 1 )
114
+ }
115
+
116
+ if( e.type === 'interaction.occurred' ){
117
+ const p = e.payload as { keid: string; valence: number; intensity: number; directedAtSelf: boolean }
118
+ if( p.directedAtSelf && p.valence < -0.5 )
119
+ this._unfairnessSignal = Math.min( 1, this._unfairnessSignal + 0.3 )
120
+ }
121
+ }
122
+
123
+ snapshot(): Record<string, unknown> { return {} }
124
+
125
+ async react(
126
+ delta: Duration,
127
+ _tick: Tick,
128
+ state: ReadonlySimulationState,
129
+ _context: SimulationContext
130
+ ): Promise<EngineResult> {
131
+ const
132
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
133
+ commands: StateCommands = { metrics: [] }
134
+
135
+ // Channel A (emotional-stability): how fast low-grade frustration snowballs into
136
+ // chronic irritability is read live as base ⊕ persona-prior. A Will that has shown
137
+ // steady affect develops a lower build-rate here, so it stays even-keeled longer —
138
+ // distinct from resilience's frustrationTolerance (how much is *tolerated*). Pure +
139
+ // deterministic; falls back to the constructor default when no seed/prior is present.
140
+ const frustrationCfg = readEffectiveParams( state, 'engine-config-frustration' )
141
+ const irritabilityRate = frustrationCfg.irritabilityRate ?? this._irritabilityRate
142
+
143
+ // Channel A (agreeableness — "yielding in conflict"): how strongly provocation
144
+ // (unfairness + blocked progress) turns into anger. An agreeable Will develops this
145
+ // DOWN — wronged, it accommodates rather than retaliates. A distinct facet from #6's
146
+ // warmth (reward.socialWeight): warmth = approach/connection, yielding = low antagonism.
147
+ const angerReactivity = frustrationCfg.angerReactivity ?? 0.7
148
+
149
+ // Channel A (resilience — recovery): how fast frustration/anger/irritability fade is
150
+ // refreshed each tick as base ⊕ persona-prior. A resilient Will develops a higher decay
151
+ // rate and so recovers from a bad patch sooner — distinct from resilience's
152
+ // frustrationTolerance (how much is endured, goal-manager #10) and from emotional-
153
+ // stability's irritabilityRate (how fast it builds, #5).
154
+ this._decayRate = frustrationCfg.decayRate ?? this._decayRate
155
+
156
+ // Expire blocked-goal entries that have not been refreshed for > 1.5×STUCK_THRESHOLD
157
+ // react() calls. goal.manager re-fires goal.blocked every STUCK_THRESHOLD ticks, so
158
+ // a goal that resolves (stops emitting) will expire here after ~25–30 ticks.
159
+ const expiryAge = Math.round( 20 * 1.5 ) // matches goal.manager._STUCK_THRESHOLD
160
+ for( const [ id, g ] of this._blockedGoals ){
161
+ if( this._reactCount - g.refreshedAt > expiryAge ){
162
+ this._blockedGoals.delete( id )
163
+ this._goalBlockedCounts.delete( id )
164
+ }
165
+ }
166
+
167
+ this._reactCount++
168
+
169
+ const previousFrustration = this._cachedFrustration
170
+ const previousAnger = this._cachedAnger
171
+ const previousIrritability = this._cachedIrritability
172
+
173
+ const
174
+ goalBlockage = this._assessGoalBlockage(),
175
+ repeatedFailure = this._assessRepeatedFailures(),
176
+ violatedExpect = this._assessViolatedExpectations(),
177
+ unfairness = this._unfairnessSignal
178
+
179
+ // Decay unfairness signal between events
180
+ this._unfairnessSignal = Math.max( 0, this._unfairnessSignal - this._decayRate * 0.5 * ( delta / 1000 ) )
181
+
182
+ const frustrationSources = goalBlockage + repeatedFailure + violatedExpect + unfairness
183
+
184
+ const
185
+ // Frustration: blocked progress without clear external cause
186
+ frustration = frustrationSources > 0.01
187
+ ? Math.min( 1, frustrationSources )
188
+ : Math.max( 0, previousFrustration - this._decayRate * ( delta / 1000 ) ),
189
+
190
+ // Anger: identifiable external cause for blockage
191
+ anger = ( goalBlockage > 0.3 && unfairness > 0.2 )
192
+ ? Math.min( 1, ( goalBlockage + unfairness ) * angerReactivity )
193
+ : Math.max( 0, previousAnger - this._decayRate * 0.5 * ( delta / 1000 ) ),
194
+
195
+ // Irritability: accumulated low-grade frustration with proportional habituation.
196
+ // Habituation (habituationRate * currentLevel) prevents the one-way ratchet to 1.0:
197
+ // under sustained frustration, irritability converges to irritabilityRate/habituationRate
198
+ // (default ≈ 0.67) rather than locking at ceiling indefinitely.
199
+ irritability = frustration > 0.1
200
+ ? Math.max( 0, Math.min( 1,
201
+ previousIrritability
202
+ + irritabilityRate * ( delta / 1000 )
203
+ - this._habituationRate * previousIrritability * ( delta / 1000 )
204
+ ))
205
+ : Math.max( 0, previousIrritability - this._decayRate * 2 * ( delta / 1000 ) )
206
+
207
+ // Cache for next tick
208
+ this._cachedFrustration = frustration
209
+ this._cachedAnger = anger
210
+ this._cachedIrritability = irritability
211
+
212
+ commands.metrics!.push(
213
+ [ 'emotion.frustration', frustration ],
214
+ [ 'emotion.anger', anger ],
215
+ [ 'emotion.irritability', irritability ],
216
+ [ 'frustration.goal_blockage', goalBlockage ],
217
+ [ 'frustration.repeated_failure', repeatedFailure ],
218
+ )
219
+
220
+ // Anger event
221
+ if( anger > 0.5 )
222
+ events.push({
223
+ type: 'emotion.anger.significant',
224
+ source: this.name,
225
+ payload: { anger, frustration, unfairness },
226
+ })
227
+
228
+ // Chronic frustration event
229
+ if( frustration > 0.6 && previousFrustration > 0.5 )
230
+ events.push({
231
+ type: 'emotion.frustration.chronic',
232
+ source: this.name,
233
+ payload: { frustration, irritability },
234
+ })
235
+
236
+ // Phase C + F: publish cognitive event — gated by prediction error
237
+ const _bus = this._bus
238
+ if( _bus && frustration > 0.5 ){
239
+ const predErr = this._model.observe( 'emotion.frustration', frustration )
240
+ if( !predErr.gated )
241
+ _bus.publish({ type: 'emotion.frustration.elevated', version: 1, sourceEngine: this.name, salience: Math.min(1, frustration), payload: { frustration } })
242
+ }
243
+
244
+ return { events: events.length > 0 ? events : undefined, commands }
245
+ }
246
+
247
+ // ── Internal ─────────────────────────────────────────────
248
+
249
+ private _assessGoalBlockage(): number {
250
+ if( this._blockedGoals.size === 0 ) return 0
251
+
252
+ return Math.min( 1,
253
+ Array.from( this._blockedGoals.values() ).reduce( ( sum, g ) =>
254
+ sum + Math.min( 1, g.ticksStuck / ( this._stuckThreshold * 4 ) ) * 0.3,
255
+ 0
256
+ )
257
+ )
258
+ }
259
+
260
+ private _assessRepeatedFailures(): number {
261
+ const repeatedGoals = Array.from( this._goalBlockedCounts.values() )
262
+ .filter( count => count >= 3 )
263
+
264
+ if( repeatedGoals.length === 0 ) return 0
265
+
266
+ return Math.min( 1,
267
+ repeatedGoals.reduce( ( sum, count ) =>
268
+ sum + Math.min( 1, count / 10 ) * 0.25,
269
+ 0
270
+ )
271
+ )
272
+ }
273
+
274
+ private _assessViolatedExpectations(): number {
275
+ // High confidence + negative outcome = violated expectation
276
+ return this._cachedConfidence > 0.6
277
+ ? this._cachedGoalLoss * this._cachedConfidence * 0.5
278
+ : this._cachedDisappointment * 0.3
279
+ }
280
+ }