Skip to content

Commit 1a63008

Browse files
committed
refactor: extract shared model logic into dedicated .ts files with tests (attention, context pressure, squeeze, zones, regions)
1 parent 6b516f0 commit 1a63008

13 files changed

Lines changed: 1762 additions & 0 deletions
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import {
4+
ATTENTION_TIERS,
5+
attentionAt,
6+
buildCurvePaths,
7+
createAttentionCurve,
8+
fillAtAttentionCrossing,
9+
jCurveStrength,
10+
projectCurve,
11+
tierAt,
12+
toneAt,
13+
zoneBandFill,
14+
} from './attentionModel.ts';
15+
16+
const compactMobilePlot = { left: 40, right: 316, top: 40, bottom: 176 };
17+
18+
/* ── Curve shape (ported from UShapeAttentionCurveGeometry.test) ── */
19+
20+
test('attention curve keeps both context edges above the middle at moderate fill', () => {
21+
const { points } = createAttentionCurve(50);
22+
const middle = points[points.length / 2 - 0.5];
23+
24+
assert.equal(points[0].position, 0);
25+
assert.equal(points.at(-1)?.position, 1);
26+
assert.ok(points[0].attention > middle.attention);
27+
assert.ok(points.at(-1)!.attention > middle.attention);
28+
});
29+
30+
test('long context turns the symmetric U toward recency', () => {
31+
const { points } = createAttentionCurve(100);
32+
33+
assert.ok(points.at(-1)!.attention > points[0].attention);
34+
assert.equal(points.at(-1)!.cyanOpacity, 1);
35+
});
36+
37+
test('compact mobile projection retains context left-to-right and attention high-to-low', () => {
38+
const { points } = createAttentionCurve(50);
39+
const projected = projectCurve(points, compactMobilePlot);
40+
const middle = projected[projected.length / 2 - 0.5];
41+
42+
assert.equal(projected[0].x, compactMobilePlot.left);
43+
assert.equal(projected.at(-1)?.x, compactMobilePlot.right);
44+
assert.ok(projected[0].y < middle.y);
45+
assert.ok(projected.at(-1)!.y < middle.y);
46+
});
47+
48+
/* ── Unification invariants ─────────────────────────────────────── */
49+
50+
test('one shared primacy-decay coefficient: J-curve is zero at half fill and ramps to 1 at full', () => {
51+
assert.equal(jCurveStrength(0), 0);
52+
assert.equal(jCurveStrength(0.5), 0);
53+
assert.equal(jCurveStrength(1), 1);
54+
});
55+
56+
test('attention degrades monotonically as the window fills at every position', () => {
57+
for (const position of [0, 0.25, 0.5, 0.75, 1]) {
58+
for (let fill = 0; fill < 1; fill += 0.05) {
59+
assert.ok(
60+
attentionAt(position, fill) >=
61+
attentionAt(position, fill + 0.05) - 1e-9,
62+
`position ${position} fill ${fill}`
63+
);
64+
}
65+
}
66+
});
67+
68+
test('recency edge stays strong at every fill while the middle collapses', () => {
69+
assert.ok(attentionAt(1, 1) >= ATTENTION_TIERS.strong);
70+
assert.ok(attentionAt(0.5, 1) < ATTENTION_TIERS.degraded);
71+
});
72+
73+
test('attention is full in an empty window regardless of position', () => {
74+
for (const position of [0, 0.25, 0.5, 0.75, 1]) {
75+
assert.equal(attentionAt(position, 0), 1);
76+
}
77+
});
78+
79+
/* ── Tiers, tones, band fills ───────────────────────────────────── */
80+
81+
test('tier and tone thresholds are consistent', () => {
82+
assert.equal(tierAt(ATTENTION_TIERS.strong), 'strong');
83+
assert.equal(tierAt(ATTENTION_TIERS.degraded), 'degraded');
84+
assert.equal(tierAt(0), 'collapsed');
85+
assert.equal(toneAt(1), 'success');
86+
assert.equal(toneAt(0.3), 'warning');
87+
assert.equal(toneAt(0.1), 'error');
88+
});
89+
90+
test('middle zone band deepens muted → warning → error as fill grows', () => {
91+
assert.equal(zoneBandFill('middle', 0), 'var(--surface-muted)');
92+
assert.equal(zoneBandFill('middle', 0.5), 'var(--surface-muted)');
93+
assert.equal(zoneBandFill('middle', 0.7), 'var(--visual-bg-warning)');
94+
assert.equal(zoneBandFill('middle', 0.9), 'var(--visual-bg-error)');
95+
});
96+
97+
test('recency band never degrades; primacy band fades only past half fill', () => {
98+
for (const fill of [0, 0.5, 0.8, 1]) {
99+
assert.equal(zoneBandFill('recency', fill), 'var(--visual-bg-cyan)');
100+
}
101+
assert.ok(zoneBandFill('primacy', 0.4).includes('100%'));
102+
assert.ok(!zoneBandFill('primacy', 1).includes('100%'));
103+
});
104+
105+
test('fillAtAttentionCrossing inverts attentionAt', () => {
106+
for (const target of [ATTENTION_TIERS.strong, ATTENTION_TIERS.degraded]) {
107+
const fill = fillAtAttentionCrossing(0.5, target);
108+
assert.ok(Math.abs(attentionAt(0.5, fill) - target) < 0.01);
109+
}
110+
// The collapsed crossing happens at a deeper fill than the degraded one.
111+
assert.ok(
112+
fillAtAttentionCrossing(0.5, ATTENTION_TIERS.degraded) >
113+
fillAtAttentionCrossing(0.5, ATTENTION_TIERS.strong)
114+
);
115+
});
116+
117+
/* ── Smooth curve paths (pressure diagram panel) ────────────────── */
118+
119+
test('buildCurvePaths reports the valley floor and spans the full plot', () => {
120+
const { minAttention, curvePath, mobileCurvePath } = buildCurvePaths(0.9);
121+
122+
assert.ok(Math.abs(minAttention - attentionAt(0.5, 0.9)) < 0.02);
123+
assert.ok(curvePath.startsWith('M'));
124+
assert.ok(mobileCurvePath.startsWith('M'));
125+
// Empty window: no drop anywhere.
126+
assert.equal(buildCurvePaths(0).minAttention, 1);
127+
});

0 commit comments

Comments
 (0)