@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,313 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/aesthetic.evaluator.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ /**
6
+ * AestheticEvaluator — responds to novelty, complexity, pattern, and beauty.
7
+ *
8
+ * Evaluates:
9
+ * - Perceptual novelty (new patterns, unexpected combinations)
10
+ * - Complexity (information richness)
11
+ * - Pattern coherence (elegance, symmetry, resolution)
12
+ * - Cognitive challenge (optimal difficulty for engagement)
13
+ *
14
+ * Produces: awe, curiosity, interest, boredom
15
+ *
16
+ * Awe = overwhelming positive novelty + pattern (transcendent experience)
17
+ * Curiosity = moderate novelty + safety (desire to explore)
18
+ * Interest = sustainable engagement with moderately complex stimuli
19
+ * Boredom = absence of novelty or excessive predictability
20
+ *
21
+ * The aesthetic drive fuels exploration, learning, and creative behavior.
22
+ * It's modulated by safety (curiosity shuts down under high threat).
23
+ *
24
+ * Part of Shard 1 (Affective Layer) — runs every tick, synchronous.
25
+ */
26
+
27
+ import type {
28
+ Duration,
29
+ Tick,
30
+ SimulationContext,
31
+ ReadonlySimulationState,
32
+ StateCommands,
33
+ SimulationEvent,
34
+ } from '#core/types'
35
+ import type { SimulationEngine, EngineResult } from '#cognition/types'
36
+ import type { CognitiveEngine } from '#cognition/types'
37
+ import type { CognitiveEvent, CognitiveBus } from '#cognition/bus'
38
+ import type { CognitiveEventSchema } from '#cognition/schema.registry'
39
+ import { GenerativeModel } from '#cognition/generative.model'
40
+ import { readEffectiveParams } from '#cognition/persona.prior'
41
+
42
+ export interface AestheticEvaluatorConfig {
43
+ /** Threshold for awe (extreme novelty + coherence) */
44
+ aweThreshold?: number
45
+ /** Optimal novelty range for curiosity [min, max] */
46
+ curiosityRange?: [number, number]
47
+ /** How quickly boredom escalates without stimulation */
48
+ boredomRate?: number
49
+ /**
50
+ * Minimum curiosity even with zero novelty (0–1).
51
+ * Prevents full curiosity collapse — the Will retains baseline intellectual drive.
52
+ */
53
+ curiosityFloor?: number
54
+ bus?: CognitiveBus
55
+ }
56
+
57
+ export class AestheticEvaluator implements SimulationEngine, CognitiveEngine {
58
+ readonly name = 'aesthetic-evaluator'
59
+
60
+ private _aweThreshold: number
61
+ private _curiosityMin: number
62
+ private _curiosityMax: number
63
+ private _boredomRate: number
64
+ private _curiosityFloor: number
65
+ private _boredomExecutiveScale = 1
66
+
67
+ // Track stimulus history for boredom detection
68
+ private _recentStimulusCount: number[] = [] // last N ticks of percept counts
69
+ private _windowSize = 20
70
+
71
+ // Rolling category buffer from percept.category.updated events (for complexity/coherence)
72
+ private _categoryBuffer = new Map<string, number>() // category → count this tick
73
+
74
+ // Cached inputs from cognitive events
75
+ private _cachedNovelty: number = 0 // from novelty.state.changed / perception.novelty.spike
76
+ private _cachedFear: number = 0 // future: subscribe to threat.evaluated
77
+ private _cachedBoredom: number = 0
78
+
79
+ private _bus: CognitiveBus | null = null
80
+
81
+ // Consecutive ticks where boredom exceeded the significant threshold.
82
+ // Once this reaches _boredomEscalationThreshold we push a drive.seek_engagement metric
83
+ // and reset so the drive only fires in sustained-boredom bursts.
84
+ private _consecutiveBoredomTicks = 0
85
+ private readonly _boredomEscalationThreshold = 5
86
+ private readonly _boredomSignificantCutoff = 0.6
87
+
88
+ private readonly _model = new GenerativeModel()
89
+
90
+
91
+ constructor( config: AestheticEvaluatorConfig = {} ){
92
+ this._bus = config.bus ?? null
93
+ this._aweThreshold = config.aweThreshold ?? 0.8
94
+ this._curiosityMin = config.curiosityRange?.[0] ?? 0.2
95
+ this._curiosityMax = config.curiosityRange?.[1] ?? 0.7
96
+ this._boredomRate = config.boredomRate ?? 0.005
97
+ this._curiosityFloor = config.curiosityFloor ?? 0.08
98
+ }
99
+ attachBus( bus: CognitiveBus ): void { this._bus = bus }
100
+
101
+ // ── Engine interface ─────────────────────────────────────
102
+
103
+ subscribes(): string[] {
104
+ return [
105
+ 'executive.prediction.formed',
106
+ 'percept.category.updated',
107
+ 'novelty.state.changed',
108
+ 'perception.novelty.spike',
109
+ ]
110
+ }
111
+
112
+ publishes(): CognitiveEventSchema[] { return [] }
113
+
114
+ onCognitiveEvent( e: CognitiveEvent ): StateCommands | void {
115
+ this._model.observe( e.type, e.salience )
116
+ if( e.type === 'executive.prediction.formed' ){
117
+ const p = e.payload as { predictedDomains: string[]; confidence: number }
118
+ if( p.predictedDomains.includes('aesthetic') )
119
+ this._model.setPrecision( 'emotion.awe', 1.0 + p.confidence * 0.5 )
120
+ }
121
+ if( e.type === 'percept.category.updated' ){
122
+ const p = e.payload as { category: string; count: number }
123
+ this._categoryBuffer.set(
124
+ p.category,
125
+ ( this._categoryBuffer.get( p.category ) ?? 0 ) + p.count
126
+ )
127
+ }
128
+ if( e.type === 'novelty.state.changed' || e.type === 'perception.novelty.spike' ){
129
+ const p = e.payload as { novelty: number }
130
+ this._cachedNovelty = p.novelty
131
+ }
132
+ }
133
+
134
+ snapshot(): Record<string, unknown> { return {} }
135
+
136
+ async react(
137
+ delta: Duration,
138
+ _tick: Tick,
139
+ state: ReadonlySimulationState,
140
+ _context: SimulationContext
141
+ ): Promise<EngineResult> {
142
+ const
143
+ events: Array<Omit<SimulationEvent, 'id' | 'timestamp' | 'tick'>> = [],
144
+ commands: StateCommands = { metrics: [] }
145
+
146
+ // Channel A (openness → aesthetic sensitivity): the awe threshold is read live as
147
+ // base ⊕ persona-prior. An open Will develops a lower threshold and so is moved to
148
+ // awe by beauty/novelty more readily. (The seed entity already existed but was ignored.)
149
+ const aweThreshold = readEffectiveParams( state, 'engine-config-aesthetic' ).aweThreshold ?? this._aweThreshold
150
+
151
+ // Consume category buffer for this tick
152
+ const categories = new Map( this._categoryBuffer )
153
+ this._categoryBuffer.clear()
154
+
155
+ // Use novelty from novelty.detector (proper expectation-based novelty signal)
156
+ const novelty = this._cachedNovelty
157
+
158
+ // Aesthetic dimensions from category buffer
159
+ const
160
+ complexity = this._assessComplexity( categories ),
161
+ coherence = this._assessCoherence( categories ),
162
+ safety = 1 - this._cachedFear,
163
+ perceptCount = Array.from( categories.values() ).reduce( ( s, v ) => s + v, 0 )
164
+
165
+ // Track stimulus history
166
+ this._recentStimulusCount.push( perceptCount )
167
+ if( this._recentStimulusCount.length > this._windowSize )
168
+ this._recentStimulusCount.shift()
169
+
170
+ const stimulusVariability = this._computeStimulusVariability()
171
+
172
+ const aweRaw = novelty * coherence * safety
173
+ const awe = aweRaw > aweThreshold ? aweRaw : 0
174
+
175
+ const curiosityRaw = this._curiosityCurve( novelty ) * safety * ( complexity * 0.7 + 0.3 )
176
+ const curiosity = Math.min( 1, curiosityRaw )
177
+
178
+ const interest = Math.min( 1,
179
+ complexity * 0.4
180
+ + novelty * 0.3
181
+ + ( 1 - Math.abs( novelty - 0.5 ) * 2 ) * 0.3
182
+ )
183
+
184
+ const previousBoredom = this._cachedBoredom
185
+ const boredom = stimulusVariability < 0.1
186
+ ? Math.min( 1, previousBoredom + this._boredomRate * ( delta / 1000 ) )
187
+ : Math.max( 0, previousBoredom - this._boredomRate * 2 * ( delta / 1000 ) )
188
+ this._cachedBoredom = boredom
189
+
190
+ commands.metrics!.push(
191
+ [ 'emotion.awe', awe ],
192
+ [ 'emotion.curiosity', curiosity ],
193
+ [ 'emotion.interest', interest ],
194
+ [ 'emotion.boredom', boredom ],
195
+ [ 'aesthetic.novelty', novelty ],
196
+ [ 'aesthetic.complexity', complexity ],
197
+ [ 'aesthetic.coherence', coherence ],
198
+ [ 'aesthetic.stimulus_variability', stimulusVariability ],
199
+ )
200
+
201
+ // Awe event (rare — transcendent moments)
202
+ if( awe > 0.6 )
203
+ events.push({
204
+ type: 'emotion.awe.significant',
205
+ source: this.name,
206
+ payload: { awe, novelty, coherence, complexity },
207
+ })
208
+
209
+ // Curiosity spike
210
+ if( curiosity > 0.7 )
211
+ events.push({
212
+ type: 'emotion.curiosity.elevated',
213
+ source: this.name,
214
+ payload: { curiosity, novelty, safety },
215
+ })
216
+
217
+ // Boredom alert + escalation drive
218
+ if( boredom > this._boredomSignificantCutoff ){
219
+ events.push({
220
+ type: 'emotion.boredom.significant',
221
+ source: this.name,
222
+ payload: { boredom, stimulusVariability },
223
+ })
224
+ this._consecutiveBoredomTicks++
225
+ } else {
226
+ this._consecutiveBoredomTicks = 0
227
+ }
228
+
229
+ // After N consecutive significant-boredom ticks, elevate a proactive drive so
230
+ // GoalManager can create a "seek engagement" goal rather than drifting in loops.
231
+ const engagementDrive = this._consecutiveBoredomTicks >= this._boredomEscalationThreshold
232
+ ? Math.min( 1, boredom * 1.2 ) // proportional intensity
233
+ : 0
234
+
235
+ commands.metrics!.push( [ 'drive.seek_engagement', engagementDrive ] )
236
+
237
+ if( engagementDrive > 0 && this._bus ){
238
+ this._bus.publish({
239
+ type: 'drive.seek_engagement', version: 1, sourceEngine: this.name,
240
+ salience: engagementDrive * 0.7,
241
+ payload: {
242
+ boredom,
243
+ consecutiveTicks: this._consecutiveBoredomTicks,
244
+ stimulusVariability,
245
+ },
246
+ })
247
+ }
248
+
249
+
250
+ // Phase C: publish cognitive event
251
+ const _bus = this._bus
252
+ if( _bus && awe > 0.4 ){
253
+ const predErr = this._model.observe( 'emotion.awe', awe )
254
+ if( !predErr.gated )
255
+ _bus.publish({ type: 'emotion.awe.experienced', version: 1, sourceEngine: this.name, salience: Math.min(1, awe * 2), payload: { awe } })
256
+ }
257
+ return { events: events.length > 0 ? events : undefined, commands }
258
+ }
259
+
260
+ // ── Aesthetic dimension assessment ───────────────────────
261
+
262
+ private _assessComplexity( categories: Map<string, number> ): number {
263
+ return Math.min( 1, categories.size / 8 )
264
+ }
265
+
266
+ private _assessCoherence( categories: Map<string, number> ): number {
267
+ const totalPercepts = Array.from( categories.values() ).reduce( ( s, v ) => s + v, 0 )
268
+ if( totalPercepts === 0 ) return 0.5
269
+
270
+ const
271
+ uniqueCategories = categories.size,
272
+ maxConcentration = Math.max( ...categories.values() ) / totalPercepts
273
+
274
+ const categoryScore = uniqueCategories >= 2 && uniqueCategories <= 4 ? 1
275
+ : uniqueCategories === 1 ? 0.3
276
+ : Math.max( 0, 1 - ( uniqueCategories - 4 ) * 0.15 )
277
+
278
+ return categoryScore * 0.5 + maxConcentration * 0.5
279
+ }
280
+
281
+ /**
282
+ * Map novelty to a curiosity curve.
283
+ * Peak curiosity at moderate novelty (not too boring, not too threatening).
284
+ */
285
+ private _curiosityCurve( novelty: number ): number {
286
+ if( novelty < this._curiosityMin ) return Math.max( this._curiosityFloor, novelty / this._curiosityMin * 0.3 )
287
+ if( novelty <= this._curiosityMax ){
288
+ // Inverted U in the optimal range
289
+ const mid = ( this._curiosityMin + this._curiosityMax ) / 2
290
+ const range = ( this._curiosityMax - this._curiosityMin ) / 2
291
+ return 1 - Math.abs( novelty - mid ) / range * 0.4
292
+ }
293
+ // Beyond optimal range — declining but still present (if safe)
294
+ return Math.max( 0.2, 1 - ( novelty - this._curiosityMax ) * 1.5 )
295
+ }
296
+
297
+ /**
298
+ * Compute variability in recent stimulus levels.
299
+ * Low variability = predictable = boring.
300
+ */
301
+ private _computeStimulusVariability(): number {
302
+ if( this._recentStimulusCount.length < 3 ) return 0.5
303
+
304
+ const mean = this._recentStimulusCount.reduce( ( s, v ) => s + v, 0 )
305
+ / this._recentStimulusCount.length
306
+
307
+ const variance = this._recentStimulusCount.reduce( ( s, v ) => s + ( v - mean ) ** 2, 0 )
308
+ / this._recentStimulusCount.length
309
+
310
+ // Normalize — variance of 0 = 0 variability, variance > 25 = high variability
311
+ return Math.min( 1, variance / 25 )
312
+ }
313
+ }