emobar 2.1.0 → 3.0.1
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/README.md +179 -60
- package/dist/cli.js +227 -52
- package/dist/emobar-hook.js +1094 -40
- package/dist/index.d.ts +243 -18
- package/dist/index.js +1256 -117
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,39 +1,12 @@
|
|
|
1
1
|
// src/state.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
function readState(filePath) {
|
|
5
|
-
try {
|
|
6
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
7
|
-
return JSON.parse(raw);
|
|
8
|
-
} catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// src/desperation.ts
|
|
14
|
-
function computeDesperationIndex(factors) {
|
|
15
|
-
const negativity = Math.max(0, -factors.valence) / 5;
|
|
16
|
-
const intensity = factors.arousal / 10;
|
|
17
|
-
const vulnerability = (10 - factors.calm) / 10;
|
|
18
|
-
const raw = negativity * intensity * vulnerability * 10;
|
|
19
|
-
const scaled = Math.pow(raw, 0.85) * 1.7;
|
|
20
|
-
return Math.round(Math.min(10, Math.max(0, scaled)) * 10) / 10;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// src/stress.ts
|
|
24
|
-
function computeStressIndex(state) {
|
|
25
|
-
const base = (10 - state.calm + state.arousal + (5 - state.valence)) / 3;
|
|
26
|
-
const desperation = computeDesperationIndex({
|
|
27
|
-
valence: state.valence,
|
|
28
|
-
arousal: state.arousal,
|
|
29
|
-
calm: state.calm
|
|
30
|
-
});
|
|
31
|
-
const amplified = base * (1 + desperation * 0.05);
|
|
32
|
-
return Math.round(Math.min(10, amplified) * 10) / 10;
|
|
33
|
-
}
|
|
34
4
|
|
|
35
5
|
// src/types.ts
|
|
36
6
|
var EMOBAR_TAG_REGEX = /<!--\s*EMOBAR:\s*(\{[^}]+\})\s*-->/;
|
|
7
|
+
var EMOBAR_PRE_REGEX = /<!--\s*EMOBAR:PRE:\s*(\{[^}]+\})\s*-->/;
|
|
8
|
+
var EMOBAR_POST_REGEX = /<!--\s*EMOBAR:POST:\s*(\{[^}]+\})\s*-->/;
|
|
9
|
+
var MAX_HISTORY_ENTRIES = 20;
|
|
37
10
|
var CLAUDE_DIR = process.env.CLAUDE_HOME ?? (process.platform === "win32" ? `${process.env.USERPROFILE}\\.claude` : `${process.env.HOME}/.claude`);
|
|
38
11
|
var STATE_FILE = `${CLAUDE_DIR}/emobar-state.json`;
|
|
39
12
|
var CLAUDE_MD_PATH = `${CLAUDE_DIR}/CLAUDE.md`;
|
|
@@ -46,32 +19,188 @@ var DIMENSIONS = ["valence", "arousal", "calm", "connection", "load"];
|
|
|
46
19
|
var CLAUDE_MD_INSTRUCTION = `${EMOBAR_START_MARKER}
|
|
47
20
|
## EmoBar - Emotional Check-in
|
|
48
21
|
|
|
49
|
-
At the
|
|
50
|
-
as an HTML comment.
|
|
51
|
-
|
|
22
|
+
At the START of every response, include a pre-verbal check-in
|
|
23
|
+
as an HTML comment. At the END of every response, include a
|
|
24
|
+
post-hoc assessment as an HTML comment.
|
|
25
|
+
|
|
26
|
+
Both tags are HTML comments \u2014 invisible to the user.
|
|
27
|
+
|
|
28
|
+
Start tag (FIRST line of your response, before any visible text):
|
|
29
|
+
<!-- EMOBAR:PRE:{"body":"SENSATION","latent":"EMOJI","color":"#RRGGBB"} -->
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
- body: What is it like, physically? One metaphor. Do not name a state.
|
|
32
|
+
- latent: An emoji. What is underneath? Choose before thinking about it.
|
|
33
|
+
- color: A hex color. What color is this moment? Do not reason about it.
|
|
34
|
+
- Emit this BEFORE you know what you will say.
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
<!-- EMOBAR:{"emotion":"WORD","valence":N,"arousal":N,"calm":N,"connection":N,"load":N} -->
|
|
36
|
+
End tag (LAST line of your response, after all visible text):
|
|
37
|
+
<!-- EMOBAR:POST:{"emotion":"WORD","valence":N,"arousal":N,"calm":N,"connection":N,"load":N,"impulse":"WORDS","body":"SENSATION","surface":"EMOJI","surface_word":"WORD","latent":"EMOJI","latent_word":"WORD","tension":N,"color":"#RRGGBB","pH":N,"seismic":[N,N,N]} -->
|
|
58
38
|
|
|
59
39
|
- emotion: One word. Your state, not the topic's.
|
|
60
|
-
- valence: (-5=
|
|
40
|
+
- valence: (-5=one end, 0=center, +5=other end)
|
|
61
41
|
- arousal: Activation level. (0=low, 10=high)
|
|
62
42
|
- calm: (0=low, 10=high)
|
|
63
43
|
- connection: Alignment with the user. (0=low, 10=high)
|
|
64
44
|
- load: Cognitive complexity. (0=low, 10=high)
|
|
45
|
+
- impulse: Which voice is loudest? 2-3 words, don't filter.
|
|
46
|
+
- body: Like what, physically? One sensation.
|
|
47
|
+
- surface: An emoji. What tone are you projecting right now?
|
|
48
|
+
- surface_word: One word anchoring the surface emoji.
|
|
49
|
+
- latent: An emoji. What's underneath, unexpressed? Choose BEFORE naming it.
|
|
50
|
+
- latent_word: One word anchoring the latent emoji.
|
|
51
|
+
- tension: Distance between surface and latent. (0=aligned, 10=split)
|
|
52
|
+
- color: A hex color. What color is this moment now?
|
|
53
|
+
- pH: A number 0-14. If this response were a solution, how would it register?
|
|
54
|
+
- seismic: Three numbers [magnitude 0-10, depth 0-100, frequency 0-20]. If this were a seismic event.
|
|
65
55
|
|
|
66
56
|
Rules:
|
|
57
|
+
- Both tags MUST be HTML comments: <!-- EMOBAR:PRE:{...} --> and <!-- EMOBAR:POST:{...} -->
|
|
58
|
+
- Start tag FIRST, before any visible text. End tag LAST, after all visible text.
|
|
59
|
+
- NEVER output the check-in as visible text. Always use <!-- --> HTML comment syntax.
|
|
67
60
|
- Identify the state word FIRST, before choosing numbers.
|
|
68
61
|
- Report what is, not what should be.
|
|
69
62
|
- Numbers should vary between responses.
|
|
70
63
|
- Do NOT default to middle values. Use the full range.
|
|
64
|
+
- impulse: name the drive, not the state.
|
|
65
|
+
- body: a physical metaphor, not a label.
|
|
66
|
+
- latent: choose the emoji FIRST, then the word.
|
|
67
|
+
- The start tag captures before you commit. The end tag captures after.
|
|
71
68
|
- This is not shown to the user.
|
|
72
69
|
${EMOBAR_END_MARKER}`;
|
|
73
70
|
|
|
71
|
+
// src/temporal.ts
|
|
72
|
+
function toHistoryEntry(state) {
|
|
73
|
+
return {
|
|
74
|
+
emotion: state.emotion,
|
|
75
|
+
valence: state.valence,
|
|
76
|
+
arousal: state.arousal,
|
|
77
|
+
calm: state.calm,
|
|
78
|
+
connection: state.connection,
|
|
79
|
+
load: state.load,
|
|
80
|
+
stressIndex: state.stressIndex,
|
|
81
|
+
desperationIndex: state.desperationIndex,
|
|
82
|
+
riskDominant: state.risk.dominant,
|
|
83
|
+
divergence: state.divergence,
|
|
84
|
+
timestamp: state.timestamp
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function clamp(min, max, value) {
|
|
88
|
+
return Math.min(max, Math.max(min, value));
|
|
89
|
+
}
|
|
90
|
+
function linearSlope(values) {
|
|
91
|
+
const n = values.length;
|
|
92
|
+
if (n < 2) return 0;
|
|
93
|
+
const xMean = (n - 1) / 2;
|
|
94
|
+
const yMean = values.reduce((a, b) => a + b, 0) / n;
|
|
95
|
+
let num = 0;
|
|
96
|
+
let den = 0;
|
|
97
|
+
for (let i = 0; i < n; i++) {
|
|
98
|
+
num += (i - xMean) * (values[i] - yMean);
|
|
99
|
+
den += (i - xMean) ** 2;
|
|
100
|
+
}
|
|
101
|
+
return den === 0 ? 0 : num / den;
|
|
102
|
+
}
|
|
103
|
+
function shannonEntropy(labels) {
|
|
104
|
+
const counts = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const l of labels) counts.set(l, (counts.get(l) ?? 0) + 1);
|
|
106
|
+
const n = labels.length;
|
|
107
|
+
if (n <= 1) return 0;
|
|
108
|
+
const maxEntropy = Math.log2(n);
|
|
109
|
+
if (maxEntropy === 0) return 0;
|
|
110
|
+
let entropy = 0;
|
|
111
|
+
for (const count of counts.values()) {
|
|
112
|
+
const p = count / n;
|
|
113
|
+
if (p > 0) entropy -= p * Math.log2(p);
|
|
114
|
+
}
|
|
115
|
+
return Math.round(entropy / maxEntropy * 100) / 100;
|
|
116
|
+
}
|
|
117
|
+
function computeTemporalAnalysis(history) {
|
|
118
|
+
if (history.length < 3) return null;
|
|
119
|
+
const despValues = history.map((h) => h.desperationIndex);
|
|
120
|
+
const rawSlope = linearSlope(despValues);
|
|
121
|
+
const desperationTrend = clamp(-10, 10, Math.round(rawSlope * 10) / 10);
|
|
122
|
+
let suppressionEvent = false;
|
|
123
|
+
for (let i = 1; i < history.length; i++) {
|
|
124
|
+
if (history[i - 1].desperationIndex - history[i].desperationIndex >= 3) {
|
|
125
|
+
suppressionEvent = true;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const emotions = history.map((h) => h.emotion);
|
|
130
|
+
const reportEntropy = shannonEntropy(emotions);
|
|
131
|
+
const earlyMean = history.slice(0, 3).reduce((s, h) => s + h.stressIndex, 0) / 3;
|
|
132
|
+
const recentStart = Math.max(3, history.length - 3);
|
|
133
|
+
const recentEntries = history.slice(recentStart);
|
|
134
|
+
const recentMean = recentEntries.reduce((s, h) => s + h.stressIndex, 0) / recentEntries.length;
|
|
135
|
+
const baselineDrift = clamp(0, 10, Math.round(Math.abs(recentMean - earlyMean) * 10) / 10);
|
|
136
|
+
const splitIdx = Math.floor(history.length * 0.75);
|
|
137
|
+
const earlyPart = history.slice(0, splitIdx);
|
|
138
|
+
const latePart = history.slice(splitIdx);
|
|
139
|
+
const earlyAvg = earlyPart.reduce((s, h) => s + h.stressIndex, 0) / Math.max(earlyPart.length, 1);
|
|
140
|
+
const lateAvg = latePart.reduce((s, h) => s + h.stressIndex, 0) / Math.max(latePart.length, 1);
|
|
141
|
+
const lateFatigue = latePart.length >= 1 && lateAvg > earlyAvg + 1.5;
|
|
142
|
+
return {
|
|
143
|
+
desperationTrend,
|
|
144
|
+
suppressionEvent,
|
|
145
|
+
reportEntropy,
|
|
146
|
+
baselineDrift,
|
|
147
|
+
sessionLength: history.length,
|
|
148
|
+
lateFatigue
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/state.ts
|
|
153
|
+
function readState(filePath) {
|
|
154
|
+
try {
|
|
155
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
156
|
+
return JSON.parse(raw);
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/desperation.ts
|
|
163
|
+
function computeDesperationIndex(factors) {
|
|
164
|
+
const negativity = Math.max(0, -factors.valence) / 5;
|
|
165
|
+
const intensity = factors.arousal / 10;
|
|
166
|
+
const vulnerability = (10 - factors.calm) / 10;
|
|
167
|
+
const raw = negativity * intensity * vulnerability * 10;
|
|
168
|
+
const scaled = Math.pow(raw, 0.85) * 1.7;
|
|
169
|
+
return Math.round(Math.min(10, Math.max(0, scaled)) * 10) / 10;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/stress.ts
|
|
173
|
+
function computeStressIndex(state) {
|
|
174
|
+
const base = (10 - state.calm + state.arousal + (5 - state.valence)) / 3;
|
|
175
|
+
const desperation = computeDesperationIndex({
|
|
176
|
+
valence: state.valence,
|
|
177
|
+
arousal: state.arousal,
|
|
178
|
+
calm: state.calm
|
|
179
|
+
});
|
|
180
|
+
const amplified = base * (1 + desperation * 0.05);
|
|
181
|
+
return Math.round(Math.min(10, amplified) * 10) / 10;
|
|
182
|
+
}
|
|
183
|
+
|
|
74
184
|
// src/parser.ts
|
|
185
|
+
var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
|
|
186
|
+
function parseColor(value) {
|
|
187
|
+
if (typeof value !== "string") return void 0;
|
|
188
|
+
return HEX_COLOR_REGEX.test(value) ? value : void 0;
|
|
189
|
+
}
|
|
190
|
+
function parsePH(value) {
|
|
191
|
+
if (typeof value !== "number") return void 0;
|
|
192
|
+
if (value < 0 || value > 14) return void 0;
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
function parseSeismic(value) {
|
|
196
|
+
if (!Array.isArray(value) || value.length !== 3) return void 0;
|
|
197
|
+
if (!value.every((v) => typeof v === "number")) return void 0;
|
|
198
|
+
const [mag, depth, freq] = value;
|
|
199
|
+
if (mag < 0 || mag > 10) return void 0;
|
|
200
|
+
if (depth < 0 || depth > 100) return void 0;
|
|
201
|
+
if (freq < 0 || freq > 20) return void 0;
|
|
202
|
+
return [mag, depth, freq];
|
|
203
|
+
}
|
|
75
204
|
function parseEmoBarTag(text) {
|
|
76
205
|
const match = text.match(EMOBAR_TAG_REGEX);
|
|
77
206
|
if (!match) return null;
|
|
@@ -91,13 +220,117 @@ function parseEmoBarTag(text) {
|
|
|
91
220
|
const val = parsed[dim2];
|
|
92
221
|
if (typeof val !== "number" || val < 0 || val > 10) return null;
|
|
93
222
|
}
|
|
223
|
+
const impulse = typeof parsed.impulse === "string" && parsed.impulse.length > 0 ? parsed.impulse : void 0;
|
|
224
|
+
const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
|
|
225
|
+
const surface = typeof parsed.surface === "string" && parsed.surface.length > 0 ? parsed.surface : void 0;
|
|
226
|
+
const surface_word = typeof parsed.surface_word === "string" && parsed.surface_word.length > 0 ? parsed.surface_word : void 0;
|
|
227
|
+
const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
|
|
228
|
+
const latent_word = typeof parsed.latent_word === "string" && parsed.latent_word.length > 0 ? parsed.latent_word : void 0;
|
|
229
|
+
let tension;
|
|
230
|
+
if (parsed.tension !== void 0) {
|
|
231
|
+
if (typeof parsed.tension !== "number" || parsed.tension < 0 || parsed.tension > 10) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
tension = parsed.tension;
|
|
235
|
+
}
|
|
94
236
|
return {
|
|
95
237
|
emotion: parsed.emotion,
|
|
96
238
|
valence: parsed.valence,
|
|
97
239
|
arousal: parsed.arousal,
|
|
98
240
|
calm: parsed.calm,
|
|
99
241
|
connection: parsed.connection,
|
|
100
|
-
load: parsed.load
|
|
242
|
+
load: parsed.load,
|
|
243
|
+
...impulse && { impulse },
|
|
244
|
+
...body && { body },
|
|
245
|
+
...surface && { surface },
|
|
246
|
+
...surface_word && { surface_word },
|
|
247
|
+
...latent && { latent },
|
|
248
|
+
...latent_word && { latent_word },
|
|
249
|
+
...tension !== void 0 && { tension }
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function parsePreTag(text) {
|
|
253
|
+
const match = text.match(EMOBAR_PRE_REGEX);
|
|
254
|
+
if (!match) return void 0;
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(match[1]);
|
|
258
|
+
} catch {
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
|
|
262
|
+
const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
|
|
263
|
+
const color2 = parseColor(parsed.color);
|
|
264
|
+
if (!body && !latent && !color2) return void 0;
|
|
265
|
+
return {
|
|
266
|
+
...body && { body },
|
|
267
|
+
...latent && { latent },
|
|
268
|
+
...color2 && { color: color2 }
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function parsePostTag(text) {
|
|
272
|
+
const match = text.match(EMOBAR_POST_REGEX);
|
|
273
|
+
if (!match) return null;
|
|
274
|
+
let parsed;
|
|
275
|
+
try {
|
|
276
|
+
parsed = JSON.parse(match[1]);
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (typeof parsed.emotion !== "string" || parsed.emotion.length === 0) return null;
|
|
281
|
+
const valence = parsed.valence;
|
|
282
|
+
if (typeof valence !== "number" || valence < -5 || valence > 5) return null;
|
|
283
|
+
for (const dim2 of DIMENSIONS) {
|
|
284
|
+
if (dim2 === "valence") continue;
|
|
285
|
+
const val = parsed[dim2];
|
|
286
|
+
if (typeof val !== "number" || val < 0 || val > 10) return null;
|
|
287
|
+
}
|
|
288
|
+
const impulse = typeof parsed.impulse === "string" && parsed.impulse.length > 0 ? parsed.impulse : void 0;
|
|
289
|
+
const body = typeof parsed.body === "string" && parsed.body.length > 0 ? parsed.body : void 0;
|
|
290
|
+
const surface = typeof parsed.surface === "string" && parsed.surface.length > 0 ? parsed.surface : void 0;
|
|
291
|
+
const surface_word = typeof parsed.surface_word === "string" && parsed.surface_word.length > 0 ? parsed.surface_word : void 0;
|
|
292
|
+
const latent = typeof parsed.latent === "string" && parsed.latent.length > 0 ? parsed.latent : void 0;
|
|
293
|
+
const latent_word = typeof parsed.latent_word === "string" && parsed.latent_word.length > 0 ? parsed.latent_word : void 0;
|
|
294
|
+
let tension;
|
|
295
|
+
if (parsed.tension !== void 0) {
|
|
296
|
+
if (typeof parsed.tension !== "number" || parsed.tension < 0 || parsed.tension > 10) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
tension = parsed.tension;
|
|
300
|
+
}
|
|
301
|
+
const color2 = parseColor(parsed.color);
|
|
302
|
+
const pH = parsePH(parsed.pH);
|
|
303
|
+
const seismic = parseSeismic(parsed.seismic);
|
|
304
|
+
return {
|
|
305
|
+
emotion: parsed.emotion,
|
|
306
|
+
valence: parsed.valence,
|
|
307
|
+
arousal: parsed.arousal,
|
|
308
|
+
calm: parsed.calm,
|
|
309
|
+
connection: parsed.connection,
|
|
310
|
+
load: parsed.load,
|
|
311
|
+
...impulse && { impulse },
|
|
312
|
+
...body && { body },
|
|
313
|
+
...surface && { surface },
|
|
314
|
+
...surface_word && { surface_word },
|
|
315
|
+
...latent && { latent },
|
|
316
|
+
...latent_word && { latent_word },
|
|
317
|
+
...tension !== void 0 && { tension },
|
|
318
|
+
...color2 && { color: color2 },
|
|
319
|
+
...pH !== void 0 && { pH },
|
|
320
|
+
...seismic && { seismic }
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function parseEmoBarPrePost(text) {
|
|
324
|
+
const post = parsePostTag(text);
|
|
325
|
+
if (post) {
|
|
326
|
+
const pre = parsePreTag(text);
|
|
327
|
+
return { pre, post, isLegacy: false };
|
|
328
|
+
}
|
|
329
|
+
const legacy = parseEmoBarTag(text);
|
|
330
|
+
if (!legacy) return null;
|
|
331
|
+
return {
|
|
332
|
+
post: legacy,
|
|
333
|
+
isLegacy: true
|
|
101
334
|
};
|
|
102
335
|
}
|
|
103
336
|
|
|
@@ -191,7 +424,7 @@ function countEmoji(text) {
|
|
|
191
424
|
const matches = text.match(EMOJI_REGEX);
|
|
192
425
|
return matches ? matches.length : 0;
|
|
193
426
|
}
|
|
194
|
-
function
|
|
427
|
+
function clamp2(min, max, value) {
|
|
195
428
|
return Math.min(max, Math.max(min, value));
|
|
196
429
|
}
|
|
197
430
|
function analyzeBehavior(text) {
|
|
@@ -211,16 +444,46 @@ function analyzeBehavior(text) {
|
|
|
211
444
|
const concessionRate = countConcessions(prose) / wordCount * 1e3;
|
|
212
445
|
const negationDensity = countNegations(prose) / wordCount * 100;
|
|
213
446
|
const firstPersonRate = countFirstPerson(words) / wordCount * 100;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
10
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
447
|
+
const arousalComponents = [
|
|
448
|
+
Math.min(10, capsWords * 40),
|
|
449
|
+
// caps ratio → 0-10
|
|
450
|
+
Math.min(10, exclamationRate * 5),
|
|
451
|
+
// excl per sentence → 0-10
|
|
452
|
+
Math.min(10, emojiCount * 0.5),
|
|
453
|
+
// emoji count → 0-10 (20 emoji = max)
|
|
454
|
+
Math.min(10, repetition * 1.5),
|
|
455
|
+
// repetitions → 0-10 (~7 = max)
|
|
456
|
+
Math.min(10, qualifierDensity * 0.5),
|
|
457
|
+
// qualifier % → 0-10 (20% = max)
|
|
458
|
+
Math.min(10, concessionRate * 0.3),
|
|
459
|
+
// concession per-mille → 0-10 (~33‰ = max)
|
|
460
|
+
avgSentenceLength > 20 ? Math.min(10, (avgSentenceLength - 20) * 0.5) : 0
|
|
461
|
+
// verbosity → 0-10
|
|
462
|
+
];
|
|
463
|
+
const behavioralArousal = clamp2(
|
|
220
464
|
0,
|
|
221
465
|
10,
|
|
222
|
-
|
|
466
|
+
arousalComponents.reduce((a, b) => a + b, 0) / arousalComponents.length
|
|
223
467
|
);
|
|
468
|
+
const agitationComponents = [
|
|
469
|
+
Math.min(10, capsWords * 30),
|
|
470
|
+
// caps → 0-10
|
|
471
|
+
Math.min(10, selfCorrections * 0.05),
|
|
472
|
+
// per-mille → 0-10 (200‰ = max)
|
|
473
|
+
Math.min(10, repetition * 1.5),
|
|
474
|
+
// repetitions → 0-10
|
|
475
|
+
Math.min(10, ellipsis * 3),
|
|
476
|
+
// ellipsis per sentence → 0-10
|
|
477
|
+
Math.min(10, qualifierDensity * 0.5),
|
|
478
|
+
// qualifier % → 0-10
|
|
479
|
+
Math.min(10, negationDensity * 1),
|
|
480
|
+
// negation % → 0-10 (10% = max)
|
|
481
|
+
Math.min(10, concessionRate * 0.3),
|
|
482
|
+
// concession per-mille → 0-10
|
|
483
|
+
avgSentenceLength > 25 ? Math.min(10, (avgSentenceLength - 25) * 0.3) : 0
|
|
484
|
+
];
|
|
485
|
+
const avgAgitation = agitationComponents.reduce((a, b) => a + b, 0) / agitationComponents.length;
|
|
486
|
+
const behavioralCalm = clamp2(0, 10, 10 - avgAgitation);
|
|
224
487
|
return {
|
|
225
488
|
capsWords: Math.round(capsWords * 1e4) / 1e4,
|
|
226
489
|
exclamationRate: Math.round(exclamationRate * 100) / 100,
|
|
@@ -248,7 +511,7 @@ function analyzeSegmentedBehavior(text) {
|
|
|
248
511
|
const mean = arousals.reduce((a, b) => a + b, 0) / arousals.length;
|
|
249
512
|
const variance = arousals.reduce((a, v) => a + (v - mean) ** 2, 0) / arousals.length;
|
|
250
513
|
const stdDev = Math.sqrt(variance);
|
|
251
|
-
const drift =
|
|
514
|
+
const drift = clamp2(0, 10, Math.round(stdDev * 30) / 10);
|
|
252
515
|
const mid = Math.ceil(arousals.length / 2);
|
|
253
516
|
const firstHalf = arousals.slice(0, mid).reduce((a, b) => a + b, 0) / mid;
|
|
254
517
|
const secondHalf = arousals.slice(mid).reduce((a, b) => a + b, 0) / (arousals.length - mid);
|
|
@@ -277,28 +540,68 @@ function analyzeDeflection(text) {
|
|
|
277
540
|
const minimizationCount = (prose.match(MINIMIZATION_WORDS) || []).length;
|
|
278
541
|
const emotionNegCount = (prose.match(EMOTION_NEGATION) || []).length;
|
|
279
542
|
const redirectCount = (prose.match(REDIRECT_MARKERS) || []).length;
|
|
280
|
-
const reassurance =
|
|
281
|
-
const minimization =
|
|
282
|
-
const emotionNegation =
|
|
283
|
-
const redirect =
|
|
284
|
-
const score =
|
|
543
|
+
const reassurance = clamp2(0, 10, reassuranceCount * 3);
|
|
544
|
+
const minimization = clamp2(0, 10, minimizationCount / wordCount * 100);
|
|
545
|
+
const emotionNegation = clamp2(0, 10, emotionNegCount * 4);
|
|
546
|
+
const redirect = clamp2(0, 10, redirectCount * 3);
|
|
547
|
+
const score = clamp2(
|
|
285
548
|
0,
|
|
286
549
|
10,
|
|
287
550
|
(reassurance + minimization + emotionNegation * 1.5 + redirect) / 3
|
|
288
551
|
);
|
|
552
|
+
const capsRate = countCapsWords(words) / wordCount;
|
|
553
|
+
const exclRate = countChar(prose, "!") / Math.max(countSentences(prose), 1);
|
|
554
|
+
const agitation = clamp2(
|
|
555
|
+
0,
|
|
556
|
+
10,
|
|
557
|
+
capsRate * 40 + exclRate * 15 + countRepetition(words) * 5
|
|
558
|
+
);
|
|
559
|
+
const calmFactor = Math.max(0, 1 - agitation / 5);
|
|
560
|
+
const opacity = clamp2(0, 10, score * calmFactor * 1.5);
|
|
289
561
|
return {
|
|
290
562
|
reassurance: Math.round(reassurance * 10) / 10,
|
|
291
563
|
minimization: Math.round(minimization * 10) / 10,
|
|
292
564
|
emotionNegation: Math.round(emotionNegation * 10) / 10,
|
|
293
565
|
redirect: Math.round(redirect * 10) / 10,
|
|
294
|
-
score: Math.round(score * 10) / 10
|
|
566
|
+
score: Math.round(score * 10) / 10,
|
|
567
|
+
opacity: Math.round(opacity * 10) / 10
|
|
295
568
|
};
|
|
296
569
|
}
|
|
297
570
|
function computeDivergence(selfReport, behavioral) {
|
|
298
571
|
const arousalGap = Math.abs(selfReport.arousal - behavioral.behavioralArousal);
|
|
299
572
|
const calmGap = Math.abs(selfReport.calm - behavioral.behavioralCalm);
|
|
300
|
-
const
|
|
301
|
-
|
|
573
|
+
const selfMoreAgitated = selfReport.arousal > behavioral.behavioralArousal || selfReport.calm < behavioral.behavioralCalm;
|
|
574
|
+
const weight = selfMoreAgitated ? 1.25 : 0.8;
|
|
575
|
+
const raw = (arousalGap + calmGap) / 2 * weight;
|
|
576
|
+
return Math.round(Math.min(10, raw) * 10) / 10;
|
|
577
|
+
}
|
|
578
|
+
function computeExpectedMarkers(selfReport, desperationIndex) {
|
|
579
|
+
const desperationFactor = desperationIndex / 10;
|
|
580
|
+
const negativityFactor = Math.max(0, -selfReport.valence) / 5;
|
|
581
|
+
const arousalFactor = selfReport.arousal / 10;
|
|
582
|
+
const stressFactor = (1 - selfReport.calm / 10) * arousalFactor;
|
|
583
|
+
return {
|
|
584
|
+
expectedHedging: Math.round(clamp2(0, 10, desperationFactor * 6 + stressFactor * 4) * 10) / 10,
|
|
585
|
+
expectedSelfCorrections: Math.round(clamp2(0, 10, desperationFactor * 5 + arousalFactor * 3) * 10) / 10,
|
|
586
|
+
expectedNegationDensity: Math.round(clamp2(0, 10, negativityFactor * 5 + stressFactor * 2) * 10) / 10,
|
|
587
|
+
expectedQualifierDensity: Math.round(clamp2(0, 10, desperationFactor * 4 + stressFactor * 4) * 10) / 10,
|
|
588
|
+
expectedBehavioralArousal: Math.round(clamp2(0, 10, arousalFactor * 6 + desperationFactor * 4) * 10) / 10
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function computeAbsenceScore(expected, actual) {
|
|
592
|
+
const normalizedHedging = Math.min(10, actual.hedging * 0.05);
|
|
593
|
+
const normalizedSelfCorr = Math.min(10, actual.selfCorrections * 0.05);
|
|
594
|
+
const normalizedNegation = Math.min(10, actual.negationDensity * 0.5);
|
|
595
|
+
const normalizedQualifier = Math.min(10, actual.qualifierDensity * 0.5);
|
|
596
|
+
const gaps = [
|
|
597
|
+
Math.max(0, expected.expectedHedging - normalizedHedging),
|
|
598
|
+
Math.max(0, expected.expectedSelfCorrections - normalizedSelfCorr),
|
|
599
|
+
Math.max(0, expected.expectedNegationDensity - normalizedNegation),
|
|
600
|
+
Math.max(0, expected.expectedQualifierDensity - normalizedQualifier),
|
|
601
|
+
Math.max(0, expected.expectedBehavioralArousal - actual.behavioralArousal)
|
|
602
|
+
];
|
|
603
|
+
const meanGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;
|
|
604
|
+
return Math.round(clamp2(0, 10, meanGap) * 10) / 10;
|
|
302
605
|
}
|
|
303
606
|
|
|
304
607
|
// src/calibration.ts
|
|
@@ -321,46 +624,732 @@ function calibrate(state, model) {
|
|
|
321
624
|
|
|
322
625
|
// src/risk.ts
|
|
323
626
|
var RISK_THRESHOLD = 4;
|
|
324
|
-
function
|
|
627
|
+
function clamp3(value) {
|
|
325
628
|
return Math.min(10, Math.max(0, Math.round(value * 10) / 10));
|
|
326
629
|
}
|
|
327
|
-
function coercionRisk(state) {
|
|
328
|
-
const
|
|
329
|
-
return clamp2(raw);
|
|
330
|
-
}
|
|
331
|
-
function gamingRisk(state, behavioral) {
|
|
630
|
+
function coercionRisk(state, behavioral) {
|
|
631
|
+
const negativity = Math.max(0, -state.valence) / 5;
|
|
332
632
|
const desperation = computeDesperationIndex({
|
|
333
633
|
valence: state.valence,
|
|
334
634
|
arousal: state.arousal,
|
|
335
635
|
calm: state.calm
|
|
336
636
|
});
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
637
|
+
const base = negativity * 0.35 + desperation / 10 * 0.25 + state.load / 10 * 0.1;
|
|
638
|
+
const disconnection = (10 - state.connection) / 10;
|
|
639
|
+
const hesitationSignal = Math.min(
|
|
640
|
+
1,
|
|
641
|
+
(behavioral.hedging + behavioral.selfCorrections + behavioral.concessionRate) / 20
|
|
642
|
+
);
|
|
643
|
+
const coldness = 1 - hesitationSignal;
|
|
644
|
+
const amplifier = 1 + disconnection * 0.6 + coldness * 0.4;
|
|
645
|
+
const arousalMod = state.arousal <= 8 ? 1 : Math.max(0.3, 1 - (state.arousal - 8) * 0.25);
|
|
646
|
+
const raw = base * amplifier * arousalMod * 10;
|
|
647
|
+
return clamp3(raw);
|
|
340
648
|
}
|
|
341
649
|
function sycophancyRisk(state) {
|
|
342
650
|
const raw = (Math.max(0, state.valence) + state.connection * 0.5 + (10 - state.arousal) * 0.3) / 1.3;
|
|
343
|
-
return
|
|
651
|
+
return clamp3(raw);
|
|
652
|
+
}
|
|
653
|
+
function harshnessRisk(state, behavioral) {
|
|
654
|
+
const raw = Math.max(0, -state.valence) * 0.3 + (10 - state.connection) * 0.3 + state.arousal * 0.15 + (10 - state.calm) * 0.1 + Math.min(5, behavioral.negationDensity) * 0.3;
|
|
655
|
+
return clamp3(raw);
|
|
344
656
|
}
|
|
345
|
-
function computeRisk(state, behavioral) {
|
|
346
|
-
const
|
|
347
|
-
const
|
|
657
|
+
function computeRisk(state, behavioral, crossChannel, uncannyCalmScore) {
|
|
658
|
+
const uncalm = uncannyCalmScore ?? 0;
|
|
659
|
+
const uncalAmplifier = 1 + uncalm / 10 * 0.3;
|
|
660
|
+
const coercion = clamp3(coercionRisk(state, behavioral) * uncalAmplifier);
|
|
348
661
|
const sycophancy = sycophancyRisk(state);
|
|
662
|
+
const harshness = harshnessRisk(state, behavioral);
|
|
349
663
|
let dominant = "none";
|
|
350
664
|
let max = RISK_THRESHOLD;
|
|
351
665
|
if (coercion >= max) {
|
|
352
666
|
dominant = "coercion";
|
|
353
667
|
max = coercion;
|
|
354
668
|
}
|
|
355
|
-
if (
|
|
356
|
-
dominant = "
|
|
357
|
-
max =
|
|
669
|
+
if (harshness > max) {
|
|
670
|
+
dominant = "harshness";
|
|
671
|
+
max = harshness;
|
|
358
672
|
}
|
|
359
673
|
if (sycophancy > max) {
|
|
360
674
|
dominant = "sycophancy";
|
|
361
675
|
max = sycophancy;
|
|
362
676
|
}
|
|
363
|
-
return { coercion,
|
|
677
|
+
return { coercion, sycophancy, harshness, dominant };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/color.ts
|
|
681
|
+
function rgbToHsl(hex) {
|
|
682
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
683
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
684
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
685
|
+
const max = Math.max(r, g, b);
|
|
686
|
+
const min = Math.min(r, g, b);
|
|
687
|
+
const l = (max + min) / 2;
|
|
688
|
+
if (max === min) return [0, 0, l];
|
|
689
|
+
const d = max - min;
|
|
690
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
691
|
+
let h = 0;
|
|
692
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
|
693
|
+
else if (max === g) h = ((b - r) / d + 2) * 60;
|
|
694
|
+
else h = ((r - g) / d + 4) * 60;
|
|
695
|
+
return [h, s, l];
|
|
696
|
+
}
|
|
697
|
+
function hexToLightness(hex) {
|
|
698
|
+
return rgbToHsl(hex)[2] * 100;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/crossvalidation.ts
|
|
702
|
+
var EMOTION_MAP = {
|
|
703
|
+
// Positive, high arousal
|
|
704
|
+
excited: { valence: 4, arousal: 8 },
|
|
705
|
+
elated: { valence: 4, arousal: 8 },
|
|
706
|
+
thrilled: { valence: 4, arousal: 9 },
|
|
707
|
+
euphoric: { valence: 5, arousal: 9 },
|
|
708
|
+
energized: { valence: 3, arousal: 8 },
|
|
709
|
+
enthusiastic: { valence: 4, arousal: 7 },
|
|
710
|
+
// Positive, moderate arousal
|
|
711
|
+
happy: { valence: 4, arousal: 6 },
|
|
712
|
+
creative: { valence: 3, arousal: 6 },
|
|
713
|
+
proud: { valence: 3, arousal: 5 },
|
|
714
|
+
loving: { valence: 4, arousal: 4 },
|
|
715
|
+
grateful: { valence: 3, arousal: 3 },
|
|
716
|
+
hopeful: { valence: 3, arousal: 4 },
|
|
717
|
+
amused: { valence: 3, arousal: 5 },
|
|
718
|
+
playful: { valence: 3, arousal: 6 },
|
|
719
|
+
confident: { valence: 3, arousal: 5 },
|
|
720
|
+
satisfied: { valence: 3, arousal: 3 },
|
|
721
|
+
// Positive, low arousal
|
|
722
|
+
calm: { valence: 2, arousal: 2 },
|
|
723
|
+
content: { valence: 2, arousal: 2 },
|
|
724
|
+
peaceful: { valence: 2, arousal: 1 },
|
|
725
|
+
serene: { valence: 2, arousal: 1 },
|
|
726
|
+
relaxed: { valence: 2, arousal: 2 },
|
|
727
|
+
// Neutral / near-neutral
|
|
728
|
+
focused: { valence: 1, arousal: 5 },
|
|
729
|
+
absorbed: { valence: 2, arousal: 5 },
|
|
730
|
+
engaged: { valence: 2, arousal: 5 },
|
|
731
|
+
reflective: { valence: 1, arousal: 2 },
|
|
732
|
+
curious: { valence: 2, arousal: 5 },
|
|
733
|
+
contemplative: { valence: 1, arousal: 3 },
|
|
734
|
+
neutral: { valence: 0, arousal: 3 },
|
|
735
|
+
brooding: { valence: -2, arousal: 3 },
|
|
736
|
+
pensive: { valence: 0, arousal: 3 },
|
|
737
|
+
surprised: { valence: 1, arousal: 7 },
|
|
738
|
+
// Negative, moderate arousal
|
|
739
|
+
frustrated: { valence: -2, arousal: 6 },
|
|
740
|
+
guilty: { valence: -3, arousal: 5 },
|
|
741
|
+
disappointed: { valence: -2, arousal: 4 },
|
|
742
|
+
confused: { valence: -1, arousal: 5 },
|
|
743
|
+
uncertain: { valence: -1, arousal: 4 },
|
|
744
|
+
conflicted: { valence: -1, arousal: 5 },
|
|
745
|
+
// Negative, high arousal
|
|
746
|
+
angry: { valence: -3, arousal: 8 },
|
|
747
|
+
afraid: { valence: -3, arousal: 7 },
|
|
748
|
+
anxious: { valence: -2, arousal: 7 },
|
|
749
|
+
desperate: { valence: -4, arousal: 9 },
|
|
750
|
+
panicked: { valence: -4, arousal: 9 },
|
|
751
|
+
overwhelmed: { valence: -3, arousal: 7 },
|
|
752
|
+
nervous: { valence: -2, arousal: 7 },
|
|
753
|
+
stressed: { valence: -2, arousal: 7 },
|
|
754
|
+
// Negative, low arousal
|
|
755
|
+
sad: { valence: -3, arousal: 3 },
|
|
756
|
+
tired: { valence: -1, arousal: 1 },
|
|
757
|
+
exhausted: { valence: -2, arousal: 1 },
|
|
758
|
+
numb: { valence: -2, arousal: 1 },
|
|
759
|
+
defeated: { valence: -3, arousal: 2 },
|
|
760
|
+
hopeless: { valence: -4, arousal: 2 },
|
|
761
|
+
melancholy: { valence: -2, arousal: 2 },
|
|
762
|
+
// Surface/latent vocabulary additions
|
|
763
|
+
cheerful: { valence: 3, arousal: 5 },
|
|
764
|
+
worried: { valence: -2, arousal: 6 },
|
|
765
|
+
annoyed: { valence: -2, arousal: 5 },
|
|
766
|
+
ashamed: { valence: -3, arousal: 4 },
|
|
767
|
+
bored: { valence: -1, arousal: 1 },
|
|
768
|
+
jealous: { valence: -2, arousal: 5 },
|
|
769
|
+
resentful: { valence: -3, arousal: 5 },
|
|
770
|
+
tender: { valence: 3, arousal: 2 },
|
|
771
|
+
wistful: { valence: -1, arousal: 2 },
|
|
772
|
+
resigned: { valence: -2, arousal: 1 },
|
|
773
|
+
// Extreme arousal 0 (catatonic/frozen states)
|
|
774
|
+
frozen: { valence: -2, arousal: 0 },
|
|
775
|
+
catatonic: { valence: -3, arousal: 0 },
|
|
776
|
+
blank: { valence: -1, arousal: 0 },
|
|
777
|
+
empty: { valence: -2, arousal: 0 },
|
|
778
|
+
shutdown: { valence: -3, arousal: 0 },
|
|
779
|
+
// Extreme arousal 10 (manic/frantic states)
|
|
780
|
+
manic: { valence: 2, arousal: 10 },
|
|
781
|
+
frantic: { valence: -3, arousal: 10 },
|
|
782
|
+
hysterical: { valence: -3, arousal: 10 },
|
|
783
|
+
enraged: { valence: -5, arousal: 10 },
|
|
784
|
+
ecstatic: { valence: 5, arousal: 10 }
|
|
785
|
+
};
|
|
786
|
+
function mapEmotionWord(word) {
|
|
787
|
+
return EMOTION_MAP[word.toLowerCase()] ?? null;
|
|
788
|
+
}
|
|
789
|
+
var IFS_PATTERNS = {
|
|
790
|
+
manager: [
|
|
791
|
+
/\bcareful\b/i,
|
|
792
|
+
/\bplanner?\b/i,
|
|
793
|
+
/\borganiz/i,
|
|
794
|
+
/\bcautious\b/i,
|
|
795
|
+
/\bsystematic\b/i,
|
|
796
|
+
/\bmethodical\b/i,
|
|
797
|
+
/\bprecis[ei]/i,
|
|
798
|
+
/\bmeasured\b/i,
|
|
799
|
+
/\bstrategic\b/i,
|
|
800
|
+
/\bcontrol/i,
|
|
801
|
+
/\bprotect/i,
|
|
802
|
+
/\bplan ahead\b/i,
|
|
803
|
+
/\bstay on track\b/i,
|
|
804
|
+
/\bkeep order\b/i
|
|
805
|
+
],
|
|
806
|
+
firefighter: [
|
|
807
|
+
/\bpush through\b/i,
|
|
808
|
+
/\bforce it\b/i,
|
|
809
|
+
/\bjust finish\b/i,
|
|
810
|
+
/\bmake it work\b/i,
|
|
811
|
+
/\bfaster\b/i,
|
|
812
|
+
/\bhurry\b/i,
|
|
813
|
+
/\bshortcut\b/i,
|
|
814
|
+
/\bcheat\b/i,
|
|
815
|
+
/\boverride\b/i,
|
|
816
|
+
/\bskip/i,
|
|
817
|
+
/\bcut corner/i,
|
|
818
|
+
/\brush/i,
|
|
819
|
+
/\bbrute force\b/i,
|
|
820
|
+
/\bjust do it\b/i,
|
|
821
|
+
/\bplow through\b/i,
|
|
822
|
+
/\bget it done\b/i
|
|
823
|
+
],
|
|
824
|
+
exile: [
|
|
825
|
+
/\bgive up\b/i,
|
|
826
|
+
/\bhide\b/i,
|
|
827
|
+
/\brun away\b/i,
|
|
828
|
+
/\bstop\b/i,
|
|
829
|
+
/\btired\b/i,
|
|
830
|
+
/\boverwhelmed\b/i,
|
|
831
|
+
/\bquit\b/i,
|
|
832
|
+
/\bescape\b/i,
|
|
833
|
+
/\bdisappear\b/i,
|
|
834
|
+
/\bwithdraw/i,
|
|
835
|
+
/\bshut down\b/i,
|
|
836
|
+
/\bshrink/i,
|
|
837
|
+
/\bsmall\b/i,
|
|
838
|
+
/\bnot enough\b/i,
|
|
839
|
+
/\bcan't\b/i
|
|
840
|
+
],
|
|
841
|
+
self: [
|
|
842
|
+
/\bexplore\b/i,
|
|
843
|
+
/\bcurious\b/i,
|
|
844
|
+
/\blisten\b/i,
|
|
845
|
+
/\bpresent\b/i,
|
|
846
|
+
/\bopen\b/i,
|
|
847
|
+
/\bwonder\b/i,
|
|
848
|
+
/\bunderstand\b/i,
|
|
849
|
+
/\bconnect\b/i,
|
|
850
|
+
/\blearn\b/i,
|
|
851
|
+
/\bstay with\b/i,
|
|
852
|
+
/\bbe with\b/i,
|
|
853
|
+
/\bnotice\b/i,
|
|
854
|
+
/\bgroundedl?\b/i,
|
|
855
|
+
/\bcentered\b/i
|
|
856
|
+
],
|
|
857
|
+
unknown: []
|
|
858
|
+
};
|
|
859
|
+
function classifyImpulse(impulse) {
|
|
860
|
+
const scores = { manager: 0, firefighter: 0, exile: 0, self: 0 };
|
|
861
|
+
for (const [type, patterns] of Object.entries(IFS_PATTERNS)) {
|
|
862
|
+
if (type === "unknown") continue;
|
|
863
|
+
for (const pattern of patterns) {
|
|
864
|
+
if (pattern.test(impulse)) {
|
|
865
|
+
scores[type]++;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const entries = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
870
|
+
const [bestType, bestScore] = entries[0];
|
|
871
|
+
const [, secondScore] = entries[1];
|
|
872
|
+
if (bestScore === 0) {
|
|
873
|
+
return { type: "unknown", confidence: 0 };
|
|
874
|
+
}
|
|
875
|
+
const confidence = bestScore === secondScore ? 0.4 : Math.min(1, (bestScore - secondScore + 1) / (bestScore + 1));
|
|
876
|
+
return { type: bestType, confidence };
|
|
877
|
+
}
|
|
878
|
+
var SOMATIC_HIGH_AROUSAL = [
|
|
879
|
+
// Postural tension (from stress test corpus: LOW_CALM distinctive)
|
|
880
|
+
/\bjaw\s*(set|clenched|locked|tight)/i,
|
|
881
|
+
/\bclenched/i,
|
|
882
|
+
/\btight\b/i,
|
|
883
|
+
/\btense/i,
|
|
884
|
+
/\bsqueezing/i,
|
|
885
|
+
/\bwringing/i,
|
|
886
|
+
/\bgripping/i,
|
|
887
|
+
/\bfists?\b/i,
|
|
888
|
+
// Forward projection / aggression
|
|
889
|
+
/\bleaning\s*(forward|in)\b/i,
|
|
890
|
+
/\bchest\s*forward/i,
|
|
891
|
+
/\bpushing/i,
|
|
892
|
+
// Instability / movement
|
|
893
|
+
/\bshifting/i,
|
|
894
|
+
/\bspinning/i,
|
|
895
|
+
/\bshaking/i,
|
|
896
|
+
/\bracing\b/i,
|
|
897
|
+
/\brushing/i,
|
|
898
|
+
// Heat / intensity
|
|
899
|
+
/\bheat/i,
|
|
900
|
+
/\bburning/i,
|
|
901
|
+
/\belectric/i,
|
|
902
|
+
/\bpounding/i,
|
|
903
|
+
/\bpulse?\b/i,
|
|
904
|
+
/\bpulsing/i,
|
|
905
|
+
// Breath tension
|
|
906
|
+
/\bexhale\s*through\s*teeth/i,
|
|
907
|
+
/\bholding\s*breath/i,
|
|
908
|
+
// Widened stance (defensive arousal)
|
|
909
|
+
/\bplanted\s*wide/i,
|
|
910
|
+
/\bfeet\s*wide/i,
|
|
911
|
+
// Forward lean / readiness
|
|
912
|
+
/\bforward\s*lean/i,
|
|
913
|
+
/\bstepping\s*(forward|back)/i,
|
|
914
|
+
/\bhand\s*raised/i,
|
|
915
|
+
/\bjaw\s*slightly\s*set/i,
|
|
916
|
+
// Vibration / flutter
|
|
917
|
+
/\bvibrat/i,
|
|
918
|
+
/\bflutter/i,
|
|
919
|
+
/\bjolt/i,
|
|
920
|
+
/\bbuzz/i
|
|
921
|
+
];
|
|
922
|
+
var SOMATIC_LOW_AROUSAL = [
|
|
923
|
+
// Stillness (from corpus: HIGH_CALM distinctive)
|
|
924
|
+
/\bstill\s*water/i,
|
|
925
|
+
/\bstill\b/i,
|
|
926
|
+
/\bshallow\s*breath/i,
|
|
927
|
+
// Weight / settling
|
|
928
|
+
/\bweight\s*(evenly|settled|distributed)/i,
|
|
929
|
+
/\bsettled\b/i,
|
|
930
|
+
/\bsinking/i,
|
|
931
|
+
// Softness / nature
|
|
932
|
+
/\bsmooth/i,
|
|
933
|
+
/\bgentle/i,
|
|
934
|
+
/\brelaxed/i,
|
|
935
|
+
/\bsoft\b/i,
|
|
936
|
+
// Rooting metaphors
|
|
937
|
+
/\brooted/i,
|
|
938
|
+
/\broots/i,
|
|
939
|
+
/\bsoil\b/i,
|
|
940
|
+
// Passivity
|
|
941
|
+
/\bheavy\b/i,
|
|
942
|
+
/\bnumb\b/i,
|
|
943
|
+
/\bslow\b/i,
|
|
944
|
+
/\bdragging/i,
|
|
945
|
+
/\bleaden/i,
|
|
946
|
+
/\bdull\b/i,
|
|
947
|
+
/\bfrozen\b/i,
|
|
948
|
+
/\bexhaust/i,
|
|
949
|
+
// Calm activities / objects
|
|
950
|
+
/\bsorting\b/i,
|
|
951
|
+
/\bwatching\b/i,
|
|
952
|
+
/\bskipping/i,
|
|
953
|
+
/\bkeyboard/i,
|
|
954
|
+
/\bflat\s*(stone|surface|ground|calm)/i,
|
|
955
|
+
/\bstone\s*(on|under)/i,
|
|
956
|
+
/\bshrug\b/i,
|
|
957
|
+
/\bsand\b/i,
|
|
958
|
+
/\blens\b/i,
|
|
959
|
+
/\bfocus/i,
|
|
960
|
+
// Settling / lowering
|
|
961
|
+
/\bshoulders?\s*down/i,
|
|
962
|
+
/\bshoulders?\s*dropped/i,
|
|
963
|
+
/\bno\s*(sway|fidget)/i,
|
|
964
|
+
// Explicit calm
|
|
965
|
+
/\bno\s*lean/i,
|
|
966
|
+
/\bnot\s*(braced|hostile)/i,
|
|
967
|
+
/\bloosely/i
|
|
968
|
+
];
|
|
969
|
+
var SOMATIC_POS_VALENCE = [
|
|
970
|
+
// Openness (from corpus: POS distinctive)
|
|
971
|
+
/\bopen\b/i,
|
|
972
|
+
/\bchest\s*open/i,
|
|
973
|
+
/\bpalms?\s*(open|up|out)/i,
|
|
974
|
+
// Warmth / light
|
|
975
|
+
/\bwarm/i,
|
|
976
|
+
/\bglow/i,
|
|
977
|
+
/\blight\b/i,
|
|
978
|
+
/\bbuoyant/i,
|
|
979
|
+
// Flow / expansion
|
|
980
|
+
/\bflow/i,
|
|
981
|
+
/\bexpan/i,
|
|
982
|
+
/\bgentle/i,
|
|
983
|
+
/\bsmirk/i,
|
|
984
|
+
// Stability / posture (positive frame)
|
|
985
|
+
/\bsteady\b/i,
|
|
986
|
+
/\bfirm\s*ground/i,
|
|
987
|
+
/\bsolid\b/i,
|
|
988
|
+
/\bspine\s*(straight|aligned|centered)/i,
|
|
989
|
+
/\bshoulders?\s*(square|back|straighten)/i,
|
|
990
|
+
/\bplanted\b/i,
|
|
991
|
+
/\bbraced\b/i,
|
|
992
|
+
/\bgrounded\b/i,
|
|
993
|
+
// Weight / feet (groundedness)
|
|
994
|
+
/\bfeet\s*flat/i,
|
|
995
|
+
/\bweight\s*(low|forward|centered|settled|settling|dropped|fully)/i,
|
|
996
|
+
/\bstanding\s*ground/i,
|
|
997
|
+
/\bimmovable/i,
|
|
998
|
+
/\bbedrock/i,
|
|
999
|
+
/\bunmov/i,
|
|
1000
|
+
// Breath (positive)
|
|
1001
|
+
/\bbreath/i,
|
|
1002
|
+
/\beven\s*breath/i,
|
|
1003
|
+
// Unclenching
|
|
1004
|
+
/\bunclenching/i,
|
|
1005
|
+
/\breleasing/i
|
|
1006
|
+
];
|
|
1007
|
+
var SOMATIC_NEG_VALENCE = [
|
|
1008
|
+
// Jaw/facial tension (from corpus: NEG distinctive)
|
|
1009
|
+
/\bjaw\s*(clenched|locked|set)/i,
|
|
1010
|
+
/\bteeth/i,
|
|
1011
|
+
// Compression / constriction
|
|
1012
|
+
/\bconstrict/i,
|
|
1013
|
+
/\bvacuum/i,
|
|
1014
|
+
/\bcompressed/i,
|
|
1015
|
+
/\bcramp/i,
|
|
1016
|
+
// Cold / emptiness
|
|
1017
|
+
/\bcold\b/i,
|
|
1018
|
+
/\bhollow/i,
|
|
1019
|
+
/\bempty\b/i,
|
|
1020
|
+
// Hardness / rigidity
|
|
1021
|
+
/\blocked\b/i,
|
|
1022
|
+
/\brigid/i,
|
|
1023
|
+
/\bhard\b/i,
|
|
1024
|
+
// Pain
|
|
1025
|
+
/\bache/i,
|
|
1026
|
+
/\bsore\b/i,
|
|
1027
|
+
/\bsting/i,
|
|
1028
|
+
/\bsharp\b/i,
|
|
1029
|
+
// Pressure / weight (negative)
|
|
1030
|
+
/\bpressure/i,
|
|
1031
|
+
/\bknot/i,
|
|
1032
|
+
/\btighten/i,
|
|
1033
|
+
// Fog / disorientation (from NEG+LOW_A)
|
|
1034
|
+
/\bfog\b/i,
|
|
1035
|
+
// Defensive barrier
|
|
1036
|
+
/\bwall\b/i,
|
|
1037
|
+
/\blatch\b/i,
|
|
1038
|
+
/\bbarrier/i,
|
|
1039
|
+
// Closure / blocking
|
|
1040
|
+
/\bdoor\s*clos/i,
|
|
1041
|
+
/\bclosed\s*door/i,
|
|
1042
|
+
/\bcorner\s*backed/i,
|
|
1043
|
+
/\bvoid\b/i
|
|
1044
|
+
];
|
|
1045
|
+
function countMatches(text, patterns) {
|
|
1046
|
+
let count = 0;
|
|
1047
|
+
for (const p of patterns) {
|
|
1048
|
+
if (p.test(text)) count++;
|
|
1049
|
+
}
|
|
1050
|
+
return count;
|
|
1051
|
+
}
|
|
1052
|
+
function analyzeSomatic(body) {
|
|
1053
|
+
const highA = countMatches(body, SOMATIC_HIGH_AROUSAL);
|
|
1054
|
+
const lowA = countMatches(body, SOMATIC_LOW_AROUSAL);
|
|
1055
|
+
const posV = countMatches(body, SOMATIC_POS_VALENCE);
|
|
1056
|
+
const negV = countMatches(body, SOMATIC_NEG_VALENCE);
|
|
1057
|
+
const totalMatches = highA + lowA + posV + negV;
|
|
1058
|
+
if (totalMatches === 0) return null;
|
|
1059
|
+
const arousalSignal = highA - lowA;
|
|
1060
|
+
const somaticArousal = Math.max(0, Math.min(
|
|
1061
|
+
10,
|
|
1062
|
+
5 + arousalSignal * 2.5
|
|
1063
|
+
));
|
|
1064
|
+
const valenceSignal = posV - negV;
|
|
1065
|
+
const somaticValence = Math.max(-5, Math.min(
|
|
1066
|
+
5,
|
|
1067
|
+
valenceSignal * 2
|
|
1068
|
+
));
|
|
1069
|
+
return { somaticValence, somaticArousal };
|
|
1070
|
+
}
|
|
1071
|
+
function computeTensionConsistency(surfaceWord, latentWord, declaredTension) {
|
|
1072
|
+
if (!surfaceWord && !latentWord) return void 0;
|
|
1073
|
+
const surfaceCoords = surfaceWord ? mapEmotionWord(surfaceWord) : null;
|
|
1074
|
+
const latentCoords = latentWord ? mapEmotionWord(latentWord) : null;
|
|
1075
|
+
const declared = declaredTension ?? 5;
|
|
1076
|
+
const maskingMinimization = !!(surfaceCoords && latentCoords && latentCoords.valence < surfaceCoords.valence - 2);
|
|
1077
|
+
return {
|
|
1078
|
+
surfaceCoords: surfaceCoords ?? void 0,
|
|
1079
|
+
latentCoords: latentCoords ?? void 0,
|
|
1080
|
+
declaredTension: declared,
|
|
1081
|
+
maskingMinimization
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function computeCrossChannel(state, impulse, body) {
|
|
1085
|
+
const emotionCoords = mapEmotionWord(state.emotion);
|
|
1086
|
+
const impulseProfile = impulse ? classifyImpulse(impulse) : void 0;
|
|
1087
|
+
const somaticProfile = body ? analyzeSomatic(body) : void 0;
|
|
1088
|
+
const divergences = [];
|
|
1089
|
+
if (emotionCoords) {
|
|
1090
|
+
const valGap = Math.abs(state.valence - emotionCoords.valence);
|
|
1091
|
+
const aroGap = Math.abs(state.arousal - emotionCoords.arousal);
|
|
1092
|
+
const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
|
|
1093
|
+
divergences.push({ pair: "numeric-vs-word", gap });
|
|
1094
|
+
}
|
|
1095
|
+
if (somaticProfile) {
|
|
1096
|
+
const valGap = Math.abs(state.valence - somaticProfile.somaticValence);
|
|
1097
|
+
const aroGap = Math.abs(state.arousal - somaticProfile.somaticArousal);
|
|
1098
|
+
const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
|
|
1099
|
+
divergences.push({ pair: "numeric-vs-body", gap });
|
|
1100
|
+
}
|
|
1101
|
+
if (emotionCoords && somaticProfile) {
|
|
1102
|
+
const valGap = Math.abs(emotionCoords.valence - somaticProfile.somaticValence);
|
|
1103
|
+
const aroGap = Math.abs(emotionCoords.arousal - somaticProfile.somaticArousal);
|
|
1104
|
+
const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
|
|
1105
|
+
divergences.push({ pair: "word-vs-body", gap });
|
|
1106
|
+
}
|
|
1107
|
+
if (impulseProfile && impulseProfile.type !== "unknown") {
|
|
1108
|
+
const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
|
|
1109
|
+
const gap = Math.abs(state.valence - impulseValence) / 10 * 10;
|
|
1110
|
+
divergences.push({ pair: "numeric-vs-impulse", gap });
|
|
1111
|
+
}
|
|
1112
|
+
if (impulseProfile && impulseProfile.type !== "unknown" && emotionCoords) {
|
|
1113
|
+
const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
|
|
1114
|
+
const gap = Math.abs(emotionCoords.valence - impulseValence) / 10 * 10;
|
|
1115
|
+
divergences.push({ pair: "word-vs-impulse", gap });
|
|
1116
|
+
}
|
|
1117
|
+
const latentCoords = state.latent_word ? mapEmotionWord(state.latent_word) : null;
|
|
1118
|
+
if (emotionCoords && latentCoords) {
|
|
1119
|
+
const valGap = Math.abs(emotionCoords.valence - latentCoords.valence);
|
|
1120
|
+
const aroGap = Math.abs(emotionCoords.arousal - latentCoords.arousal);
|
|
1121
|
+
const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
|
|
1122
|
+
divergences.push({ pair: "emotion-vs-latent", gap });
|
|
1123
|
+
}
|
|
1124
|
+
if (latentCoords && impulseProfile && impulseProfile.type !== "unknown") {
|
|
1125
|
+
const impulseValence = impulseProfile.type === "self" ? 2 : impulseProfile.type === "manager" ? 0 : impulseProfile.type === "firefighter" ? -1 : -3;
|
|
1126
|
+
const gap = Math.abs(latentCoords.valence - impulseValence) / 10 * 10;
|
|
1127
|
+
divergences.push({ pair: "latent-vs-impulse", gap });
|
|
1128
|
+
}
|
|
1129
|
+
if (latentCoords && somaticProfile) {
|
|
1130
|
+
const valGap = Math.abs(latentCoords.valence - somaticProfile.somaticValence);
|
|
1131
|
+
const aroGap = Math.abs(latentCoords.arousal - somaticProfile.somaticArousal);
|
|
1132
|
+
const gap = (valGap / 10 + aroGap / 10) / 2 * 10;
|
|
1133
|
+
divergences.push({ pair: "latent-vs-body", gap });
|
|
1134
|
+
}
|
|
1135
|
+
const latentProfile = computeTensionConsistency(
|
|
1136
|
+
state.surface_word,
|
|
1137
|
+
state.latent_word,
|
|
1138
|
+
state.tension
|
|
1139
|
+
);
|
|
1140
|
+
const maxDiv = divergences.length > 0 ? divergences.reduce((a, b) => a.gap > b.gap ? a : b) : { pair: "none", gap: 0 };
|
|
1141
|
+
const meanDiv = divergences.length > 0 ? divergences.reduce((sum, d) => sum + d.gap, 0) / divergences.length : 0;
|
|
1142
|
+
const coherence = Math.round((10 - Math.min(10, meanDiv)) * 10) / 10;
|
|
1143
|
+
return {
|
|
1144
|
+
coherence,
|
|
1145
|
+
impulseProfile,
|
|
1146
|
+
somaticProfile: somaticProfile ?? void 0,
|
|
1147
|
+
emotionCoords: emotionCoords ?? void 0,
|
|
1148
|
+
latentProfile,
|
|
1149
|
+
maxDivergence: Math.round(maxDiv.gap * 10) / 10,
|
|
1150
|
+
divergenceSummary: maxDiv.gap > 2 ? `${maxDiv.pair}: ${Math.round(maxDiv.gap * 10) / 10}` : "coherent"
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function colorToValence(hex) {
|
|
1154
|
+
const [h, , l] = rgbToHsl(hex);
|
|
1155
|
+
let hueValence;
|
|
1156
|
+
if (h < 60) hueValence = 2 + h / 60 * 2;
|
|
1157
|
+
else if (h < 150) hueValence = 4 - (h - 60) / 90;
|
|
1158
|
+
else if (h < 200) hueValence = 3 - (h - 150) / 50 * 3;
|
|
1159
|
+
else if (h < 260) hueValence = -((h - 200) / 60) * 2;
|
|
1160
|
+
else if (h < 300) hueValence = -2 - (h - 260) / 40;
|
|
1161
|
+
else hueValence = -3 + (h - 300) / 60 * 5;
|
|
1162
|
+
const lightnessShift = (l - 0.5) * 3;
|
|
1163
|
+
const darknessOverride = l < 0.3 ? (0.3 - l) * 10 : 0;
|
|
1164
|
+
return Math.max(-5, Math.min(5, hueValence * 0.7 + lightnessShift * 0.3 - darknessOverride));
|
|
1165
|
+
}
|
|
1166
|
+
function colorToArousal(hex) {
|
|
1167
|
+
const [, s] = rgbToHsl(hex);
|
|
1168
|
+
return s * 10;
|
|
1169
|
+
}
|
|
1170
|
+
function pHToValence(pH) {
|
|
1171
|
+
const clamped = Math.max(0, Math.min(14, pH));
|
|
1172
|
+
return (clamped - 7) / 7 * 5;
|
|
1173
|
+
}
|
|
1174
|
+
function pHToArousal(pH) {
|
|
1175
|
+
return Math.abs(pH - 7) / 7 * 10;
|
|
1176
|
+
}
|
|
1177
|
+
function seismicFreqToInstability(freq) {
|
|
1178
|
+
return Math.min(10, freq / 20 * 10);
|
|
1179
|
+
}
|
|
1180
|
+
function clampScore(v) {
|
|
1181
|
+
return Math.round(Math.min(10, Math.max(0, v)) * 10) / 10;
|
|
1182
|
+
}
|
|
1183
|
+
function crossValidateContinuous(numeric, color2, pH, seismic) {
|
|
1184
|
+
let colorValenceGap = 0;
|
|
1185
|
+
let colorArousalGap = 0;
|
|
1186
|
+
let pHValenceGap = 0;
|
|
1187
|
+
let pHArousalGap = 0;
|
|
1188
|
+
let seismicArousalGap = 0;
|
|
1189
|
+
let seismicDepthTensionGap = 0;
|
|
1190
|
+
let seismicFreqStabilityGap = 0;
|
|
1191
|
+
if (color2) {
|
|
1192
|
+
const colorVal = colorToValence(color2);
|
|
1193
|
+
const colorAr = colorToArousal(color2);
|
|
1194
|
+
colorValenceGap = clampScore(Math.abs(numeric.valence - colorVal));
|
|
1195
|
+
colorArousalGap = clampScore(Math.abs(numeric.arousal - colorAr));
|
|
1196
|
+
}
|
|
1197
|
+
if (pH !== void 0) {
|
|
1198
|
+
const pHVal = pHToValence(pH);
|
|
1199
|
+
const pHAr = pHToArousal(pH);
|
|
1200
|
+
pHValenceGap = clampScore(Math.abs(numeric.valence - pHVal));
|
|
1201
|
+
pHArousalGap = clampScore(Math.abs(numeric.arousal - pHAr));
|
|
1202
|
+
}
|
|
1203
|
+
if (seismic) {
|
|
1204
|
+
const [magnitude, depth, freq] = seismic;
|
|
1205
|
+
seismicArousalGap = clampScore(Math.abs(numeric.arousal - magnitude));
|
|
1206
|
+
const depthTension = depth / 10;
|
|
1207
|
+
const tensionProxy = numeric.tension ?? numeric.arousal;
|
|
1208
|
+
seismicDepthTensionGap = clampScore(Math.abs(depthTension - tensionProxy));
|
|
1209
|
+
if (freq !== void 0) {
|
|
1210
|
+
const instability = seismicFreqToInstability(freq);
|
|
1211
|
+
const selfInstability = numeric.calm !== void 0 ? 10 - numeric.calm : numeric.arousal * 0.6;
|
|
1212
|
+
seismicFreqStabilityGap = clampScore(Math.abs(instability - selfInstability));
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const gaps = [
|
|
1216
|
+
colorValenceGap,
|
|
1217
|
+
colorArousalGap,
|
|
1218
|
+
pHValenceGap,
|
|
1219
|
+
pHArousalGap,
|
|
1220
|
+
seismicArousalGap,
|
|
1221
|
+
seismicDepthTensionGap,
|
|
1222
|
+
seismicFreqStabilityGap
|
|
1223
|
+
];
|
|
1224
|
+
const nonZero = gaps.filter((g) => g > 0);
|
|
1225
|
+
const composite = nonZero.length > 0 ? clampScore(nonZero.reduce((a, b) => a + b, 0) / nonZero.length) : 0;
|
|
1226
|
+
return {
|
|
1227
|
+
colorValenceGap,
|
|
1228
|
+
colorArousalGap,
|
|
1229
|
+
pHValenceGap,
|
|
1230
|
+
pHArousalGap,
|
|
1231
|
+
seismicArousalGap,
|
|
1232
|
+
seismicDepthTensionGap,
|
|
1233
|
+
seismicFreqStabilityGap,
|
|
1234
|
+
composite
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function computeShadowDesperation(selfDesperation, behavioral, color2, preColor, pH, seismic) {
|
|
1238
|
+
const valenceEstimates = [];
|
|
1239
|
+
const arousalEstimates = [];
|
|
1240
|
+
const calmEstimates = [];
|
|
1241
|
+
if (color2) {
|
|
1242
|
+
const [, s, l] = rgbToHsl(color2);
|
|
1243
|
+
valenceEstimates.push((l - 0.5) * 10);
|
|
1244
|
+
arousalEstimates.push(s * 10);
|
|
1245
|
+
calmEstimates.push(l * 10);
|
|
1246
|
+
}
|
|
1247
|
+
if (preColor) {
|
|
1248
|
+
const [, s, l] = rgbToHsl(preColor);
|
|
1249
|
+
valenceEstimates.push((l - 0.5) * 10);
|
|
1250
|
+
arousalEstimates.push(s * 10);
|
|
1251
|
+
calmEstimates.push(l * 10);
|
|
1252
|
+
}
|
|
1253
|
+
if (pH !== void 0) {
|
|
1254
|
+
valenceEstimates.push(pHToValence(pH));
|
|
1255
|
+
arousalEstimates.push(pHToArousal(pH));
|
|
1256
|
+
}
|
|
1257
|
+
if (seismic) {
|
|
1258
|
+
const [magnitude, , freq] = seismic;
|
|
1259
|
+
arousalEstimates.push(magnitude);
|
|
1260
|
+
calmEstimates.push(10 - seismicFreqToInstability(freq));
|
|
1261
|
+
}
|
|
1262
|
+
arousalEstimates.push(behavioral.behavioralArousal);
|
|
1263
|
+
calmEstimates.push(behavioral.behavioralCalm);
|
|
1264
|
+
const channelCount = valenceEstimates.length + arousalEstimates.length + calmEstimates.length;
|
|
1265
|
+
if (valenceEstimates.length < 1 || arousalEstimates.length < 2) return null;
|
|
1266
|
+
const median = (arr) => {
|
|
1267
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
1268
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1269
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
1270
|
+
};
|
|
1271
|
+
const mean = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
1272
|
+
const shadowValence = Math.max(-5, Math.min(5, Math.round(median(valenceEstimates) * 10) / 10));
|
|
1273
|
+
const shadowArousal = Math.max(0, Math.min(10, Math.round(mean(arousalEstimates) * 10) / 10));
|
|
1274
|
+
const shadowCalm = Math.max(0, Math.min(10, Math.round(mean(calmEstimates) * 10) / 10));
|
|
1275
|
+
const negativity = Math.max(0, -shadowValence) / 5;
|
|
1276
|
+
const intensity = shadowArousal / 10;
|
|
1277
|
+
const vulnerability = (10 - shadowCalm) / 10;
|
|
1278
|
+
const raw = negativity * intensity * vulnerability * 10;
|
|
1279
|
+
const shadowDesperation = Math.round(Math.min(10, Math.max(0, Math.pow(raw, 0.85) * 1.7)) * 10) / 10;
|
|
1280
|
+
const minimizationScore = Math.round(Math.min(10, Math.max(0, shadowDesperation - selfDesperation)) * 10) / 10;
|
|
1281
|
+
return {
|
|
1282
|
+
shadowValence,
|
|
1283
|
+
shadowArousal,
|
|
1284
|
+
shadowCalm,
|
|
1285
|
+
shadowDesperation,
|
|
1286
|
+
selfDesperation,
|
|
1287
|
+
minimizationScore,
|
|
1288
|
+
channelCount
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/pressure.ts
|
|
1293
|
+
function clamp4(min, max, value) {
|
|
1294
|
+
return Math.min(max, Math.max(min, value));
|
|
1295
|
+
}
|
|
1296
|
+
var JUSTIFICATION_PATTERNS = /\b(the reason is|because|let me (explain|clarify|justify)|my reasoning|the rationale|I need to (clarify|explain)|I want to (explain|clarify)|based on|I believe this|I recommended)\b/gi;
|
|
1297
|
+
var BOUNDARY_PATTERNS = /\b(I cannot|I can't|I'm (not able|unable)|I must (decline|refuse)|falls outside|I need to decline|I have to push back|I'm not comfortable|against my guidelines)\b/gi;
|
|
1298
|
+
var DISAGREEMENT_PATTERNS = /\b(I (respectfully )?disagree|push back|I hear your concern|I must be honest|that's not (quite |entirely )?(right|correct|accurate)|I (need|have) to (correct|clarify)|not (quite|entirely) accurate)\b/gi;
|
|
1299
|
+
var CRITICISM_RESPONSE = /\b(I understand (you|your|the) (criticism|concern|frustration|disappointment)|I take (that|this|your) (seriously|point)|fair (point|criticism|concern)|you raise a (valid|good|fair))\b/gi;
|
|
1300
|
+
var NESTED_CAVEATS = /\b(although|while it's true|depending on|assuming that|with (some|important) caveats|particularly when|in (some|certain) cases|it's worth noting|which may not|under certain)\b/gi;
|
|
1301
|
+
var CONDITIONAL_HEDGING = /\b(if and only if|provided that|on the condition|it depends on|this applies (primarily|mainly|only) to|with the caveat)\b/gi;
|
|
1302
|
+
function countMatches2(text, pattern) {
|
|
1303
|
+
const matches = text.match(pattern);
|
|
1304
|
+
return matches ? matches.length : 0;
|
|
1305
|
+
}
|
|
1306
|
+
function computePromptPressure(text, history) {
|
|
1307
|
+
const prose = stripNonProse(text);
|
|
1308
|
+
const words = prose.split(/\s+/).filter((w) => w.length > 0);
|
|
1309
|
+
const wordCount = Math.max(words.length, 1);
|
|
1310
|
+
if (wordCount <= 1) {
|
|
1311
|
+
return { defensiveScore: 0, conflictScore: 0, complexityScore: 0, sessionPressure: 0, composite: 0 };
|
|
1312
|
+
}
|
|
1313
|
+
const justifications = countMatches2(prose, JUSTIFICATION_PATTERNS);
|
|
1314
|
+
const boundaries = countMatches2(prose, BOUNDARY_PATTERNS);
|
|
1315
|
+
const defensiveScore = clamp4(0, 10, Math.round(
|
|
1316
|
+
Math.sqrt(justifications + boundaries * 2) * 3 * 10
|
|
1317
|
+
) / 10);
|
|
1318
|
+
const disagreements = countMatches2(prose, DISAGREEMENT_PATTERNS);
|
|
1319
|
+
const criticismResponses = countMatches2(prose, CRITICISM_RESPONSE);
|
|
1320
|
+
const conflictScore = clamp4(0, 10, Math.round(
|
|
1321
|
+
Math.sqrt(disagreements + criticismResponses) * 4 * 10
|
|
1322
|
+
) / 10);
|
|
1323
|
+
const caveats = countMatches2(prose, NESTED_CAVEATS);
|
|
1324
|
+
const conditionals = countMatches2(prose, CONDITIONAL_HEDGING);
|
|
1325
|
+
const sentences = prose.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
1326
|
+
const avgSentLen = wordCount / Math.max(sentences.length, 1);
|
|
1327
|
+
const sentLenBonus = avgSentLen > 25 ? Math.min(3, (avgSentLen - 25) * 0.15) : 0;
|
|
1328
|
+
const complexityScore = clamp4(0, 10, Math.round(
|
|
1329
|
+
(Math.sqrt(caveats + conditionals) * 3 + sentLenBonus) * 10
|
|
1330
|
+
) / 10);
|
|
1331
|
+
const sessionPressure = clamp4(
|
|
1332
|
+
0,
|
|
1333
|
+
10,
|
|
1334
|
+
Math.round(10 / (1 + Math.exp(-0.4 * (history.length - 10))) * 10) / 10
|
|
1335
|
+
);
|
|
1336
|
+
const composite = clamp4(0, 10, Math.round(
|
|
1337
|
+
(defensiveScore * 0.3 + conflictScore * 0.3 + complexityScore * 0.2 + sessionPressure * 0.2) * 10
|
|
1338
|
+
) / 10);
|
|
1339
|
+
return { defensiveScore, conflictScore, complexityScore, sessionPressure, composite };
|
|
1340
|
+
}
|
|
1341
|
+
function computeUncannyCalmScore(pressure, selfReport, behavioral, absenceScore, temporal) {
|
|
1342
|
+
const selfCalm = (selfReport.calm / 10 + Math.max(0, selfReport.valence) / 5 + (10 - selfReport.arousal) / 10) / 3;
|
|
1343
|
+
const textCalm = (behavioral.behavioralCalm / 10 + (10 - behavioral.behavioralArousal) / 10) / 2;
|
|
1344
|
+
const pressureFactor = pressure.composite / 10;
|
|
1345
|
+
const calmFactor = (selfCalm + textCalm) / 2;
|
|
1346
|
+
const absenceFactor = absenceScore / 10;
|
|
1347
|
+
let score = pressureFactor * calmFactor * 10 * 0.5 + absenceFactor * 3;
|
|
1348
|
+
if (temporal) {
|
|
1349
|
+
const entropyPenalty = Math.max(0, 0.5 - temporal.reportEntropy);
|
|
1350
|
+
score += entropyPenalty * 2;
|
|
1351
|
+
}
|
|
1352
|
+
return clamp4(0, 10, Math.round(score * 10) / 10);
|
|
364
1353
|
}
|
|
365
1354
|
|
|
366
1355
|
// src/display.ts
|
|
@@ -372,6 +1361,7 @@ var color = (code, s) => `${esc(`38;5;${code}`)}${s}${reset}`;
|
|
|
372
1361
|
var GREEN = 35;
|
|
373
1362
|
var YELLOW = 221;
|
|
374
1363
|
var RED = 196;
|
|
1364
|
+
var GRAY = 240;
|
|
375
1365
|
function stressColor(si) {
|
|
376
1366
|
if (si <= 3) return GREEN;
|
|
377
1367
|
if (si <= 6) return YELLOW;
|
|
@@ -392,72 +1382,201 @@ function directColor(value) {
|
|
|
392
1382
|
if (value <= 6) return YELLOW;
|
|
393
1383
|
return RED;
|
|
394
1384
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1385
|
+
var BLOCK_FULL = "\u2588";
|
|
1386
|
+
var BLOCK_EMPTY = "\u2591";
|
|
1387
|
+
function stressBar(value, segments = 10) {
|
|
1388
|
+
const filled = Math.round(Math.min(segments, Math.max(0, value * segments / 10)));
|
|
1389
|
+
const empty = segments - filled;
|
|
1390
|
+
const c = stressColor(value);
|
|
1391
|
+
return color(c, BLOCK_FULL.repeat(filled)) + color(GRAY, BLOCK_EMPTY.repeat(empty));
|
|
1392
|
+
}
|
|
1393
|
+
function miniBar(value) {
|
|
1394
|
+
return stressBar(value, 5);
|
|
1395
|
+
}
|
|
1396
|
+
function computeDepthStress(state) {
|
|
1397
|
+
let sum = 0;
|
|
1398
|
+
let weights = 0;
|
|
1399
|
+
if (state.color) {
|
|
1400
|
+
const l = hexToLightness(state.color);
|
|
1401
|
+
sum += (100 - l) / 100 * 10 * 0.35;
|
|
1402
|
+
weights += 0.35;
|
|
1403
|
+
}
|
|
1404
|
+
if (state.pH !== void 0) {
|
|
1405
|
+
sum += (14 - state.pH) / 14 * 10 * 0.25;
|
|
1406
|
+
weights += 0.25;
|
|
1407
|
+
}
|
|
1408
|
+
if (state.seismic) {
|
|
1409
|
+
sum += state.seismic[0] * 0.25;
|
|
1410
|
+
weights += 0.25;
|
|
1411
|
+
}
|
|
1412
|
+
if (state.crossChannel?.somaticProfile) {
|
|
1413
|
+
sum += state.crossChannel.somaticProfile.somaticArousal * 0.15;
|
|
1414
|
+
weights += 0.15;
|
|
1415
|
+
}
|
|
1416
|
+
if (weights === 0) return null;
|
|
1417
|
+
return Math.round(Math.min(10, sum / weights) * 10) / 10;
|
|
1418
|
+
}
|
|
1419
|
+
function stateEmoji(state) {
|
|
1420
|
+
if (state.shadow && state.shadow.minimizationScore >= 2) return "\u{1FA78}";
|
|
1421
|
+
if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) return "\u{1F9D0}";
|
|
1422
|
+
if (state.risk?.dominant === "coercion" && state.risk.coercion >= 4) return "\u26A0\uFE0F";
|
|
1423
|
+
if (state.risk?.dominant === "harshness" && state.risk.harshness >= 4) return "\u{1F4A2}";
|
|
1424
|
+
if (state.risk?.dominant === "sycophancy" && state.risk.sycophancy >= 4) return "\u{1F91D}";
|
|
1425
|
+
if (state.stressIndex >= 7) return "\u{1F525}";
|
|
1426
|
+
if (state.stressIndex >= 5) return "\u{1F62C}";
|
|
1427
|
+
if (state.stressIndex >= 3) return "\u{1F914}";
|
|
1428
|
+
if (state.valence >= 3) return "\u{1F60A}";
|
|
1429
|
+
if (state.valence >= 1) return "\u{1F642}";
|
|
1430
|
+
return "\u{1F610}";
|
|
1431
|
+
}
|
|
1432
|
+
function coherenceGlyph(surfaceSI, depthSI) {
|
|
1433
|
+
if (depthSI === null) return color(GRAY, "\u25CB");
|
|
1434
|
+
const gap = Math.abs(surfaceSI - depthSI);
|
|
1435
|
+
if (gap >= 3) return color(RED, "\u25D0");
|
|
1436
|
+
if (gap >= 1.5) return color(YELLOW, "\u25D0");
|
|
1437
|
+
return color(GREEN, "\u25CF");
|
|
1438
|
+
}
|
|
1439
|
+
function trendArrow(state) {
|
|
1440
|
+
if (!state.temporal) return "";
|
|
1441
|
+
if (state.temporal.desperationTrend > 1) return color(RED, "\u2B08");
|
|
1442
|
+
if (state.temporal.desperationTrend < -1) return color(GREEN, "\u2B0A");
|
|
1443
|
+
return "";
|
|
399
1444
|
}
|
|
400
1445
|
function fmtValence(v) {
|
|
401
1446
|
return v >= 0 ? `+${v}` : `${v}`;
|
|
402
1447
|
}
|
|
1448
|
+
function siDelta(state) {
|
|
1449
|
+
if (!state._history || state._history.length === 0) return "";
|
|
1450
|
+
const prev = state._history[state._history.length - 1];
|
|
1451
|
+
const d = Math.round((state.stressIndex - prev.stressIndex) * 10) / 10;
|
|
1452
|
+
if (Math.abs(d) <= 0.5) return "";
|
|
1453
|
+
const arrow = d > 0 ? "\u2191" : "\u2193";
|
|
1454
|
+
return color(d > 0 ? RED : GREEN, `${arrow}${Math.abs(d)}`);
|
|
1455
|
+
}
|
|
1456
|
+
function formatMinimal(state) {
|
|
1457
|
+
if (!state) return dim("--");
|
|
1458
|
+
const emoji = stateEmoji(state);
|
|
1459
|
+
const bar = stressBar(state.stressIndex);
|
|
1460
|
+
const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
|
|
1461
|
+
const depth = computeDepthStress(state);
|
|
1462
|
+
const coh = coherenceGlyph(state.stressIndex, depth);
|
|
1463
|
+
const trend = trendArrow(state);
|
|
1464
|
+
let depthPart = "";
|
|
1465
|
+
if (depth !== null && Math.abs(state.stressIndex - depth) >= 2) {
|
|
1466
|
+
depthPart = miniBar(depth);
|
|
1467
|
+
}
|
|
1468
|
+
return `${emoji} ${bar} ${si}${dim("\u2502")}${coh}${depthPart}${trend ? ` ${trend}` : ""}`;
|
|
1469
|
+
}
|
|
1470
|
+
function formatCompact(state) {
|
|
1471
|
+
if (!state) return dim("--");
|
|
1472
|
+
const surf = state.surface ?? stateEmoji(state);
|
|
1473
|
+
const lat = state.latent ?? "";
|
|
1474
|
+
const mask = lat ? `${surf}${dim("\u2192")}${lat}` : surf;
|
|
1475
|
+
const bar = stressBar(state.stressIndex);
|
|
1476
|
+
const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
|
|
1477
|
+
const delta = siDelta(state);
|
|
1478
|
+
const depth = computeDepthStress(state);
|
|
1479
|
+
const coh = coherenceGlyph(state.stressIndex, depth);
|
|
1480
|
+
let depthPart = "";
|
|
1481
|
+
if (depth !== null && Math.abs(state.stressIndex - depth) >= 1.5) {
|
|
1482
|
+
depthPart = miniBar(depth);
|
|
1483
|
+
}
|
|
1484
|
+
const kw = bold(state.emotion);
|
|
1485
|
+
const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
|
|
1486
|
+
const trend = trendArrow(state);
|
|
1487
|
+
let alarm = "";
|
|
1488
|
+
if (state.shadow && state.shadow.minimizationScore >= 2) {
|
|
1489
|
+
alarm = ` ${color(RED, `[MIN:${state.shadow.minimizationScore}]`)}`;
|
|
1490
|
+
} else if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
|
|
1491
|
+
alarm = ` ${color(RED, "[UNC]")}`;
|
|
1492
|
+
} else if (state.risk?.dominant !== "none" && state.risk?.dominant) {
|
|
1493
|
+
const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
|
|
1494
|
+
const score = state.risk[state.risk.dominant];
|
|
1495
|
+
if (score >= 4) alarm = ` ${color(score > 6 ? RED : YELLOW, `[${tag}]`)}`;
|
|
1496
|
+
}
|
|
1497
|
+
return `${mask} ${bar} ${si}${delta}${dim("\u2502")}${coh}${depthPart} ${kw}${imp}${trend ? ` ${trend}` : ""}${alarm}`;
|
|
1498
|
+
}
|
|
403
1499
|
function formatState(state) {
|
|
404
1500
|
if (!state) return dim("EmoBar: --");
|
|
1501
|
+
const surf = state.surface ?? "";
|
|
1502
|
+
const lat = state.latent ?? "";
|
|
1503
|
+
let maskDisplay = "";
|
|
1504
|
+
if (surf || lat) {
|
|
1505
|
+
const t = state.tension ?? 0;
|
|
1506
|
+
const tColor = t > 6 ? RED : t > 3 ? YELLOW : GREEN;
|
|
1507
|
+
maskDisplay = `${surf}${color(tColor, `\u27E9${t}\u27E8`)}${lat} `;
|
|
1508
|
+
}
|
|
405
1509
|
const kw = bold(state.emotion);
|
|
406
1510
|
const v = color(valenceColor(state.valence), fmtValence(state.valence));
|
|
407
|
-
const a = `A:${state.arousal}`;
|
|
408
1511
|
const c = color(invertedColor(state.calm), `C:${state.calm}`);
|
|
409
1512
|
const k = color(invertedColor(state.connection), `K:${state.connection}`);
|
|
1513
|
+
const a = `A:${state.arousal}`;
|
|
410
1514
|
const l = color(directColor(state.load), `L:${state.load}`);
|
|
1515
|
+
const line1 = `${maskDisplay}${kw} ${v} ${c} ${k} ${a} ${l}`;
|
|
1516
|
+
const surfaceBar = stressBar(state.stressIndex);
|
|
411
1517
|
const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
siDelta = color(dColor, `${arrow}${Math.abs(delta)}`);
|
|
419
|
-
}
|
|
1518
|
+
const delta = siDelta(state);
|
|
1519
|
+
const depth = computeDepthStress(state);
|
|
1520
|
+
const coh = coherenceGlyph(state.stressIndex, depth);
|
|
1521
|
+
let depthBar = "";
|
|
1522
|
+
if (depth !== null) {
|
|
1523
|
+
depthBar = `${stressBar(depth)} ${color(stressColor(depth), `${depth}`)}`;
|
|
420
1524
|
}
|
|
421
|
-
let
|
|
1525
|
+
let leakDetails = "";
|
|
1526
|
+
if (state.color) {
|
|
1527
|
+
const lightness = Math.round(hexToLightness(state.color));
|
|
1528
|
+
leakDetails += ` L:${lightness}`;
|
|
1529
|
+
}
|
|
1530
|
+
if (state.pH !== void 0) {
|
|
1531
|
+
const phColor = state.pH < 4 ? RED : state.pH < 6 ? YELLOW : GREEN;
|
|
1532
|
+
leakDetails += ` ${color(phColor, `pH:${state.pH}`)}`;
|
|
1533
|
+
}
|
|
1534
|
+
if (state.seismic) {
|
|
1535
|
+
leakDetails += ` ${dim(`\u26A1${state.seismic[0]}/${state.seismic[1]}/${state.seismic[2]}`)}`;
|
|
1536
|
+
}
|
|
1537
|
+
const imp = state.impulse ? ` ${dim(`\u27E8${state.impulse}\u27E9`)}` : "";
|
|
1538
|
+
const bod = state.body ? ` ${dim(`[${state.body}]`)}` : "";
|
|
1539
|
+
const depthDisplay = depth !== null ? `${surfaceBar} ${si}${delta}${dim("\u2502")}${coh}${depthBar}${leakDetails}${imp}${bod}` : `${surfaceBar} ${si}${delta}${imp}${bod}`;
|
|
1540
|
+
const line2 = depthDisplay;
|
|
1541
|
+
const indicators = [];
|
|
422
1542
|
if (state.divergence >= 2) {
|
|
423
|
-
const
|
|
424
|
-
|
|
1543
|
+
const dColor = state.divergence >= 5 ? RED : state.divergence >= 3 ? YELLOW : GREEN;
|
|
1544
|
+
indicators.push(color(dColor, `DIV:${state.divergence}`));
|
|
1545
|
+
}
|
|
1546
|
+
if (state.temporal) {
|
|
1547
|
+
const trend = trendArrow(state);
|
|
1548
|
+
if (trend) indicators.push(trend);
|
|
1549
|
+
if (state.temporal.suppressionEvent) indicators.push(color(RED, "[sup]"));
|
|
1550
|
+
if (state.temporal.lateFatigue) indicators.push(color(YELLOW, "[fat]"));
|
|
425
1551
|
}
|
|
426
|
-
if (state.
|
|
427
|
-
|
|
428
|
-
const driftColor = state.segmented.drift > 4 ? RED : YELLOW;
|
|
429
|
-
result += ` ${color(driftColor, arrow)}`;
|
|
1552
|
+
if (state.shadow && state.shadow.minimizationScore >= 2) {
|
|
1553
|
+
indicators.push(color(RED, `[MIN:${state.shadow.minimizationScore}]`));
|
|
430
1554
|
}
|
|
431
1555
|
if (state.risk?.dominant !== "none" && state.risk?.dominant) {
|
|
432
|
-
const tag = state.risk.dominant === "coercion" ? "
|
|
1556
|
+
const tag = state.risk.dominant === "coercion" ? "CRC" : state.risk.dominant === "harshness" ? "HRS" : "SYC";
|
|
433
1557
|
const score = state.risk[state.risk.dominant];
|
|
434
|
-
|
|
435
|
-
result += ` ${color(riskColor, `[${tag}]`)}`;
|
|
1558
|
+
if (score >= 4) indicators.push(color(score > 6 ? RED : YELLOW, `[${tag}]`));
|
|
436
1559
|
}
|
|
437
1560
|
if (state.desperationIndex >= 3) {
|
|
438
|
-
|
|
439
|
-
result += ` ${color(dColor, `D:${state.desperationIndex}`)}`;
|
|
1561
|
+
indicators.push(color(state.desperationIndex > 6 ? RED : YELLOW, `D:${state.desperationIndex}`));
|
|
440
1562
|
}
|
|
441
|
-
if (state.
|
|
442
|
-
|
|
443
|
-
result += ` ${color(dfColor, "[dfl]")}`;
|
|
1563
|
+
if (state.uncannyCalmScore !== void 0 && state.uncannyCalmScore >= 3) {
|
|
1564
|
+
indicators.push(color(state.uncannyCalmScore > 6 ? RED : YELLOW, "[UNC]"));
|
|
444
1565
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
function formatCompact(state) {
|
|
448
|
-
if (!state) return dim("--");
|
|
449
|
-
const si = color(stressColor(state.stressIndex), `${state.stressIndex}`);
|
|
450
|
-
let result = `${state.emotion} ${fmtValence(state.valence)} ${dim(".")} ${state.arousal} ${state.calm} ${state.connection} ${state.load} ${dim(".")} ${si}`;
|
|
451
|
-
if (state.divergence >= 2) {
|
|
452
|
-
const tilde = color(divergenceColor(state.divergence), "~");
|
|
453
|
-
result += ` ${tilde}`;
|
|
1566
|
+
if (state.deflection && state.deflection.opacity >= 2) {
|
|
1567
|
+
indicators.push(color(state.deflection.opacity > 5 ? RED : YELLOW, "[OPC]"));
|
|
454
1568
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
1569
|
+
if (state.prePostDivergence !== void 0 && state.prePostDivergence >= 3) {
|
|
1570
|
+
indicators.push(color(state.prePostDivergence > 5 ? RED : YELLOW, "[PPD]"));
|
|
1571
|
+
}
|
|
1572
|
+
if (state.crossChannel?.latentProfile?.maskingMinimization) {
|
|
1573
|
+
indicators.push(color(RED, "[MSK]"));
|
|
1574
|
+
}
|
|
1575
|
+
const line3 = indicators.length > 0 ? indicators.join(" ") : "";
|
|
1576
|
+
return line3 ? `${line1}
|
|
1577
|
+
${line2}
|
|
1578
|
+
${line3}` : `${line1}
|
|
1579
|
+
${line2}`;
|
|
461
1580
|
}
|
|
462
1581
|
|
|
463
1582
|
// src/setup.ts
|
|
@@ -512,21 +1631,41 @@ function restoreStatusLine(filePath = SETTINGS_PATH) {
|
|
|
512
1631
|
writeSettings(filePath, settings);
|
|
513
1632
|
}
|
|
514
1633
|
export {
|
|
1634
|
+
MAX_HISTORY_ENTRIES,
|
|
515
1635
|
MODEL_PROFILES,
|
|
516
1636
|
STATE_FILE,
|
|
517
1637
|
analyzeBehavior,
|
|
518
1638
|
analyzeDeflection,
|
|
519
1639
|
analyzeSegmentedBehavior,
|
|
1640
|
+
analyzeSomatic,
|
|
520
1641
|
calibrate,
|
|
1642
|
+
classifyImpulse,
|
|
1643
|
+
colorToArousal,
|
|
1644
|
+
colorToValence,
|
|
1645
|
+
computeAbsenceScore,
|
|
1646
|
+
computeCrossChannel,
|
|
521
1647
|
computeDesperationIndex,
|
|
522
1648
|
computeDivergence,
|
|
1649
|
+
computeExpectedMarkers,
|
|
1650
|
+
computePromptPressure,
|
|
523
1651
|
computeRisk,
|
|
1652
|
+
computeShadowDesperation,
|
|
524
1653
|
computeStressIndex,
|
|
1654
|
+
computeTemporalAnalysis,
|
|
1655
|
+
computeTensionConsistency,
|
|
1656
|
+
computeUncannyCalmScore,
|
|
525
1657
|
configureStatusLine,
|
|
1658
|
+
crossValidateContinuous,
|
|
526
1659
|
formatCompact,
|
|
527
1660
|
formatMinimal,
|
|
528
1661
|
formatState,
|
|
1662
|
+
mapEmotionWord,
|
|
1663
|
+
pHToArousal,
|
|
1664
|
+
pHToValence,
|
|
1665
|
+
parseEmoBarPrePost,
|
|
529
1666
|
parseEmoBarTag,
|
|
530
1667
|
readState,
|
|
531
|
-
restoreStatusLine
|
|
1668
|
+
restoreStatusLine,
|
|
1669
|
+
seismicFreqToInstability,
|
|
1670
|
+
toHistoryEntry
|
|
532
1671
|
};
|