Twinned from https://github.com/cfsh/ninefold/issues/751 by tools/beads/import_github.py's reconcile pass.
Suggestions from ninefold-reviewer's review of #749 (c66d80f). All advisory, none gating — #749 converged.
1. A step can state two step kinds, and only one pair is linted
VisualProbe.Load is a single else if chain (shared/VisualProbe.cs:300-540) whose closing else fires only when no key matched. A step carrying two *known* kinds therefore runs as whichever comes first in the chain, and the rest are dropped with no error.
test_a_step_never_states_its_wait_in_BOTH_units closes one instance of that class — the wait/wait_ms pair. The general shape is unlinted: {"action": "ui_right", "shot": "grid"} silently takes no screenshot, and wait sits at the head of the chain so it beats every stimulus key.
That last fact reaches the new budget lint. test_a_budgeted_wait_measures_from_a_cheap_stimulus builds prev from every key of the preceding step and tests prev & CHEAP_STIMULI. For {"joydown": "a", "wait": 30} the intersection is {joydown}, so it passes — while at runtime the preceding step is a 30-frame wait charged straight to the budget, which is exactly the placement the lint exists to reject.
Suggested close: generalise lint 1 from the pair to a step names at most one step kind (len(keys & KINDS) <= 1). That subsumes the wait/wait_ms case and makes prev & CHEAP_STIMULI an exact test of the effective kind rather than an approximate one. Neither shape is in the corpus today — the same standing as the three lints themselves.
2. set_dial does set _waitLeft
The CHEAP_STIMULI comment block in tools/tests/test_probe_corpus.py says set_dial *"sets no _waitLeft at all"*. It sets _waitLeft = 1 — shared/VisualProbe.cs:904, same as joydown. The classification is right and the block's own header (*"one frame or fewer"*) stays true; only the per-kind annotation is wrong, in the block whose authority is that it was derived from measured cost.
3. Docstring narrower than the check
test_the_cheap_stimulus_list_covers_the_one_frame_steps_in_use's docstring says it asserts against *"every step kind that appears immediately before an existing wait"*. The code classifies every step key anywhere in the corpus, which is the better check — the docstring describes a narrower one.
4. Two writings disagree on a first-step budget
The lint asserts i > 0 with *"a budgeted wait cannot be first"*; VisualProbe.cs:678-681 deliberately accommodates that case. The lint is defensible — steps[0]'s runtime predecessor is the root scene step, 2 frames plus a scene load — but if the lint is the rule, the VisualProbe comment should say its fallback is a safety net rather than a supported shape.
None.
No comments.