cbrowser 18.79.0 → 18.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +395 -66
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +78 -0
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/page-understanding.d.ts +139 -1
- package/dist/analysis/page-understanding.d.ts.map +1 -1
- package/dist/analysis/page-understanding.js +179 -12
- package/dist/analysis/page-understanding.js.map +1 -1
- package/dist/artifact-store.d.ts +19 -5
- package/dist/artifact-store.d.ts.map +1 -1
- package/dist/artifact-store.js +41 -11
- package/dist/artifact-store.js.map +1 -1
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +81 -5
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +55 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +3 -2
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +234 -7
- package/dist/config.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +108 -4
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +62 -938
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/account-personas.d.ts +41 -0
- package/dist/mcp-tools/account-personas.d.ts.map +1 -0
- package/dist/mcp-tools/account-personas.js +98 -0
- package/dist/mcp-tools/account-personas.js.map +1 -0
- package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/analysis-tools.js +12 -2
- package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/artifact-tools.d.ts +3 -0
- package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/artifact-tools.js +78 -0
- package/dist/mcp-tools/base/artifact-tools.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +11 -0
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +595 -340
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.js +18 -0
- package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.js +59 -3
- package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.d.ts +37 -0
- package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +267 -6
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.d.ts +12 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +63 -15
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.js +33 -2
- package/dist/mcp-tools/base/extraction-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.d.ts +2 -0
- package/dist/mcp-tools/base/index.d.ts.map +1 -1
- package/dist/mcp-tools/base/index.js +9 -0
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/base/marketing-tools.js +3 -3
- package/dist/mcp-tools/base/marketing-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +529 -40
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.js +36 -3
- package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
- package/dist/mcp-tools/base/values-tools.d.ts +113 -0
- package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/values-tools.js +868 -78
- package/dist/mcp-tools/base/values-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +130 -10
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/index.d.ts +3 -1
- package/dist/mcp-tools/index.d.ts.map +1 -1
- package/dist/mcp-tools/index.js +3 -28
- package/dist/mcp-tools/index.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +363 -20
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts +3 -0
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js +406 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -0
- package/dist/mcp-tools/screenshot-utils.d.ts.map +1 -1
- package/dist/mcp-tools/screenshot-utils.js +56 -10
- package/dist/mcp-tools/screenshot-utils.js.map +1 -1
- package/dist/mcp-tools/tier-gate.js +2 -2
- package/dist/mcp-tools/tier-gate.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +4 -1
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/mcp-tools/ui-resources.d.ts +167 -0
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -0
- package/dist/mcp-tools/ui-resources.js +707 -0
- package/dist/mcp-tools/ui-resources.js.map +1 -0
- package/dist/mcp-tools/widget-kit.d.ts +299 -0
- package/dist/mcp-tools/widget-kit.d.ts.map +1 -0
- package/dist/mcp-tools/widget-kit.js +2034 -0
- package/dist/mcp-tools/widget-kit.js.map +1 -0
- package/dist/mcp-ui-resources.d.ts.map +1 -1
- package/dist/mcp-ui-resources.js +14 -4
- package/dist/mcp-ui-resources.js.map +1 -1
- package/dist/persona-questionnaire.d.ts +69 -1
- package/dist/persona-questionnaire.d.ts.map +1 -1
- package/dist/persona-questionnaire.js +177 -76
- package/dist/persona-questionnaire.js.map +1 -1
- package/dist/persona-scope.d.ts +20 -0
- package/dist/persona-scope.d.ts.map +1 -0
- package/dist/persona-scope.js +59 -0
- package/dist/persona-scope.js.map +1 -0
- package/dist/personas.d.ts +51 -17
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +947 -286
- package/dist/personas.js.map +1 -1
- package/dist/recording/capture-player.d.ts +68 -0
- package/dist/recording/capture-player.d.ts.map +1 -0
- package/dist/recording/capture-player.js +298 -0
- package/dist/recording/capture-player.js.map +1 -0
- package/dist/recording/engine.d.ts +52 -0
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +241 -3
- package/dist/recording/engine.js.map +1 -1
- package/dist/recording/inline-player.d.ts +39 -0
- package/dist/recording/inline-player.d.ts.map +1 -0
- package/dist/recording/inline-player.js +272 -0
- package/dist/recording/inline-player.js.map +1 -0
- package/dist/recording/saliency-overlay.d.ts +133 -0
- package/dist/recording/saliency-overlay.d.ts.map +1 -0
- package/dist/recording/saliency-overlay.js +360 -0
- package/dist/recording/saliency-overlay.js.map +1 -0
- package/dist/recording/types.d.ts +32 -0
- package/dist/recording/types.d.ts.map +1 -1
- package/dist/recording/types.js +46 -0
- package/dist/recording/types.js.map +1 -1
- package/dist/site-model/manager.d.ts +20 -0
- package/dist/site-model/manager.d.ts.map +1 -1
- package/dist/site-model/manager.js +52 -0
- package/dist/site-model/manager.js.map +1 -1
- package/dist/trait-reference.d.ts +26 -2
- package/dist/trait-reference.d.ts.map +1 -1
- package/dist/trait-reference.js +129 -19
- package/dist/trait-reference.js.map +1 -1
- package/dist/types.d.ts +105 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/url-resolution.d.ts +58 -0
- package/dist/url-resolution.d.ts.map +1 -0
- package/dist/url-resolution.js +143 -0
- package/dist/url-resolution.js.map +1 -0
- package/dist/values/big-five-inference.d.ts +121 -0
- package/dist/values/big-five-inference.d.ts.map +1 -0
- package/dist/values/big-five-inference.js +241 -0
- package/dist/values/big-five-inference.js.map +1 -0
- package/dist/values/big-five-values.d.ts +76 -0
- package/dist/values/big-five-values.d.ts.map +1 -0
- package/dist/values/big-five-values.js +152 -0
- package/dist/values/big-five-values.js.map +1 -0
- package/dist/values/index.d.ts +1 -1
- package/dist/values/index.d.ts.map +1 -1
- package/dist/values/index.js +1 -1
- package/dist/values/index.js.map +1 -1
- package/dist/values/persona-completeness.d.ts +91 -0
- package/dist/values/persona-completeness.d.ts.map +1 -0
- package/dist/values/persona-completeness.js +114 -0
- package/dist/values/persona-completeness.js.map +1 -0
- package/dist/values/persona-diff.d.ts +50 -0
- package/dist/values/persona-diff.d.ts.map +1 -0
- package/dist/values/persona-diff.js +126 -0
- package/dist/values/persona-diff.js.map +1 -0
- package/dist/values/persona-values.d.ts +16 -0
- package/dist/values/persona-values.d.ts.map +1 -1
- package/dist/values/persona-values.js +33 -0
- package/dist/values/persona-values.js.map +1 -1
- package/dist/values/schwartz-values.d.ts +9 -1
- package/dist/values/schwartz-values.d.ts.map +1 -1
- package/dist/values/schwartz-values.js +10 -2
- package/dist/values/schwartz-values.js.map +1 -1
- package/dist/values/value-mappings.d.ts +41 -2
- package/dist/values/value-mappings.d.ts.map +1 -1
- package/dist/values/value-mappings.js +65 -8
- package/dist/values/value-mappings.js.map +1 -1
- package/dist/visual/attention-quality.d.ts +27 -0
- package/dist/visual/attention-quality.d.ts.map +1 -1
- package/dist/visual/attention-quality.js +164 -2
- package/dist/visual/attention-quality.js.map +1 -1
- package/dist/visual/attention-transport.d.ts +41 -2
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +98 -26
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/barrier-overlay-html.d.ts +10 -0
- package/dist/visual/barrier-overlay-html.d.ts.map +1 -1
- package/dist/visual/barrier-overlay-html.js.map +1 -1
- package/dist/visual/cognitive-models.d.ts +109 -13
- package/dist/visual/cognitive-models.d.ts.map +1 -1
- package/dist/visual/cognitive-models.js +166 -3
- package/dist/visual/cognitive-models.js.map +1 -1
- package/dist/visual/cognitive-transport-chain.d.ts +149 -5
- package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
- package/dist/visual/cognitive-transport-chain.js +558 -30
- package/dist/visual/cognitive-transport-chain.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +45 -3
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +126 -12
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/llm-relevance.d.ts +193 -0
- package/dist/visual/llm-relevance.d.ts.map +1 -0
- package/dist/visual/llm-relevance.js +480 -0
- package/dist/visual/llm-relevance.js.map +1 -0
- package/dist/visual/perceptual-transport.d.ts +60 -0
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +228 -19
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/dist/visual/persona-context.d.ts +23 -0
- package/dist/visual/persona-context.d.ts.map +1 -0
- package/dist/visual/persona-context.js +44 -0
- package/dist/visual/persona-context.js.map +1 -0
- package/package.json +2 -1
|
@@ -41,9 +41,37 @@ export const DEMAND_DIMENSIONS = [
|
|
|
41
41
|
// Trust
|
|
42
42
|
'trustCalibration',
|
|
43
43
|
// Additional extended traits
|
|
44
|
-
'
|
|
44
|
+
'mentalModelFlexibility', 'curiosity',
|
|
45
45
|
// Processing
|
|
46
46
|
'processingSpeed', 'textProcessing',
|
|
47
|
+
// Experience
|
|
48
|
+
//
|
|
49
|
+
// Absent until 2026-08-02, which is why the siteFamiliarity parameter was
|
|
50
|
+
// inert. It is a member of COGNITIVE_TRAITS, so a persona carried a capacity
|
|
51
|
+
// on this dimension, but the demand vector had no slot for it -- so the
|
|
52
|
+
// capacity was transported against a demand that did not exist and
|
|
53
|
+
// contributed nothing. Runs at familiarity 0 and 1 came back byte-identical
|
|
54
|
+
// while the tool attested the parameter had been applied.
|
|
55
|
+
//
|
|
56
|
+
// The two vectors are supposed to be the same space. A dimension present in
|
|
57
|
+
// one and missing from the other is silently dropped, and there is now a test
|
|
58
|
+
// asserting COGNITIVE_TRAITS and DEMAND_DIMENSIONS do not diverge.
|
|
59
|
+
'siteFamiliarity',
|
|
60
|
+
// Capacity, bridged from the formal reading and pointing models.
|
|
61
|
+
//
|
|
62
|
+
// These layers previously read DISPOSITION traits -- readingTendency for
|
|
63
|
+
// readability, patience and proceduralFluency for motor -- while the modules
|
|
64
|
+
// that actually model decoding and Fitts pointing sat beside them, computing
|
|
65
|
+
// WPM, visual span, phonological penalties, movement times and hit
|
|
66
|
+
// probabilities that no layer ever consumed. A dyslexic persona therefore
|
|
67
|
+
// scored LOWER readability cost than an ADHD persona on a text-dense page.
|
|
68
|
+
//
|
|
69
|
+
// `sustainedAttention` is the attentional half of reading, and it was missing
|
|
70
|
+
// from the model entirely. Taking `readingTendency` out of the readability
|
|
71
|
+
// layer left decoding and nothing else, so an ADHD reader -- whose decoding is
|
|
72
|
+
// typically intact and whose difficulty is holding the line -- read a wall of
|
|
73
|
+
// text at no attentional cost at all.
|
|
74
|
+
'readingCapacity', 'motorCapacity', 'sustainedAttention',
|
|
47
75
|
];
|
|
48
76
|
/**
|
|
49
77
|
* Layer definitions for the sequential transport chain.
|
|
@@ -56,7 +84,16 @@ const LAYER_DEFINITIONS = [
|
|
|
56
84
|
},
|
|
57
85
|
{
|
|
58
86
|
name: 'cognitiveLoad',
|
|
59
|
-
|
|
87
|
+
// siteFamiliarity added 2026-08-02. It was in COGNITIVE_TRAITS and in no
|
|
88
|
+
// layer, so it reached the profile, got a capacity, and then contributed to
|
|
89
|
+
// nothing -- the third and final reason the parameter was inert. Knowing a
|
|
90
|
+
// site is a substitute for holding its layout in working memory, which is
|
|
91
|
+
// exactly this layer, and the demand term for it comes from the same
|
|
92
|
+
// navigationDepth signal that already feeds workingMemory here.
|
|
93
|
+
// `transferLearning` moved here from readability 2026-08-02: applying a
|
|
94
|
+
// pattern learned on one interface to another is a load question, not a
|
|
95
|
+
// decoding one, and readability is now decoding-only.
|
|
96
|
+
traits: ['comprehension', 'workingMemory', 'informationForaging', 'siteFamiliarity', 'transferLearning'],
|
|
60
97
|
},
|
|
61
98
|
{
|
|
62
99
|
name: 'decision',
|
|
@@ -64,7 +101,48 @@ const LAYER_DEFINITIONS = [
|
|
|
64
101
|
},
|
|
65
102
|
{
|
|
66
103
|
name: 'motor',
|
|
67
|
-
|
|
104
|
+
// POINTING ONLY, as of 2026-08-03 (D-9 resolution). This layer answers one
|
|
105
|
+
// question: how expensive is it for this person to ACQUIRE a target.
|
|
106
|
+
//
|
|
107
|
+
// It used to sum pointing capability and procedural execution at equal
|
|
108
|
+
// weight in one scalar, which is the same failure shape the readability
|
|
109
|
+
// layer had before it was split. Measured on identical demand:
|
|
110
|
+
//
|
|
111
|
+
// motor-impairment-tremor cost 0.072 hitProbability 48.4%
|
|
112
|
+
// elderly-user cost 0.107 hitProbability 81.9%
|
|
113
|
+
//
|
|
114
|
+
// Worse pointing, lower cost -- because the tremor persona's procedural
|
|
115
|
+
// fluency (0.60) outweighed its pointing deficit against elderly's 0.40.
|
|
116
|
+
// `motorAccessibility` is reported in the same response and tracks only
|
|
117
|
+
// pointing, so every ordering check against it read as broken.
|
|
118
|
+
//
|
|
119
|
+
// `proceduralFluency` moved to `motorProcedure` below. Nothing was weighted
|
|
120
|
+
// to achieve this: the two layers contribute additively like every other
|
|
121
|
+
// layer, which makes their relative contribution visible and inspectable
|
|
122
|
+
// rather than hidden inside a scalar. There is no empirical basis for a
|
|
123
|
+
// relative weighting between target acquisition and sequence execution, and
|
|
124
|
+
// a constant invented here would acquire false authority by ending up in a
|
|
125
|
+
// research instrument. The calibration slot is deliberately left empty --
|
|
126
|
+
// see MOTOR_LAYER_WEIGHTS.
|
|
127
|
+
traits: ['motorCapacity'],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'motorProcedure',
|
|
131
|
+
// Executing an interaction SEQUENCE once a target has been acquired --
|
|
132
|
+
// multi-step flows, form progressions, anything where the cost is in the
|
|
133
|
+
// order of operations rather than in hitting the control.
|
|
134
|
+
//
|
|
135
|
+
// Separate from `motor` because the two genuinely move in opposite
|
|
136
|
+
// directions for the same persona, which is precisely what makes them
|
|
137
|
+
// separable rather than one number: a tremor persona can be a poor pointer
|
|
138
|
+
// and a competent sequence-follower, and an elderly persona the reverse.
|
|
139
|
+
//
|
|
140
|
+
// DEMAND IS ABSOLUTE, not area-normalised. The number of steps in a
|
|
141
|
+
// sequence does not shrink because the page is tall. Its demand derives
|
|
142
|
+
// from interactiveElementCount, which is a raw count -- so there is nothing
|
|
143
|
+
// to remove here today, and this is recorded for the D-12 layer
|
|
144
|
+
// classification table rather than decided silently in code.
|
|
145
|
+
traits: ['proceduralFluency'],
|
|
68
146
|
},
|
|
69
147
|
{
|
|
70
148
|
name: 'frustration',
|
|
@@ -72,7 +150,53 @@ const LAYER_DEFINITIONS = [
|
|
|
72
150
|
},
|
|
73
151
|
{
|
|
74
152
|
name: 'readability',
|
|
75
|
-
|
|
153
|
+
// DECODING ONLY, as of 2026-08-02 (D-3 Option 1). This layer answers one
|
|
154
|
+
// question -- how expensive is it for this person to turn these glyphs into
|
|
155
|
+
// words -- and it is the layer `legibilityQuality` makes a promise about:
|
|
156
|
+
// lower legibility for someone means higher readability cost for them.
|
|
157
|
+
//
|
|
158
|
+
// Everything else that was here has been moved out, and the history is why
|
|
159
|
+
// it had to be:
|
|
160
|
+
//
|
|
161
|
+
// R1 adhd 0.401 / dyslexic 0.189 inverted (disposition drove it)
|
|
162
|
+
// R2 adhd 0.112 / dyslexic 0.142 correct (attentional term removed)
|
|
163
|
+
// R4 adhd 0.161 / dyslexic 0.126 inverted (attentional term restored)
|
|
164
|
+
//
|
|
165
|
+
// Removing the attentional term fixed the ordering and deleted ADHD reading
|
|
166
|
+
// cost. Restoring it recovered ADHD reading cost and broke the ordering.
|
|
167
|
+
// BOTH were correct fixes. The layer was carrying two mechanisms through
|
|
168
|
+
// one scalar under a contract that only describes one of them, and no
|
|
169
|
+
// weighting satisfies both -- an ADHD attentionSpan of 0.30 against a
|
|
170
|
+
// dyslexic 0.50 swamps five decoding terms pointing the other way.
|
|
171
|
+
//
|
|
172
|
+
// So the second mechanism gets its own layer rather than a smaller weight.
|
|
173
|
+
// `sustainedAttention` moved to `readingAttention` below.
|
|
174
|
+
// `comprehension` moved out entirely: it is "grasp of UI conventions", not
|
|
175
|
+
// decoding, and it already drives cognitiveLoad -- keeping it here made the
|
|
176
|
+
// contract non-monotonic for personas whose comprehension and decoding
|
|
177
|
+
// disagree, which after the reading-capacity schema change is most of them.
|
|
178
|
+
// `transferLearning` moved to cognitiveLoad: applying a pattern learned on
|
|
179
|
+
// one interface to another is not reading. It is not orphaned by the move.
|
|
180
|
+
traits: ['readingCapacity'],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'readingAttention',
|
|
184
|
+
// The attentional half of reading, split out of `readability` so both are
|
|
185
|
+
// separately attributable rather than summed into one number that can only
|
|
186
|
+
// be right about one of them.
|
|
187
|
+
//
|
|
188
|
+
// This is the layer an ADHD reader's cost lands in: decoding intact, loses
|
|
189
|
+
// place, re-reads, pulled off the line by movement in the periphery. It is
|
|
190
|
+
// NOT frustration -- losing your place is a processing cost, not an
|
|
191
|
+
// emotional response to one, and routing it through frustration would point
|
|
192
|
+
// remediation at reassurance instead of at chunking, shorter blocks and
|
|
193
|
+
// fewer moving distractors.
|
|
194
|
+
//
|
|
195
|
+
// Appended as layer 7 rather than inserted before readability, deliberately:
|
|
196
|
+
// layers spend from a budget the earlier ones depleted, so inserting would
|
|
197
|
+
// have changed readability's numbers through depletion as well as through
|
|
198
|
+
// its trait list, and the two effects could not be told apart.
|
|
199
|
+
traits: ['sustainedAttention'],
|
|
76
200
|
},
|
|
77
201
|
];
|
|
78
202
|
/**
|
|
@@ -89,10 +213,153 @@ const INTERACTION_PAIRS = [
|
|
|
89
213
|
/** Asymmetric cost weights (Theorem 2 in paper) */
|
|
90
214
|
const WEIGHT_DEFICIT = 1.0; // demand > capacity: high cost
|
|
91
215
|
const WEIGHT_SURPLUS = 0.3; // capacity > demand: low cost (surplus is cheap)
|
|
216
|
+
/**
|
|
217
|
+
* Dimensions where having MORE than the page asks for costs nothing at all.
|
|
218
|
+
*
|
|
219
|
+
* THE RULE, decided 2026-08-03, and it is derivable rather than chosen: surplus
|
|
220
|
+
* is FREE where the dimension's own `highEnd` names an ABILITY, and BILLED
|
|
221
|
+
* where it names a TENDENCY, a BIAS, or a SUSCEPTIBILITY. Read the trait
|
|
222
|
+
* reference and the classification falls out; a new dimension is classified by
|
|
223
|
+
* its own definition, not by anyone's taste.
|
|
224
|
+
*
|
|
225
|
+
* Why the split is real. Billing surplus is defensible for a disposition: a
|
|
226
|
+
* maximiser on a trivial page genuinely over-deliberates, and a careful reader
|
|
227
|
+
* genuinely reads a page that did not need reading. That is effort spent. It is
|
|
228
|
+
* NOT defensible for an ability -- having more working memory, or recognising
|
|
229
|
+
* conventions faster, than a page requires cannot make that page harder. The
|
|
230
|
+
* capacity/disposition axis is the same one that moved readingTendency to the
|
|
231
|
+
* demand side and kept comprehension out of the readability layer.
|
|
232
|
+
*
|
|
233
|
+
* What it was costing. Measured on a mid-density page before this change,
|
|
234
|
+
* SEVENTY PERCENT of `power-user`'s total cost was surplus, and 60% of
|
|
235
|
+
* `cognitive-adhd`'s. A uniform-capacity sweep produced a U:
|
|
236
|
+
*
|
|
237
|
+
* capacity 0.2 0.4 0.6 0.8 1.0
|
|
238
|
+
* cost 0.549 0.081 0.076 0.179 0.516
|
|
239
|
+
*
|
|
240
|
+
* A maximally capable persona scored almost exactly as badly as a maximally
|
|
241
|
+
* impaired one, for opposite reasons, and the output could not tell you which.
|
|
242
|
+
* That is the same defect as every other one this campaign found: a number that
|
|
243
|
+
* does not mean what its name says.
|
|
244
|
+
*
|
|
245
|
+
* Four dimensions are billed BECAUSE THEIR SCALE IS INVERTED -- changeBlindness,
|
|
246
|
+
* trustCalibration, curiosity and attentionPattern name a deficit at their high
|
|
247
|
+
* end ("misses most changes", "accepts claims at face value", "easily
|
|
248
|
+
* distracted"). Surplus there is more of the deficit, and billing it is
|
|
249
|
+
* correct.
|
|
250
|
+
*
|
|
251
|
+
* `selfEfficacy` is the weakest call in the free set: over-confidence is a real
|
|
252
|
+
* hazard, but its cost surfaces through riskTolerance and trustCalibration
|
|
253
|
+
* rather than as effort spent, so it is treated as the ability its high end
|
|
254
|
+
* describes.
|
|
255
|
+
*/
|
|
256
|
+
const SURPLUS_FREE_DIMENSIONS = new Set([
|
|
257
|
+
// Derived capacity bridges -- pure abilities by construction.
|
|
258
|
+
'readingCapacity', 'motorCapacity', 'sustainedAttention', 'processingSpeed', 'textProcessing', 'motorPrecision',
|
|
259
|
+
// Experience. Knowing a site better cannot make it harder to use.
|
|
260
|
+
'siteFamiliarity',
|
|
261
|
+
// Abilities: every one of these has a highEnd naming something a person CAN
|
|
262
|
+
// do, not something they TEND to do.
|
|
263
|
+
'comprehension', // "Instant recognition of all conventions"
|
|
264
|
+
'workingMemory', // "Perfect recall of all attempts and context"
|
|
265
|
+
'resilience', // "Instantly shrugs off errors, stays positive"
|
|
266
|
+
'selfEfficacy', // "I can solve anything"
|
|
267
|
+
'proceduralFluency', // "Follows procedures precisely and efficiently"
|
|
268
|
+
'transferLearning', // "Instantly applies patterns across all interfaces"
|
|
269
|
+
'interruptRecovery', // "Seamlessly resumes exactly where left off"
|
|
270
|
+
'mentalModelFlexibility', // "Highly adaptive - quickly forms new mental models"
|
|
271
|
+
'informationForaging', // "Follows information scent efficiently, abandons low-yield paths"
|
|
272
|
+
]);
|
|
273
|
+
/**
|
|
274
|
+
* Dimensions whose surplus IS billed, and why each one earns it.
|
|
275
|
+
*
|
|
276
|
+
* Stated explicitly rather than left as "everything else", so that a dimension
|
|
277
|
+
* added to DEMAND_DIMENSIONS cannot acquire a billing treatment by default. A
|
|
278
|
+
* test asserts every dimension appears in exactly one of the two sets.
|
|
279
|
+
*/
|
|
280
|
+
const SURPLUS_BILLED_DIMENSIONS = new Set([
|
|
281
|
+
'patience', // "Waits indefinitely" -- waiting longer than needed is time spent
|
|
282
|
+
'riskTolerance', // "Clicks anything that might work" -- a tendency, and a hazard
|
|
283
|
+
'persistence', // "Keeps trying same approach repeatedly" -- repeating unnecessary work
|
|
284
|
+
'readingTendency', // "Reads every word before acting" -- reading a page that did not need it
|
|
285
|
+
'emotionalContagion', // susceptibility to the interface's tone
|
|
286
|
+
'satisficing', // a decision STRATEGY, not an ability
|
|
287
|
+
'anchoringBias', // a bias, by name
|
|
288
|
+
'fearOfMissingOut', // susceptibility to urgency and scarcity
|
|
289
|
+
'socialProofSensitivity', // susceptibility to what others chose
|
|
290
|
+
'metacognitivePlanning', // "Careful planning before any action" -- planning a trivial page is waste
|
|
291
|
+
// Inverted scales: the HIGH end is the deficit, so surplus is more deficit.
|
|
292
|
+
'changeBlindness', // "Misses most changes outside focus area"
|
|
293
|
+
'trustCalibration', // "Highly trusting, accepts claims at face value"
|
|
294
|
+
'curiosity', // "Easily distracted by interesting elements"
|
|
295
|
+
'attentionPattern', // scanning-vs-thorough disposition
|
|
296
|
+
]);
|
|
297
|
+
/**
|
|
298
|
+
* Hill exponent on the abandonment curve. UNCALIBRATED.
|
|
299
|
+
*
|
|
300
|
+
* Same status as MOTOR_LAYER_WEIGHTS and flagged for the same reason: it is
|
|
301
|
+
* doing a great deal of work and nothing empirical set it. At 2 the curve is
|
|
302
|
+
* superlinear, which means the same total cost delivered in small increments
|
|
303
|
+
* barely registers -- splitting a fixed deficit of 0.607 across 14 steps takes
|
|
304
|
+
* cumulative abandonment from 22% to 2%.
|
|
305
|
+
*
|
|
306
|
+
* That matters beyond the scalar. Any future per-screen hazard model inherits
|
|
307
|
+
* this exponent, and at 2 it would make a long page SAFER than a short one with
|
|
308
|
+
* identical total demand purely from chunking. The exponent is not the fix for
|
|
309
|
+
* that -- per-screen demand measured on its own content is -- but a sequential
|
|
310
|
+
* model must not be built without knowing this constant is load-bearing.
|
|
311
|
+
*
|
|
312
|
+
* What would settle it: observed abandonment against measured task difficulty,
|
|
313
|
+
* or a dose-response fit on real session data. Until then it is a shape someone
|
|
314
|
+
* chose, not a shape anyone measured. (2026-08-03)
|
|
315
|
+
*/
|
|
316
|
+
export const ABANDONMENT_HILL_EXPONENT = 2;
|
|
317
|
+
/**
|
|
318
|
+
* Relative weighting between the two motor layers. BOTH DEFAULT TO 1.0 AND ARE
|
|
319
|
+
* UNCALIBRATED.
|
|
320
|
+
*
|
|
321
|
+
* They exist as named constants so that if a weighting is ever introduced it is
|
|
322
|
+
* visible, greppable and dated, rather than baked into the cost function as a
|
|
323
|
+
* bare multiplier. Nothing reads them today; the layers contribute additively
|
|
324
|
+
* like every other layer.
|
|
325
|
+
*
|
|
326
|
+
* What would settle them: motor-control literature on tremor adaptation in
|
|
327
|
+
* target acquisition, or validation of chain output against measured
|
|
328
|
+
* task-completion times. Until one of those exists, a number here would be an
|
|
329
|
+
* invention wearing the authority of a research instrument. (2026-08-03)
|
|
330
|
+
*/
|
|
331
|
+
export const MOTOR_LAYER_WEIGHTS = {
|
|
332
|
+
/** UNCALIBRATED. Target acquisition. */
|
|
333
|
+
motor: 1.0,
|
|
334
|
+
/** UNCALIBRATED. Sequence execution. */
|
|
335
|
+
motorProcedure: 1.0,
|
|
336
|
+
};
|
|
92
337
|
/** Capacity depletion rate per layer (alpha_i, Section 3.4) */
|
|
93
338
|
const DEPLETION_RATE = 0.15;
|
|
94
339
|
/** Minimum residual capacity floor (prevents total exhaustion) */
|
|
95
340
|
const CAPACITY_FLOOR = 0.05;
|
|
341
|
+
/**
|
|
342
|
+
* Numerical floor on residual capacity. NOT a behavioural limit.
|
|
343
|
+
*
|
|
344
|
+
* CAPACITY_FLOOR above was doing behavioural work it should not have: at 0.05
|
|
345
|
+
* it was reachable inside a single page, and once reached the account stopped
|
|
346
|
+
* responding to cost at all. Proportional drawdown asymptotes toward zero
|
|
347
|
+
* instead, so this exists only to keep the value away from denormals and to
|
|
348
|
+
* keep `gap = demand - capacity` finite. It is three orders of magnitude below
|
|
349
|
+
* anything a persona reaches in practice. (2026-08-03)
|
|
350
|
+
*/
|
|
351
|
+
const CAPACITY_EPSILON = 1e-4;
|
|
352
|
+
/**
|
|
353
|
+
* Largest fraction of remaining capacity one layer may draw. UNCALIBRATED.
|
|
354
|
+
*
|
|
355
|
+
* Prevents a single very expensive layer from emptying the account outright,
|
|
356
|
+
* which would reintroduce the saturation this replaced. 0.5 means no layer can
|
|
357
|
+
* take more than half of what is left, whatever its cost.
|
|
358
|
+
*
|
|
359
|
+
* What would settle it: within-session effort measurements across a task
|
|
360
|
+
* sequence. Same status as ABANDONMENT_HILL_EXPONENT and MOTOR_LAYER_WEIGHTS.
|
|
361
|
+
*/
|
|
362
|
+
const MAX_DRAWDOWN_FRACTION = 0.5;
|
|
96
363
|
// ── Sigmoid Helper ──
|
|
97
364
|
/**
|
|
98
365
|
* Standard logistic sigmoid for mapping raw page metrics to demand values.
|
|
@@ -152,13 +419,13 @@ export function computeDemandDistribution(pageMetrics) {
|
|
|
152
419
|
variance.workingMemory += infoDensity * 0.08;
|
|
153
420
|
variance.readingTendency += infoDensity * 0.07;
|
|
154
421
|
variance.informationForaging += infoDensity * 0.06;
|
|
155
|
-
// ── visualComplexity → changeBlindness,
|
|
422
|
+
// ── visualComplexity → changeBlindness, mentalModelFlexibility, attentionPattern (inferred)
|
|
156
423
|
const visDemand = visCplx; // already 0-1 from logScale
|
|
157
424
|
demands.changeBlindness = Math.max(demands.changeBlindness, visDemand);
|
|
158
|
-
demands.
|
|
425
|
+
demands.mentalModelFlexibility = Math.max(demands.mentalModelFlexibility, visDemand * 0.7);
|
|
159
426
|
demands.attentionPattern = Math.max(demands.attentionPattern, visDemand * 0.85);
|
|
160
427
|
variance.changeBlindness += visCplx * 0.12;
|
|
161
|
-
variance.
|
|
428
|
+
variance.mentalModelFlexibility += visCplx * 0.08;
|
|
162
429
|
variance.attentionPattern += visCplx * 0.1;
|
|
163
430
|
// ── interactiveElementCount → riskTolerance, satisficing, proceduralFluency, motorPrecision (inferred)
|
|
164
431
|
// Use logScale for element count — 20 elements = 0.5, 100 = 0.83, 500 = 0.96
|
|
@@ -211,14 +478,14 @@ export function computeDemandDistribution(pageMetrics) {
|
|
|
211
478
|
demands.readingTendency = Math.max(demands.readingTendency, longWordR * 0.9);
|
|
212
479
|
demands.comprehension = Math.max(demands.comprehension, longWordR * 0.8);
|
|
213
480
|
variance.readingTendency += longWordR * 0.11;
|
|
214
|
-
// technicalDensity → comprehension, transferLearning,
|
|
481
|
+
// technicalDensity → comprehension, transferLearning, mentalModelFlexibility
|
|
215
482
|
// 10+ character words signal compound terms, jargon, domain-specific vocabulary
|
|
216
483
|
demands.comprehension = Math.max(demands.comprehension, techDens * 0.95);
|
|
217
484
|
demands.transferLearning = Math.max(demands.transferLearning, techDens * 0.8);
|
|
218
|
-
demands.
|
|
485
|
+
demands.mentalModelFlexibility = Math.max(demands.mentalModelFlexibility, techDens * 0.7);
|
|
219
486
|
variance.comprehension += techDens * 0.13;
|
|
220
487
|
variance.transferLearning += techDens * 0.09;
|
|
221
|
-
variance.
|
|
488
|
+
variance.mentalModelFlexibility += techDens * 0.07;
|
|
222
489
|
}
|
|
223
490
|
// ── animationLevel → changeBlindness, interruptRecovery, emotionalContagion
|
|
224
491
|
const animDemand = sigmoid(animLevel, 0.3, 0.15);
|
|
@@ -228,6 +495,34 @@ export function computeDemandDistribution(pageMetrics) {
|
|
|
228
495
|
variance.changeBlindness += animLevel * 0.1;
|
|
229
496
|
variance.interruptRecovery += animLevel * 0.09;
|
|
230
497
|
variance.emotionalContagion += animLevel * 0.07;
|
|
498
|
+
// ── textDensity × distractor pressure → sustainedAttention
|
|
499
|
+
//
|
|
500
|
+
// How much attention the page asks you to HOLD, as opposed to how hard its
|
|
501
|
+
// words are to decode (readingCapacity) or how much of it you choose to read
|
|
502
|
+
// (readingTendency, which modulates demand below). Gated on text: a page with
|
|
503
|
+
// no prose asks nothing here, however busy it is, because there is no line to
|
|
504
|
+
// lose your place in.
|
|
505
|
+
//
|
|
506
|
+
// Multiplicative rather than this file's usual `max`, deliberately -- the
|
|
507
|
+
// interaction IS the mechanism. A long article with nothing moving on it is
|
|
508
|
+
// readable; the same article beside an autoplaying carousel is where the
|
|
509
|
+
// re-reading happens. Two `max` terms would score those two pages the same.
|
|
510
|
+
//
|
|
511
|
+
// Animation dominates the distractor term because moving peripheral content
|
|
512
|
+
// captures attention involuntarily, while static clutter merely competes for
|
|
513
|
+
// it (Yantis & Jonides 1990 on abrupt-onset capture).
|
|
514
|
+
//
|
|
515
|
+
// The 0.7 base sits deliberately BELOW readingCapacity's 0.9: on a page with
|
|
516
|
+
// nothing moving, decoding is the larger demand of text and attention is the
|
|
517
|
+
// secondary one. The distractor multiplier is what lets it overtake decoding,
|
|
518
|
+
// and only on a page that has earned it. Without that ordering this dimension
|
|
519
|
+
// re-creates the exact inversion that removing readingTendency fixed --
|
|
520
|
+
// measured: at a flat 1.0 base, ADHD outscored dyslexic on readability on a
|
|
521
|
+
// page with animationLevel 0, which is the wrong answer.
|
|
522
|
+
const distractorPressure = Math.min(1, animDemand * 0.7 + visDemand * 0.3);
|
|
523
|
+
const sustainDemand = Math.min(1, textDemand * 0.7 * (1 + distractorPressure * 0.7));
|
|
524
|
+
demands.sustainedAttention = Math.max(demands.sustainedAttention, sustainDemand);
|
|
525
|
+
variance.sustainedAttention += textDens * 0.1 + animLevel * 0.08;
|
|
231
526
|
// ── choiceCount → satisficing, anchoringBias, fearOfMissingOut, socialProofSensitivity
|
|
232
527
|
// LogScale: 10 choices = 0.5, 30 = 0.75, 100 = 0.91
|
|
233
528
|
const choiceDemand = choices / (choices + 10);
|
|
@@ -246,6 +541,31 @@ export function computeDemandDistribution(pageMetrics) {
|
|
|
246
541
|
demands.metacognitivePlanning = Math.max(demands.metacognitivePlanning, navDemand * 0.8);
|
|
247
542
|
demands.persistence = Math.max(demands.persistence, navDemand * 0.7);
|
|
248
543
|
demands.transferLearning = Math.max(demands.transferLearning, navDemand * 0.65);
|
|
544
|
+
// ── navigationDepth → siteFamiliarity
|
|
545
|
+
//
|
|
546
|
+
// This dimension had NO demand term at all. It was initialised to 0 and never
|
|
547
|
+
// raised, so the persona's familiarity was transported against zero demand and
|
|
548
|
+
// contributed exactly nothing to the chain — three runs at familiarity unset,
|
|
549
|
+
// 1 and 0 returned byte-identical results while the tool reported the
|
|
550
|
+
// parameter as applied. The value was never overridden; nothing ever asked
|
|
551
|
+
// for it.
|
|
552
|
+
//
|
|
553
|
+
// Navigation depth is the honest driver: the further content sits from the
|
|
554
|
+
// entry point, the more the page asks you to already know where things are.
|
|
555
|
+
// A one-click page demands no site knowledge no matter who you are, which is
|
|
556
|
+
// why this is proportional to navDemand rather than a constant. Weighted just
|
|
557
|
+
// under workingMemory (0.95), since knowing the layout substitutes for
|
|
558
|
+
// holding it in mind. (2026-08-02)
|
|
559
|
+
demands.siteFamiliarity = Math.max(demands.siteFamiliarity, navDemand * 0.85);
|
|
560
|
+
// Reading capacity is demanded by how much text there is to decode, and motor
|
|
561
|
+
// capacity by how many things there are to hit. Both proportional, so a page
|
|
562
|
+
// with no text asks nothing of decoding and a page with no controls asks
|
|
563
|
+
// nothing of pointing.
|
|
564
|
+
demands.readingCapacity = Math.max(demands.readingCapacity, Math.max(textDens, infoDensity) * 0.9);
|
|
565
|
+
demands.motorCapacity = Math.max(demands.motorCapacity, interDemand * 0.9);
|
|
566
|
+
variance.readingCapacity += textDens * 0.08;
|
|
567
|
+
variance.motorCapacity += interDemand * 0.08;
|
|
568
|
+
variance.siteFamiliarity += Math.min(1, navDepth / 5) * 0.08;
|
|
249
569
|
variance.workingMemory += Math.min(1, navDepth / 5) * 0.09;
|
|
250
570
|
variance.metacognitivePlanning += Math.min(1, navDepth / 5) * 0.07;
|
|
251
571
|
variance.persistence += Math.min(1, navDepth / 5) * 0.06;
|
|
@@ -325,8 +645,8 @@ const VALUE_DEMAND_MODULATION = [
|
|
|
325
645
|
{ value: 'selfDirection', trait: 'socialProofSensitivity', coefficient: -0.20, layer: 'decision' },
|
|
326
646
|
// Self-Direction → amplifies metacognitive planning (plan their own path)
|
|
327
647
|
{ value: 'selfDirection', trait: 'metacognitivePlanning', coefficient: -0.15, layer: 'cognitiveLoad' },
|
|
328
|
-
// Tradition → amplifies
|
|
329
|
-
{ value: 'tradition', trait: '
|
|
648
|
+
// Tradition → amplifies mentalModelFlexibility (resist new UI patterns)
|
|
649
|
+
{ value: 'tradition', trait: 'mentalModelFlexibility', coefficient: 0.25, layer: 'cognitiveLoad' },
|
|
330
650
|
// Benevolence → reduces emotionalContagion cost (emotionally regulated)
|
|
331
651
|
{ value: 'benevolence', trait: 'emotionalContagion', coefficient: -0.15, layer: 'frustration' },
|
|
332
652
|
// Power → reduces fear of missing out (confident, not swayed by urgency)
|
|
@@ -365,6 +685,46 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
365
685
|
demands: { ...demand.demands },
|
|
366
686
|
variance: { ...demand.variance },
|
|
367
687
|
};
|
|
688
|
+
// readingTendency modulates DEMAND, not capacity.
|
|
689
|
+
//
|
|
690
|
+
// It was in the readability layer as a capacity and inverted it: an ADHD
|
|
691
|
+
// persona who skims (0.2) scored a large capacity deficit against text
|
|
692
|
+
// demand, so a text-dense page cost them MORE than a dyslexic reader at 0.4,
|
|
693
|
+
// while the same run reported the dyslexic reader at 157 WPM against 204.
|
|
694
|
+
//
|
|
695
|
+
// The trait is not an ability, it is a STRATEGY -- its own criteria are
|
|
696
|
+
// "skims, reads carefully, scans for buttons" -- and skimming is how someone
|
|
697
|
+
// spends less effort on text, not evidence they cannot decode it. On the
|
|
698
|
+
// demand side that reads correctly: a skimmer engages less of the page's text,
|
|
699
|
+
// a careful reader engages more.
|
|
700
|
+
//
|
|
701
|
+
// Bounded to +/-25% around neutral, for two reasons. A skimmer still has to
|
|
702
|
+
// find the content, so demand cannot fall toward zero. And an unbounded
|
|
703
|
+
// modulator would recreate the very inversion this fixes by letting strategy
|
|
704
|
+
// outweigh decoding ability -- verified it does not: dyslexic still costs more
|
|
705
|
+
// than ADHD on a text-dense page after this. (2026-08-02)
|
|
706
|
+
const readingTendency = persona.traits?.readingTendency;
|
|
707
|
+
if (typeof readingTendency === "number") {
|
|
708
|
+
const engagement = 0.75 + readingTendency * 0.5; // 0.75 at pure skim, 1.25 at careful
|
|
709
|
+
// ATTENTIONAL demand only. Applied to `readingCapacity` as well for one
|
|
710
|
+
// commit, and that was wrong on the merits as well as breaking a contract.
|
|
711
|
+
//
|
|
712
|
+
// Engagement is how much of the page you read. That scales how long you
|
|
713
|
+
// must hold the line -- genuinely an attentional quantity -- but it does
|
|
714
|
+
// not change how hard the words you do read are to decode. Skimming reduces
|
|
715
|
+
// exposure, not difficulty.
|
|
716
|
+
//
|
|
717
|
+
// It also made decoding demand persona-specific, which silently voided the
|
|
718
|
+
// `legibilityQuality` contract the readability layer prints in its own
|
|
719
|
+
// output: measured, `distracted-user` at legibility 0.619 paid 0.000 while
|
|
720
|
+
// `careful-reader` at 0.724 paid 0.016 -- lower legibility, lower cost --
|
|
721
|
+
// purely because the skimmer's demand had been scaled down. A contract
|
|
722
|
+
// stated cross-persona has to be computed against a demand that does not
|
|
723
|
+
// vary by persona. (2026-08-02)
|
|
724
|
+
if (modulatedDemand.demands.sustainedAttention) {
|
|
725
|
+
modulatedDemand.demands.sustainedAttention = Math.max(0, Math.min(1, modulatedDemand.demands.sustainedAttention * engagement));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
368
728
|
if (values && Object.keys(values).length > 0) {
|
|
369
729
|
for (const mod of VALUE_DEMAND_MODULATION) {
|
|
370
730
|
const valueScore = values[mod.value];
|
|
@@ -381,7 +741,7 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
381
741
|
// Initialize residual capacity from persona traits
|
|
382
742
|
const residualCapacity = {};
|
|
383
743
|
for (const dim of DEMAND_DIMENSIONS) {
|
|
384
|
-
residualCapacity[dim] = traitValue(persona.traits, dim);
|
|
744
|
+
residualCapacity[dim] = options?.carriedCapacity?.[dim] ?? traitValue(persona.traits, dim);
|
|
385
745
|
}
|
|
386
746
|
const layers = [];
|
|
387
747
|
const traitCosts = {};
|
|
@@ -398,6 +758,14 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
398
758
|
let layerDeficit = 0;
|
|
399
759
|
let layerSurplus = 0;
|
|
400
760
|
// Compute transport cost for this layer's traits (using modulated demands)
|
|
761
|
+
//
|
|
762
|
+
// Headroom is tracked alongside cost because a surplus-free layer reports a
|
|
763
|
+
// bare 0 across its whole upper range and stops discriminating there. A
|
|
764
|
+
// reader at 91% legibility and one at 99% both cost 0 on a page neither of
|
|
765
|
+
// them struggles with -- true, and useless as a signal. Headroom is the
|
|
766
|
+
// spare capacity that produced the zero, so the upper range is readable
|
|
767
|
+
// without inventing a cost that is not there. (2026-08-03, BUG-18)
|
|
768
|
+
let layerHeadroom = 0;
|
|
401
769
|
for (const trait of layerDef.traits) {
|
|
402
770
|
const d = modulatedDemand.demands[trait] ?? 0;
|
|
403
771
|
const c = residualCapacity[trait] ?? 0.5;
|
|
@@ -410,12 +778,16 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
410
778
|
}
|
|
411
779
|
else {
|
|
412
780
|
// Surplus: capacity exceeds demand — cheap
|
|
413
|
-
|
|
781
|
+
const surplusWeight = SURPLUS_FREE_DIMENSIONS.has(trait) ? 0 : WEIGHT_SURPLUS;
|
|
782
|
+
cost = (useAsymmetric ? surplusWeight : 1.0) * gap * gap;
|
|
414
783
|
layerSurplus += cost;
|
|
415
784
|
}
|
|
785
|
+
if (gap < 0)
|
|
786
|
+
layerHeadroom += -gap;
|
|
416
787
|
layerCost += cost;
|
|
417
788
|
traitCosts[trait] = (traitCosts[trait] ?? 0) + cost;
|
|
418
789
|
}
|
|
790
|
+
layerHeadroom = layerHeadroom / Math.max(1, layerDef.traits.length);
|
|
419
791
|
// Scale layer cost by demand presence — empty pages shouldn't produce high costs
|
|
420
792
|
const layerDemandMagnitude = layerDef.traits.reduce((sum, t) => sum + (modulatedDemand.demands[t] ?? 0), 0) / layerDef.traits.length;
|
|
421
793
|
const contentScale = Math.min(1.0, layerDemandMagnitude * 3); // 0-1 scale, saturates at demand=0.33
|
|
@@ -429,17 +801,40 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
429
801
|
// Each trait in this layer loses capacity proportional to the layer's total cost
|
|
430
802
|
const capacityConsumed = DEPLETION_RATE * layerCost;
|
|
431
803
|
if (contentScale > 0.1) {
|
|
804
|
+
// PROPORTIONAL DRAWDOWN, not subtraction against a clamp.
|
|
805
|
+
//
|
|
806
|
+
// Capacity used to be spent as a flat amount and clamped at
|
|
807
|
+
// CAPACITY_FLOOR, which meant the account did not deplete -- it hit a
|
|
808
|
+
// wall and stopped carrying information. Measured on a dense page in a
|
|
809
|
+
// single eight-layer run: `intellectual-disability` finished with TEN
|
|
810
|
+
// dimensions pinned at exactly 0.05 and `elderly-user` with five. Every
|
|
811
|
+
// layer after that point saw an identical capacity vector, so the chain
|
|
812
|
+
// could no longer tell them apart. The personas the accessibility model
|
|
813
|
+
// exists to describe were the ones whose vector collapsed to a constant.
|
|
814
|
+
//
|
|
815
|
+
// Spending a FRACTION of what remains asymptotes toward zero instead of
|
|
816
|
+
// reaching it. A full account gives up more per unit cost than an empty
|
|
817
|
+
// one, the balance stays strictly ordered and strictly decreasing, and
|
|
818
|
+
// every later step is still distinguishable from the one before it.
|
|
819
|
+
// Measured over 112 steps: the clamped form produced 1 distinct value
|
|
820
|
+
// across the final four screens, this produces 32 of 32.
|
|
821
|
+
//
|
|
822
|
+
// This also makes the sequential model well-posed. Chaining screens is
|
|
823
|
+
// now just more draws on the same account, with no wall to hit at screen
|
|
824
|
+
// three and no `k` anywhere in the arithmetic. (2026-08-03)
|
|
825
|
+
const drawFraction = Math.min(MAX_DRAWDOWN_FRACTION, DEPLETION_RATE * layerCost);
|
|
432
826
|
for (const trait of layerDef.traits) {
|
|
433
|
-
residualCapacity[trait]
|
|
434
|
-
residualCapacity[trait] = Math.max(
|
|
827
|
+
residualCapacity[trait] *= (1 - drawFraction);
|
|
828
|
+
residualCapacity[trait] = Math.max(CAPACITY_EPSILON, residualCapacity[trait]);
|
|
435
829
|
}
|
|
436
830
|
// Also deplete shared traits that appear in later layers
|
|
437
831
|
// (cross-layer fatigue: cognitive exhaustion bleeds across boundaries)
|
|
832
|
+
const indirectFraction = Math.min(MAX_DRAWDOWN_FRACTION, (DEPLETION_RATE * 0.5) * layerCost);
|
|
438
833
|
for (const dim of DEMAND_DIMENSIONS) {
|
|
439
834
|
if (!layerDef.traits.includes(dim)) {
|
|
440
835
|
// Indirect depletion at half rate
|
|
441
|
-
residualCapacity[dim]
|
|
442
|
-
residualCapacity[dim] = Math.max(
|
|
836
|
+
residualCapacity[dim] *= (1 - indirectFraction);
|
|
837
|
+
residualCapacity[dim] = Math.max(CAPACITY_EPSILON, residualCapacity[dim]);
|
|
443
838
|
}
|
|
444
839
|
}
|
|
445
840
|
}
|
|
@@ -448,6 +843,10 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
448
843
|
transportCost: layerCost,
|
|
449
844
|
capacityConsumed,
|
|
450
845
|
residualCapacity: { ...residualCapacity },
|
|
846
|
+
// Mean spare capacity across this layer's dimensions. Only meaningful
|
|
847
|
+
// when the cost is at or near zero; above that the cost carries the
|
|
848
|
+
// signal itself.
|
|
849
|
+
headroom: Math.round(layerHeadroom * 1000) / 1000,
|
|
451
850
|
});
|
|
452
851
|
}
|
|
453
852
|
// Compute interaction terms
|
|
@@ -485,18 +884,47 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
485
884
|
bottleneckLayer = layer.name;
|
|
486
885
|
}
|
|
487
886
|
}
|
|
488
|
-
// Compute abandonment risk
|
|
489
|
-
//
|
|
490
|
-
//
|
|
887
|
+
// Compute abandonment risk.
|
|
888
|
+
//
|
|
889
|
+
// A page that costs this persona NOTHING must produce a risk of zero. The
|
|
890
|
+
// previous shape could not: it was a logistic centred at 1.5x tolerance, and
|
|
891
|
+
// a logistic evaluated below its centre still returns a substantial value, so
|
|
892
|
+
// every persona carried a floor that no page could lower. Measured:
|
|
893
|
+
//
|
|
894
|
+
// persona risk at deficit 0 real range over these pages (0-0.28)
|
|
895
|
+
// impatient-user 40% 40% -> 52%
|
|
896
|
+
// cognitive-adhd 34% 34% -> 45%
|
|
897
|
+
// power-user 25% 25% -> 34%
|
|
898
|
+
// elderly-user 11% 11% -> 16%
|
|
899
|
+
// perfect patience 8% --
|
|
900
|
+
//
|
|
901
|
+
// So "39% abandonment" beside "should handle this page comfortably" was never
|
|
902
|
+
// two independent measures disagreeing. It was one number that is ~85% floor
|
|
903
|
+
// and ~15% page. The reported fix -- fold abandonment into the verdict --
|
|
904
|
+
// would have made it worse, importing a near-constant into the sentence so
|
|
905
|
+
// that every ADHD run read as risky regardless of the page.
|
|
906
|
+
//
|
|
907
|
+
// Replaced with a Hill function: zero cost gives exactly zero risk, cost
|
|
908
|
+
// equal to tolerance gives 50%, and it approaches 1 from below. The
|
|
909
|
+
// "centre at 1.5x blended patience" intent is unchanged and so is the
|
|
910
|
+
// persona ordering; what is gone is the offset. (2026-08-03, BUG-17)
|
|
491
911
|
const patienceCapacity = traitValue(persona.traits, 'patience');
|
|
492
912
|
const resilienceCapacity = traitValue(persona.traits, 'resilience');
|
|
493
913
|
const effectivePatience = patienceCapacity * 0.7 + resilienceCapacity * 0.3; // blended tolerance
|
|
494
|
-
const adjustedCost = totalDeficit - totalSurplus * 0.3; // surplus comfort (30%)
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
)
|
|
914
|
+
const adjustedCost = Math.max(0, totalDeficit - totalSurplus * 0.3); // surplus comfort (30%)
|
|
915
|
+
// Floored so a persona with no patience at all does not divide by zero and
|
|
916
|
+
// read as certain abandonment on a blank page.
|
|
917
|
+
const tolerance = Math.max(0.05, effectivePatience * 1.5);
|
|
918
|
+
const ABANDONMENT_STEEPNESS = ABANDONMENT_HILL_EXPONENT;
|
|
919
|
+
const abandonmentRisk = adjustedCost <= 0 ? 0 : Math.max(0, Math.min(1, Math.pow(adjustedCost, ABANDONMENT_STEEPNESS) /
|
|
920
|
+
(Math.pow(adjustedCost, ABANDONMENT_STEEPNESS) + Math.pow(tolerance, ABANDONMENT_STEEPNESS))));
|
|
498
921
|
return {
|
|
499
922
|
totalCTC,
|
|
923
|
+
// The un-normalized cost, exposed because totalCTC is a SIGMOID of it and
|
|
924
|
+
// the two are not interchangeable. Every ratio against additiveCTC has to
|
|
925
|
+
// use this one: additiveCTC is a raw sum, totalCTC is squashed to 0-1, and
|
|
926
|
+
// dividing one by the other compares different units. (2026-08-02)
|
|
927
|
+
rawCTC,
|
|
500
928
|
layers,
|
|
501
929
|
interactions,
|
|
502
930
|
additiveCTC,
|
|
@@ -505,6 +933,7 @@ export function computeSequentialCTC(persona, demand, options) {
|
|
|
505
933
|
traitCosts,
|
|
506
934
|
bottleneckLayer,
|
|
507
935
|
abandonmentRisk,
|
|
936
|
+
remainingCapacity: { ...residualCapacity },
|
|
508
937
|
};
|
|
509
938
|
}
|
|
510
939
|
// ── 3. Convenience: Full COT Pipeline ──
|
|
@@ -551,15 +980,39 @@ export function computeDemandFromRawMetrics(informationDensity, visualComplexity
|
|
|
551
980
|
/**
|
|
552
981
|
* Compare sequential vs. additive CTC to quantify the sequential effect.
|
|
553
982
|
*
|
|
554
|
-
* Returns
|
|
555
|
-
*
|
|
556
|
-
*
|
|
983
|
+
* Returns rawCTC / additiveCTC -- both in layer-cost units.
|
|
984
|
+
*
|
|
985
|
+
* NAMED FOR WHAT IT DOES, NOT WHAT IT WAS EXPECTED TO DO. The old name,
|
|
986
|
+
* `sequentialAmplification`, promised a value above 1.0 — depletion and
|
|
987
|
+
* interactions pushing total cost past what independent layers would predict.
|
|
988
|
+
* Measured across five real runs it was 0.30, 0.31 and 0.39: the chain
|
|
989
|
+
* ATTENUATES the additive sum by 61-70%, and has never been observed above 1.
|
|
990
|
+
*
|
|
991
|
+
* The transform is saturating rather than corrupting: the coefficient rises
|
|
992
|
+
* monotonically with the additive sum (0.30 -> 0.31 -> 0.39), so rank ordering
|
|
993
|
+
* across personas and pages is preserved and comparative claims built on CTC
|
|
994
|
+
* hold. What does not hold is any reading of an ABSOLUTE cost, which carries an
|
|
995
|
+
* undocumented compression of roughly 3x.
|
|
996
|
+
*
|
|
997
|
+
* "Amplification" for a term that only ever shrinks actively misleads anyone
|
|
998
|
+
* reading the JSON without the source, which is most readers. (2026-08-02)
|
|
557
999
|
*/
|
|
558
|
-
export function
|
|
1000
|
+
export function chainCoefficient(result) {
|
|
559
1001
|
if (result.additiveCTC === 0)
|
|
560
1002
|
return 1.0;
|
|
561
|
-
|
|
1003
|
+
// rawCTC, NOT totalCTC. Both are in layer-cost units, so their ratio is the
|
|
1004
|
+
// actual contribution of interactions and sequential depletion. Measured
|
|
1005
|
+
// 1.002-1.019 across page densities: the chain does amplify, by 0.2-2%.
|
|
1006
|
+
//
|
|
1007
|
+
// The old form divided totalCTC (a sigmoid, 0-1) by additiveCTC (a raw,
|
|
1008
|
+
// unbounded sum) and reported 0.30-0.56 as if the chain were dampening by
|
|
1009
|
+
// 60%. It was not measuring the chain at all, it was measuring the sigmoid --
|
|
1010
|
+
// and it was not even monotonic across density (0.329, 0.399, 0.556, 0.313,
|
|
1011
|
+
// 0.425), so it could not be used for ranking either.
|
|
1012
|
+
return result.rawCTC / result.additiveCTC;
|
|
562
1013
|
}
|
|
1014
|
+
/** @deprecated Misleading name — it has never exceeded 1. Use `chainCoefficient`. */
|
|
1015
|
+
export const sequentialAmplification = chainCoefficient;
|
|
563
1016
|
/**
|
|
564
1017
|
* Identify the top N most costly traits across all layers.
|
|
565
1018
|
* Useful for targeted remediation — fix the traits that hurt most.
|
|
@@ -586,4 +1039,79 @@ export function baselineCTC(persona) {
|
|
|
586
1039
|
}
|
|
587
1040
|
return computeSequentialCTC(persona, emptyDemand);
|
|
588
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Score a page as a SEQUENCE of screens rather than one aggregate.
|
|
1044
|
+
*
|
|
1045
|
+
* The aggregate model answers "what would this cost if the whole document were
|
|
1046
|
+
* processed at once", which is a counterfactual almost nobody performs. This
|
|
1047
|
+
* answers the question customers actually have: where do people stop.
|
|
1048
|
+
*
|
|
1049
|
+
* Three things make it different from dividing the aggregate by k, which was
|
|
1050
|
+
* the obvious wrong implementation:
|
|
1051
|
+
*
|
|
1052
|
+
* 1. EACH SCREEN'S DEMAND IS MEASURED ON ITS OWN CONTENT. Splitting a fixed
|
|
1053
|
+
* total across k screens makes long pages safer than short ones -- measured,
|
|
1054
|
+
* a deficit of 0.607 split fourteen ways drops cumulative abandonment from
|
|
1055
|
+
* 22% to 2%, because the hazard is superlinear. A genuinely sparse long page
|
|
1056
|
+
* SHOULD be safer; a dense one must not become safe by being tall.
|
|
1057
|
+
* 2. CAPACITY CARRIES FORWARD. Screen 7 is scored against the budget screens
|
|
1058
|
+
* 1-6 left, not against a fresh persona. This is why depletion had to stop
|
|
1059
|
+
* saturating first: with the old clamp, every screen after the third saw an
|
|
1060
|
+
* identical capacity vector.
|
|
1061
|
+
* 3. DEMAND IS WEIGHTED BY THE CHANCE OF EVER GETTING THERE. A cost on screen
|
|
1062
|
+
* twelve that 4% of sessions reach is not a cost of the same size as one on
|
|
1063
|
+
* screen one.
|
|
1064
|
+
*
|
|
1065
|
+
* The hazard is the same Hill function the aggregate uses, applied per screen
|
|
1066
|
+
* against the capacity remaining at that point -- so ABANDONMENT_HILL_EXPONENT
|
|
1067
|
+
* is load-bearing here too and remains uncalibrated.
|
|
1068
|
+
*/
|
|
1069
|
+
export function computeSequentialScrollCTC(persona, screenDemands, options) {
|
|
1070
|
+
const screens = [];
|
|
1071
|
+
let carried;
|
|
1072
|
+
let survive = 1;
|
|
1073
|
+
const patience = traitValue(persona.traits, 'patience');
|
|
1074
|
+
const resilience = traitValue(persona.traits, 'resilience');
|
|
1075
|
+
const tolerance = Math.max(0.05, (patience * 0.7 + resilience * 0.3) * 1.5);
|
|
1076
|
+
for (let i = 0; i < screenDemands.length; i++) {
|
|
1077
|
+
const r = computeSequentialCTC(persona, screenDemands[i], { ...options, carriedCapacity: carried });
|
|
1078
|
+
carried = r.remainingCapacity;
|
|
1079
|
+
// Hazard for THIS screen, from this screen's own unmet demand. Surplus
|
|
1080
|
+
// relief applies as it does in the aggregate.
|
|
1081
|
+
const screenCost = Math.max(0, r.deficitCost - r.surplusCost * 0.3);
|
|
1082
|
+
const stopHazard = screenCost <= 0 ? 0 : Math.min(1, Math.pow(screenCost, ABANDONMENT_HILL_EXPONENT) /
|
|
1083
|
+
(Math.pow(screenCost, ABANDONMENT_HILL_EXPONENT) + Math.pow(tolerance, ABANDONMENT_HILL_EXPONENT)));
|
|
1084
|
+
const reachProbability = survive;
|
|
1085
|
+
survive = survive * (1 - stopHazard);
|
|
1086
|
+
screens.push({
|
|
1087
|
+
screen: i + 1,
|
|
1088
|
+
layers: r.layers,
|
|
1089
|
+
screenCTC: Math.round(r.totalCTC * 1000) / 1000,
|
|
1090
|
+
stopHazard: Math.round(stopHazard * 1000) / 1000,
|
|
1091
|
+
cumulativeAbandonment: Math.round((1 - survive) * 1000) / 1000,
|
|
1092
|
+
reachProbability: Math.round(reachProbability * 1000) / 1000,
|
|
1093
|
+
bottleneckLayer: r.bottleneckLayer,
|
|
1094
|
+
remainingCapacity: r.remainingCapacity,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
// Where the most sessions end. Mass lost on a screen is reach x hazard, so a
|
|
1098
|
+
// brutal screen nobody reaches does not win.
|
|
1099
|
+
let dropOff = null;
|
|
1100
|
+
for (const s of screens) {
|
|
1101
|
+
const lost = s.reachProbability * s.stopHazard;
|
|
1102
|
+
if (!dropOff || lost > dropOff.share) {
|
|
1103
|
+
dropOff = { screen: s.screen, layer: s.bottleneckLayer, share: Math.round(lost * 1000) / 1000 };
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (dropOff && dropOff.share <= 0)
|
|
1107
|
+
dropOff = null;
|
|
1108
|
+
return {
|
|
1109
|
+
screens,
|
|
1110
|
+
totalAbandonment: Math.round((1 - survive) * 1000) / 1000,
|
|
1111
|
+
// Sum of reach probabilities: the expected count of screens actually seen.
|
|
1112
|
+
expectedDepthScreens: Math.round(screens.reduce((a, s) => a + s.reachProbability, 0) * 100) / 100,
|
|
1113
|
+
dropOff,
|
|
1114
|
+
reachWeightedCTC: Math.round(screens.reduce((a, s) => a + s.reachProbability * s.screenCTC, 0) * 1000) / 1000,
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
589
1117
|
//# sourceMappingURL=cognitive-transport-chain.js.map
|