open-standards/aac/conformance/v1/dwell.json
David Charlot 9cdb252ea7 feat(aac-conformance): C5 scan-rate floor, C6 dwell proposals, C7 language honesty
Conformance for the rules added in SPEC 0.2.0, 236 checks in all:

- C5 (§5.1–§5.2): for the published response-time vectors
  (conformance/v1/scan_rate.json, 10 cases) the rate is at least the
  nearest-rank P95 and ⌊100·M/65⌋, within 300..5000 ms; nothing adapts before
  10 responses or without opt-in; a proposal never applies itself; the 10%
  proposal rule is pinned at both edges.
- C6 (§5.1, §5.3): dwell vectors (conformance/v1/dwell.json, 24 cases) —
  nothing changes without opt-in, shorter proposals respect the 300 ms floor,
  both directions are reachable, clean selections alone change nothing, and an
  opted-in engine applies exactly its proposal, once. A restored 30-selection
  ratchet fails.
- C7 (§4.1): stable CoreStatus wire names, a record without a status reads as
  GlossOnly, and only English carries a natively studied core.

C5 and C6 recompute every expected value from the raw inputs with an
independent oracle (cross-checked with exact fractions) before comparing the
engine, so the engine is held to the rule, not to itself. gen-vectors takes an
output directory and regenerates all three vector files byte-for-byte; the
harness takes a vector directory. A missing or unparseable vector is a FAIL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 16:26:17 -04:00

246 lines
6.5 KiB
JSON

{
"cases": [
{
"current_dwell_ms": 800,
"expected_direction": "longer",
"expected_proposal_ms": 897,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "30% undone: one rung longer",
"selections": 20,
"undos": 6
},
{
"current_dwell_ms": 800,
"expected_direction": "longer",
"expected_proposal_ms": 897,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "5 of 24 undone, just over 20%: longer",
"selections": 24,
"undos": 5
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "exactly 20% undone: nothing",
"selections": 20,
"undos": 4
},
{
"current_dwell_ms": 800,
"expected_direction": "shorter",
"expected_proposal_ms": 736,
"glances": 0,
"late_abandons": 8,
"max_dwell_ms": 2000,
"name": "8 of 28 attempts abandoned late, no undos: one rung shorter",
"selections": 20,
"undos": 0
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 5,
"max_dwell_ms": 2000,
"name": "exactly 20% abandoned late: nothing",
"selections": 20,
"undos": 0
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 8,
"max_dwell_ms": 2000,
"name": "late abandons, but 5% undone: nothing",
"selections": 20,
"undos": 1
},
{
"current_dwell_ms": 800,
"expected_direction": "longer",
"expected_proposal_ms": 897,
"glances": 0,
"late_abandons": 8,
"max_dwell_ms": 2000,
"name": "undos and late abandons both high: longer wins",
"selections": 20,
"undos": 6
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 30,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "glances are not abandons: nothing",
"selections": 20,
"undos": 0
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "nine selections: too few for any proposal",
"selections": 9,
"undos": 5
},
{
"current_dwell_ms": 800,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "ninety clean selections: nothing",
"selections": 90,
"undos": 0
},
{
"current_dwell_ms": 2000,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "at the ceiling: nothing longer",
"selections": 20,
"undos": 6
},
{
"current_dwell_ms": 1900,
"expected_direction": "longer",
"expected_proposal_ms": 2000,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "one rung past the ceiling: the ceiling",
"selections": 20,
"undos": 6
},
{
"current_dwell_ms": 320,
"expected_direction": "shorter",
"expected_proposal_ms": 300,
"glances": 0,
"late_abandons": 8,
"max_dwell_ms": 2000,
"name": "one rung below the proposal floor: the floor",
"selections": 20,
"undos": 0
},
{
"current_dwell_ms": 300,
"expected_direction": "none",
"expected_proposal_ms": null,
"glances": 0,
"late_abandons": 8,
"max_dwell_ms": 2000,
"name": "at the proposal floor: nothing shorter",
"selections": 20,
"undos": 0
},
{
"current_dwell_ms": 200,
"expected_direction": "longer",
"expected_proposal_ms": 235,
"glances": 0,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "the person chose 200 ms and often undoes: one rung up, still under 300",
"selections": 20,
"undos": 6
}
],
"opt_in": [
{
"current_dwell_ms": 800,
"expected_dwell_ms": 897,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "undos push one rung longer, once",
"selections": 20,
"undos_after_opt_in": 6
},
{
"current_dwell_ms": 800,
"expected_dwell_ms": 736,
"late_abandons": 12,
"max_dwell_ms": 2000,
"name": "late abandons pull one rung shorter at the first undo, once",
"selections": 40,
"undos_after_opt_in": 1
},
{
"current_dwell_ms": 800,
"expected_dwell_ms": 800,
"late_abandons": 0,
"max_dwell_ms": 2000,
"name": "ninety clean selections change nothing",
"selections": 90,
"undos_after_opt_in": 0
}
],
"rule": {
"absolute_floor_ms": 150,
"adaptation_default": false,
"ladder": "DT(X) = 300 * e^(X / 12) - 150 ms; rung X = round(12 * ln((dwell + 150) / 300)); a step is round(DT(X +/- 1))",
"late_abandon": "gaze leaves a target after at least half the dwell time",
"longer": "5 * undos > selections: one rung up, capped at max_dwell_ms; nothing at the ceiling",
"min_events": 10,
"proposal_floor_ms": 300,
"set_dwell": "clamp(requested, absolute_floor_ms, max_dwell_ms)",
"shorter": "20 * undos < selections and 5 * late_abandons > selections + late_abandons: one rung down, never below proposal_floor_ms; nothing at or below it",
"window": "counts restart whenever the dwell changes; nothing is proposed before min_events selections"
},
"set_dwell": [
{
"expected_dwell_ms": 150,
"max_dwell_ms": 2000,
"requested_ms": 50
},
{
"expected_dwell_ms": 150,
"max_dwell_ms": 2000,
"requested_ms": 149
},
{
"expected_dwell_ms": 150,
"max_dwell_ms": 2000,
"requested_ms": 150
},
{
"expected_dwell_ms": 1234,
"max_dwell_ms": 2000,
"requested_ms": 1234
},
{
"expected_dwell_ms": 2000,
"max_dwell_ms": 2000,
"requested_ms": 2000
},
{
"expected_dwell_ms": 2000,
"max_dwell_ms": 2000,
"requested_ms": 10000
}
],
"spec": "SPEC.md §5.1, §5.3",
"standard": "aac",
"vector": "dwell",
"version": "0.2.0"
}