open-standards/aac/conformance/v1/scan_rate.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

285 lines
5.2 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cases": [
{
"current_rate_ms": 1000,
"expected_proposal_ms": null,
"expected_rate_ms": 1076,
"floor_ms": 1076,
"mean_ms": 700,
"name": "steady presses, mean-dominated, within 10% of the default",
"p95_ms": 770,
"response_times_ms": [
620,
680,
700,
710,
690,
740,
660,
730,
700,
770,
650,
750
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 2000,
"expected_rate_ms": 2000,
"floor_ms": 2000,
"mean_ms": 830,
"name": "one slow press, P95-dominated",
"p95_ms": 2000,
"response_times_ms": [
700,
700,
700,
700,
700,
700,
700,
700,
700,
2000
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 1500,
"expected_rate_ms": 1500,
"floor_ms": 1500,
"mean_ms": 605,
"name": "n = 20, the 19th smallest is the P95",
"p95_ms": 1500,
"response_times_ms": [
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
500,
1500,
1600
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 300,
"expected_rate_ms": 300,
"floor_ms": 230,
"mean_ms": 150,
"name": "fast presses, clamped to the lower bound",
"p95_ms": 150,
"response_times_ms": [
150,
150,
150,
150,
150,
150,
150,
150,
150,
150
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 5000,
"expected_rate_ms": 5000,
"floor_ms": 6153,
"mean_ms": 4000,
"name": "slow presses, clamped to the upper bound",
"p95_ms": 4000,
"response_times_ms": [
4000,
4000,
4000,
4000,
4000,
4000,
4000,
4000,
4000,
4000
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 692,
"expected_rate_ms": 692,
"floor_ms": 692,
"mean_ms": 450,
"name": "faster than the default, within bounds",
"p95_ms": 470,
"response_times_ms": [
430,
450,
470,
440,
460,
450,
455,
445,
450,
450
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": 1100,
"expected_rate_ms": 1100,
"floor_ms": 1100,
"mean_ms": 715,
"name": "exactly 10% slower is proposed",
"p95_ms": 715,
"response_times_ms": [
715,
715,
715,
715,
715,
715,
715,
715,
715,
715
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": null,
"expected_rate_ms": 1098,
"floor_ms": 1098,
"mean_ms": 714,
"name": "just under 10% slower is not",
"p95_ms": 714,
"response_times_ms": [
714,
714,
714,
714,
714,
714,
714,
714,
714,
714
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": null,
"expected_rate_ms": 2000,
"floor_ms": 2000,
"mean_ms": 1300,
"name": "nine presses: not enough to change anything",
"p95_ms": 1300,
"response_times_ms": [
1300,
1300,
1300,
1300,
1300,
1300,
1300,
1300,
1300
]
},
{
"current_rate_ms": 1000,
"expected_proposal_ms": null,
"expected_rate_ms": 960,
"floor_ms": 960,
"mean_ms": 510,
"name": "a full 50-response window",
"p95_ms": 960,
"response_times_ms": [
20,
40,
60,
80,
100,
120,
140,
160,
180,
200,
220,
240,
260,
280,
300,
320,
340,
360,
380,
400,
420,
440,
460,
480,
500,
520,
540,
560,
580,
600,
620,
640,
660,
680,
700,
720,
740,
760,
780,
800,
820,
840,
860,
880,
900,
920,
940,
960,
980,
1000
]
}
],
"rule": {
"adaptive_rate_default": false,
"bounds_ms": [
300,
5000
],
"default_rate_ms": 1000,
"floor": "F = max(floor(100 * M / 65), P95)",
"mean": "M = floor(sum / n) over the most recent responses, at most `window`",
"min_samples": 10,
"p95": "the ceil(0.95 * n)-th smallest response (nearest rank)",
"proposal": "the rate, once n >= min_samples and |rate - current| * 100 >= current * proposal_min_change_pct",
"proposal_min_change_pct": 10,
"rate": "clamp(F, bounds_ms[0], bounds_ms[1])",
"window": 50
},
"spec": "SPEC.md §5.1–§5.2",
"standard": "aac",
"vector": "scan_rate",
"version": "0.2.0"
}