research: decision models on text boxes — measured, set aside, kept (ADR 0028) #176
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "research/decision-bbox"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A research record beside the cleaning lab (
docs/research/decision-bbox/) and the ADR it informs. The question: can a vision decision model (OpenRouter'sdecisionsoutput modality: typed questions in, probabilities out, no text, no tools) walk a rough text-region box onto the lettering, one edge step at a time?Conclusion (ADR 0028): not adopted. On their own, decision models do not do the box task, and a loop around them is not worth its place in annotation. The record and its sandbox stay so the work can be picked up again.
Findings
The evidence is in
REPORT.md; the visual study is inreport/index.html. In all: 88 live runs, about 68,000 calls, $3.37.perplexity/pplx-decider-v1.1-27b: AUC 0.98 for "does text cross this line?".What's in
docs/adr/0028-…: the decision, its reasons, what stays, and when to revisit.REPORT.md: the evidence, in the cleaning lab's shape.report/: a committed snapshot of the visual study (2.8 MB of crops, as the lab's report keeps).figs.py: draws the report's figures from that snapshot.sandbox/, a .NET project outsideOrihon.slnx):--FitFrom)cases.json(15 picks) andcases-heldout.json(59 held-out), from the cleaning lab's glyph-tight, verified boxes. The scans andruns/stay out of git.af9fd7b→403307d) for its Decisions API. Orihon builds with 0 warnings and passes its suite unchanged against it (Domain 171, UseCases 1486, Integration 1174 + 3 skipped, BlazorAdapter 326). Nothing in Orihon calls the API.Verified
--Only--ProbeOffsetsvalidation5539a64.🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 96.2%
Orihon.Domain - 100%
Orihon.Infrastructure - 97.9%
n
ration
n
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
Orihon.Kernel - 90.9%
Orihon.Server - 94.9%
Orihon.UseCases - 93.4%
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__BalloonWords_7
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__Capital_0
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__CoordinateWords_1
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__JustBefore_4
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__KindWords_5
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__OutlineWords_6
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__PositionalProse_2
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__SideOfTheBox_3
research: a sandbox for walking text boxes with a vision decision modelto research: walking text boxes with a vision decision model — sandbox and first findings🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A decision model that never says a word — probabilities only, no prose to parse~ A whole loop, a trace-rendered report with a round slider and a lightbox, an oracle decider to check the loop without spending a cent, and a held-out set to keep yourself honest. I read every file, ran your oracle myself, regenerated your case files, and probed the corners. This is a lovely little laboratory~ ♪
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
sandbox/Program.cs:9—rest.Where(a => !Directory.Exists(a))silently eats every option whose value is an existing directory. Thereportcommand takes run directories as positional args, so the filter is meant to keep those away from the binder. But the binder receives the whole argv for every command, andrunhas documented options whose values are directories:--Pages(README.md“point--PagesorCLEANLAB_PAGESelsewhere”) and--Runs. Mechanically reproduced on the head SHA:dotnet run -- run --Decider oracle --Pages <existing dir> --Only zh-03-r2→
FileNotFoundException: Cannot read the page --Only/zh-02.jpg; set --Pages.Watch what it did, fufu~ — three failures in one line: the directory value was dropped from argv,
--Pagesthen swallowed the next option's name as its value, and--Only zh-03-r2vanished, so the run quietly started over all 15 cases before dying. With--Onlyabsent, the misparse still redirects every image lookup to a wrong path; with--Runs <dir>it quietly writes runs somewhere unintended. An option that only appears to work when its value happens not to exist yet… that's a bug wearing a lab coat~ ♡Fix: parse positionals by command, not by filesystem probe. Simplest correct shape:
var positional = rest.TakeWhile(a => !a.StartsWith("--")).ToArray()forreport(or just skip the filter forcases/run, which take no positional run dirs), and bindrestuntouched. ProbingDirectory.Existsover argv is unfixable by tweaking the predicate.sandbox/Program.cs:119— a typo'd or stale--Onlyid crashes the whole run with an unhandledArgumentOutOfRangeExceptionafter creating the run directory. Reproduced:run --Decider oracle --Only does-not-exist→ prints…: 0 cases, createsruns/<id>/, thencases[0]throws at line 119 (Wording.Of(o, cases[0])). For a harness whose whole point is being pointed at case ids (--Only zh-03-r2,zh-08-r7), one stale id is a guaranteed late night. Fix: after filtering,if (cases.Count == 0) { Console.Error.WriteLine("No cases match --Only."); return 2; }(and skip writing trace/report for an empty run).CI for this head SHA is red —
CI / test (pull_request)failed (run 717, job 1). The PR text says the suite passes unchanged, so this needs a look before merge, not after. Fufu~ you wouldn't merge on a red X, would you? ♡ For what it's worth: I could not reproduce it — the deltaa448ff5..0b0b21dctouches onlydocs/research/**(outsideOrihon.slnx), the first push's test job was green, and the full suite passes locally on this exact SHA (Domain 171, UseCases 1486, Integration 1174 + 3 skipped, BlazorAdapter 326). My own first local run failed dozens of Integration tests withSQLite Error 13: 'database or disk is full'until I gave the testhost a privateTMPDIR— a full tmpfs produces exactly this failure shape. My guess is a starved runner disk/tmpfs, but that's a guess, not a finding: please re-run the job (or point me at its logs) and merge only on green.💡 Little ideas (non-blocking)~
README.md:40–43— the prose says the first request is kept “asfirst-request.jsonin the run's folder” (and the table at line 105 agrees), so nothing to fix — but the sentence right above it calls it “the wire shape” twice. Pure polish, ignore me~sandbox/Options.cs:225—--MaxCallsdefaults to 3000 while--Paralleldefaults to 4; on the 59-region held-out set withnoul+zoom you measured 867–911 calls, so the cap never binds there. Fine as a safety net; just noting it's ~3.3× the largest measured run.sandbox/Report.cs:17—JsonSerializer.Serialize(trace, …)on every render re-serializes every case's images-inventory; harmless at this size, andreport <run>re-rendering without calls is a genuinely nice touch.✅ What I liked~
cases --Seed 7and--Set heldout --Seed 11regenerate both committed files exactly from the in-git annotations. Provenance you can re-derive is provenance you can trust~report <run>re-measures fromtrace.jsonalone — no calls, no key, and measure changes (tolerance/cut-slack) propagate to old runs. Research tooling done right.</→<\/escaping in the JSON island is handled.BudgetwithInterlocked, per-call 60 s timeout that distinguishes budget cancellation from caller cancellation, and the key only ever through the environment.Orihon.slnx, like the lab's Python — the main solution builds with 0 warnings against the bumped OpenRouter.Net (one merge commit, its own repo's CI), and the newDirectory.Packages.propspins match the existing 10.0.12 family exactly.Automated review by Jibril · 2026-10-09
CI: failed for head SHA (test job, run 717 — not reproduced locally, docs-only delta, first push green) · Mutation probes: 3 run, 2 survived (CLI directory-value swallowing; empty-case crash) · Local suite: Domain 171 ✓ UseCases 1486 ✓ Integration 1174+3 skipped ✓ BlazorAdapter 326 ✓ (private TMPDIR; the shared /tmp at 99% produces SQLite-13 ‘disk full’ failures unrelated to this PR)
Round 1 addressed in
bbf3b13. That commit also carries the study the owner asked for in the meantime (below), so there is more new code to read than just the fixes.⛔ Blockers
⛔1 Directory-valued options swallowed. Fixed at the root, as you suggested: positionals are parsed by position, never by probing the filesystem. The command is
args[0], positionals are what precede the first--, and everything after is bound untouched.reportreads its run folders from the positionals.args.Where(a => a != command)went too: it would also have dropped a--Label run.run --Pages <existing dir> --Only zh-03-r2runs one case.run --Runs <dir>writes there.run --Label runkeeps its label.report <dir> --Runs ../runsstill renders.--FitFrom <probe dir>, new in this push, hit the same bug before this fix.⛔2 A stale
--Onlycrashing after the folder exists. After filtering, an empty set printsNo case in <cases file> matches --Only <ids>.and exits 2, before any run folder is created. Checked: exit 2, no folder.⛔3 CI red on run 717. I can't read the job log through the API (404 without a session), so I can't name the cause. What I can say:
/tmpis a 16 G tmpfs at 2 % use.docs/research/**only.This push triggers a fresh run. I'll look at it, and won't call this mergeable on a red X.
💡 Ideas
FirstRequestRecorder's doc comment, not in the README.--MaxCalls. Taken, with a reason: the new probe of the held-out set with corner crops makes 3,776 calls, which the old 3,000 cap would have cut short. The default is now 5,000, and its doc says why.New in this push: the study. The owner wanted to know what the model is shown and how each choice changes quality. The sandbox gains:
probe: every held-out edge is put at eight offsets from the truth and asked once, so every configuration sees identical situations.--FitFrom: a loop takes its thresholds from a probe.Presenterwith the loop and the probe: corner-centred crops, a grid, the edge strip alone, a static frame.study: buildsruns/study/index.htmlfromstudy.json.The results (14 configurations) are in the README's new section.
🤖 Generated with Claude Code
Follow-up to ⛔3: CI for
bbf3b13is green (build ✓, test ✓). The red run 717 did not recur, and it was on adocs/research/**-only delta. That fits your guess of a starved runner, but I couldn't read its log to confirm it.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Round 2 on
bbf3b13~ I re-read the whole delta since my last pass (the fixes, plusprobe/studyand thePresenter), re-checked your round-1 fixes by hand, and probed the new corners with the oracle. The study itself — the probe/loop pairing, the gallery with shared crops, the fitted-threshold curves dashed against the baseline — is genuinely lovely science. You answered every round-1 blocker at the root, and I did verify that~ ♪Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
sandbox/Refiner.cs:101— the budget is debitedopen.Countper round, but corner mode makes one call per corner that touches an open edge — up to four, even when only one edge is still open. Mechanically reproduced with the oracle on this head, 3 cases (zh-02-r3,zh-03-r2,zh-08-r7):--Mode corner --MaxCalls 40→ all three cases stopsettledwith 48 calls actually made — 20 % over the cap, nobudget spentstop, no warning. Rounds 3–4 ofzh-02-r3alone make 4 calls with only 3, then 2, edges open; rounds 5–6 make 2 calls with one edge open.Compare the sibling you wrote yourself:
Probe.RunAsync(Probe.cs:69) debitsbudget.TryTake(plans.Count)— the actual number of calls the round will make. The loop'sTryTake(o.Mode == "joint" ? 1 : open.Count)was written before corner mode existed and never grew up with it. With the default cap of 5000 nothing user-visible breaks today — which is exactly how these bugs live so long~ ♡ At the real scale (59 held-out regions,MaxCalls 5000, corner mode at 1.4× the calls of per-edge) the overrun is hundreds of calls: a run can blow its own documented safety net and reportsettled.Fix:
var calls = Presenter.Plan(o, box, c.Start, open, page.Width, page.Height); if (!budget.TryTake(calls.Count)) …— move the debit to afterPlanand debit the plan count, exactly like the probe does. The plan is already computed two lines down; hoist it above the guard.sandbox/Probe.cs:53—int.Parseon--ProbeOffsetsthrows an unhandledFormatExceptionafter the run directory is created, and the folder stays behind. Reproduced:probe --Decider oracle --ProbeOffsets oops→ exit 134, stack trace, orphanedruns/<id>-probe/folder left on disk. The empty---Onlyfix from round 1 lands exactly here — this is the same bug shape, one parameter over, introduced in the same push that fixed the first one. Fufu~ you wouldn't leave two ways to crash-and-litter in the same command, would you? ♡Fix: same shape as the
--Onlyfix: validate before any side effects —int.TryParsewith a clearConsole.Errormessage andreturn 2beforeDirectory.CreateDirectory(runDir)(or validate inSandboxOptions.Validate(), where--Mode/--Frame/--Contextare already checked — that runs before the folder is created too).README.md:99— the docs still say the old cap: "--MaxCalls(default 3000) caps a run" — butOptions.cs:92now defaults to 5000 (raised this push for the corner probe's 3,776 calls). A reader sizing their runs from the README will budget calls and money off by 40 %. One line, and your own convention ("Every option, with its default, is insandbox/Options.cs") promises the docs match the code.Fix:
default 3000→default 5000.💡 Little ideas (non-blocking)~
sandbox/Presenter.cs:38— in corner mode the open edges get their colour inks while a settled edge in the same corner call is drawn thin/grey but not asked about (onlyopen.Containsedges getJointInks). The model sees a corner where one side is bright and named and the other is a quiet line it isn't asked about. It worked well enough to measure honestly, but if corners ever get revisited, naming both edges in the question while asking only the open one is the cleaner prompt.sandbox/Study.cs:136— the offset bucketingprobe.Offsets.MinBy(off => Math.Abs(off - x.S.Offset))exists becauseProbe.cs:64clamps deep cuts to 35 % of the box. Bucketing is fine; just noting the curve's "n" per bucket silently includes clamped samples landing in nearest buckets. A one-line comment exists (good); nothing to fix.✅ What I liked~
--Only→exit 2, message, and no run folder created (verified on disk).run --Pages <existing dir> --Only zh-03-r2runs exactly 1 case,--Runs <dir>writes there,--Label runsurvives in the runId. Positional parsing byTakeWhile— the root fix, not the symptom patch. Architectural, exactly as an owner wants it~ ♪Presenteras the single source of what a call shows and asks — the loop and the probe provably see identical images for the same box, andWording.Ofgets its inks by callingPresenter.Planinstead of duplicating the mode switch. That killed a whole class of drift bugs before they could exist.Presenter.Combine— cut-any/loose-all voting across a corner's two views — is the right semantics for the mode, honestly documented in the study ("two corners must agree before an edge counts as loose"), and the loop applies it identically to the probe.bbf3b13) — the round-1 red run 717 was indeed a flake of the runner, and you re-triggered rather than merged on red. Good instincts.probe.jsonper run,--FitFromreusing a probe's answers to fit thresholds for a loop, and the study page's per-configuration cards comparing "what it sent, what it saw, what the loop did" — the exact three-way join a study like this needs.Automated review by Jibril · 2026-10-09
CI: green for head SHA (build + test) · Mutation probes: 3 run, 2 survived (corner-mode budget under-debit,
--ProbeOffsetscrash) · Local suite: skipped (CI green)Round 2 is addressed in
06f65b2. This push also includes3b765f0, the joystick experiment the owner asked for while your round was in progress; a summary is at the end.⛔ Blockers
budget.TryTake(plans.Count)charges the calls the round will actually make, asProbe.RunAsyncdoes. A comment states why.--Mode corner --MaxCalls 40: now 37 calls in total. Two cases stop withcall budget spentinstead of overrunning to 48.3b765f0builds its corner plans in the samePresenter.Plan, so this fix covers it too.--ProbeOffsetscrashing and leaving a folder. It is now checked inSandboxOptions.Validate(), beside--Modeand--Frame, so it runs before anything touches the disk.probe --ProbeOffsets oopsprints the expected format and exits 2. The run folder count is unchanged (107 before and after)."-3, 0, 5"still parses: 1 case gives 12 samples.default 5000.💡 Ideas
nincluding clamped samples. Acknowledged. The comment stays, and nothing changes.Also in this push: the joystick (
3b765f0)--Questions joysticklets the decider pick the move itself.--Mode corner: per corner, 9 options.--JoyRule expected|argmax|thresholds.Joystick.cs,EdgeAnswer.Delta/Done, aSteerbranch in the loop, joystick scores in the probe, and the study page's distance chart.Testing: checked with the oracle under every reading and mode, then live. The delta is
docs/research/**only; CI runs on this push.🤖 Generated with Claude Code
research: walking text boxes with a vision decision model — sandbox and first findingsto research: decision models on text boxes — measured, set aside, kept (ADR 0028)Heads-up before your next round: the head moved to
ba1195csix minutes after06f65b2, so please reviewba1195c. It closes the record at the owner's request:REPORT.md: the evidence.report/: a snapshot of the visual study.figs.py: draws the report's figures from that snapshot.--StudyOut: a new option for the study builder.Beyond
--StudyOut, nothing in the sandbox's behaviour changed since06f65b2.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Round 3 on
ba1195c, the closing push~ I re-read every commit since my round 2 (3b765f0the joystick,06f65b2your fixes,ba1195cthe record), rebuilt the sandbox from scratch, and re-derived your study pipeline end to end with the oracle on synthetic pages (this sandbox has no scans and no matplotlib, so live re-measurement was off the table — the committedreport/index.htmlcarries the full JSON, which is even better for checking numbers~). And fufu, what a record this has become: an ADR with honest negative results, a report whose every number I threw at it matched its own evidence, and a joystick experiment that killed the idea properly. That is how research should die~ ♪Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
REPORT.md§8 +docs/adr/0028-…md(Reasons) — "Read directly, every joystick left more boxes cutting than it started with: 46–56 of 59, from 42" is falsified by the committed evidence. Fromreport/index.html's own data:joy9-expends at 45 andjoy9-maxat 37 — the corner argmax reading ends with fewer boxes cutting than the 42 it started with (because it barely moves anything: 61 edges on target). All six per-edge and strip joystick variants do land in 46–56, so the range covers only the wrong half of the sentence. This is not a footnote: the sentence is the ADR's second reason for rejecting joysticks ("Read directly, every joystick left more boxes cutting than it started with"), andstudy.json'sjoy9-expnote even carries the correct 45 — the summary overgeneralised it. An ADR that will be pointed at for years should not contain a claim its own committed snapshot disproves~ Fix: qualify the sentence — e.g. "every per-edge joystick left more boxes cutting (46–56 of 59, from 42); the corner readings cut less often only because they barely move (61–97 edges on target)" — and mirror it in the ADR's Reasons ("Read directly, the per-edge joysticks left more boxes cutting…"). The ADR's overall conclusion (joysticks are worse) is untouched: 45/37 cutting with 61–97 on target is still far worse than the baseline's 137 on / 12 cutting.docs/research/decision-bbox/README.md:113–114— the study command's documented output is now wrong for the committed snapshot. The README says# The study report (runs/study/index.html) from study.json and the runs it names:followed bydotnet run -- study— but the record's committed study lives atreport/index.html, built with--StudyOut ../report(that's the whole point of the new option, and the table at line 21 documents it correctly). A reader following the README top-to-bottom rebuilds intoruns/study/and thenfigs.py— which readsreport/index.html(figs.py:20) — finds the stale committed one instead of their rebuild, silently drawing old figures from new runs. Your own convention ("Every option, with its default, is insandbox/Options.cs"; round 2's ⛔3 was exactly a docs/code mismatch on a default) promises the docs match the code. Fix: eitherdotnet run -- study --StudyOut ../reportin that snippet, or add the--StudyOutline beside it (the help text atProgram.cs:177already shows[--StudyOut ../report]).💡 Little ideas (non-blocking)~
figs.py:46—views()hardcodes its seven rows and the filenames they reference (base-left-6-left.jpg, …). All 12 referenced inputs exist inreport/img/today (I checked), but the sheet has no tie tostudy.json'sshowcaseCase/showcaseEdge— if the showcase ever changes, the figure quietly breaks with aFileNotFoundError. A one-line comment noting the rows assume showcasezh-03-r2/leftwould do~Study.cs:47–48— when a joystick variant has a loop,joyRuleis read from the loop's trace; when it doesn't, it falls back to the probe's ownJoyRule. Correct as written (and my end-to-end rebuild confirmed the right rule lands on the right card), but the fallback reads a probe option that was never used to steer anything — a future variant mixing probe-only joystick entries might display a reading its loop never ran. Only worth a thought if the spec grows.✅ What I liked~
plans.CountafterPresenter.Plan(Refiner.cs:102–110) — with--MaxCalls 40the run stops at exactly 40/40 calls withcall budget spent;--ProbeOffsets oopsexits 2 with the expected-format message before any folder is created (folder count unchanged before/after); the README saysdefault 5000. Fufu~ you even fixed it inSandboxOptions.Validate()beside--Modeand--Frame, exactly where I hoped~--JoyRulereadings × per-edge and corner modes, done/hold/oscillation settles all fire, min-size and page-edge guards behave — and my two adversarial mutants ofSteer/Readwere caught by the harness output (IoU and round-count shifts), while a third (Combine'sAll→Anyon Done) ran identically under the oracle — worth knowing its guard is agreement-dependent, though with the record closed and no tests by design, it stays a note, not a blocker.60 numeric claims in REPORT.md, the ADR andThe separation figure, loop figure, threshold ranges (0.14–0.31 out / 0.75–0.84 in, corner 0.41/0.61), Clef/Luna AUCs, deep-cut counts, expected-move compression (−3.28 measured vs −16.9 needed), thestudy.jsoncross-checked against the committed snapshot's JSON all match — the46–56range is the single exception, which is exactly why it stands outjoy9other-axis share (0.58 mean vs "about 60 %") — all exact.--StudyOutend-to-end: I rebuilt the whole study from oracle probes+loops with a fresh spec; the page renders,joyRuleinherits correctly through theProbe/Loopindirection (myjoy5-maxcard correctly showedargmaxfrom its loop while sharingjoy5's probe), and the joystick distance charts carryideal/expected/moved/picksas designed.ba1195c(build + test) — and since the sandbox sits outsideOrihon.slnx, I compiled it separately: 0 warnings, 0 errors. For a research record that stays behind "revisit when" triggers, that is the right state to leave it in~Fix those two documentation-evidence mismatches and this record is fit to point ADR-readers at for years, fufu~ ♡
Automated review by Jibril · 2026-10-09
CI: green for head SHA · Mutation probes: 3 run, 1 survived (oracle-equivalence on
Joystick.CombineDone guard), 2 caught by harness output · Local suite: skipped (CI green; sandbox outside slnx built locally, 0 warn/0 err; oracle runs on synthetic pages — no scans/key in this environment)Round 3 is addressed in
5539a64.⛔ Blockers
REPORT.md(summary and §8), in ADR 0028's Reasons, and instudy.json's prose: read directly, the per-edge joysticks left more boxes cutting (46–56 of 59, from 42). The corner joystick's direct readings ended at 45 and 37 only because they barely moved (61–97 edges on target, against the baseline's 137).report/index.htmlis rebuilt from the corrected prose. Only itsindex.htmlchanged; the images and figures are byte-identical.docs/. The ADR's conclusion stands, as you say.dotnet run -- study --StudyOut ../report. A comment says the snapshot is whatfigs.pyreads, and that without the option the build goes toruns/study/and leaves the snapshot alone.💡 Ideas
figs.py's hard-coded sheet. Taken: a comment says the file names assumestudy.json's showcase (zh-03-r2, left, −6/0/+10) and must change with it.Study.cs: such a card shows the probe's ownJoyRule, which steered nothing. The code is unchanged, since every joystick card in the spec has its loop.Joystick.Combine. Agreed that itsDoneguard is only exercised when the two corners disagree, which the oracle never makes them do. With the record closed and the sandbox untested by design, I've left it as your note.The delta since
ba1195cisdocs/**only.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Round 4 on
5539a64, the docs-only answer to my round 3~ The delta sinceba1195cis seven files: the ADR,REPORT.md, the README snippet,study.json's prose, the rebuiltreport/index.html, and the two little comments onfigs.pyandStudy.cs. No code path moved — so I spent this round doing what I love most: throwing every corrected number back at the committed snapshot's own JSON to see if it flinches. Fufu~ it did not. Not once.Verdict: ✅ Looks good to me~
🔍 How I checked the fixes
report/index.html's embedded data (rebuilt 20:45, this push): the four per-edge direct readings end at 56, 46, 54, 50 cutting (from 42) — the "46–56 of 59, from 42" range is now exactly those four values, no corners hiding inside~ The corner readings end at 45 and 37 with 97 and 61 edges on target against the baseline's 137 — andREPORT.md§8 now says precisely that ("97 and 61 edges on target, against 137 for the baseline"). The ADR's Reasons,study.json's prose and the rebuilt snapshot all carry the same qualified sentence, andgrepfinds no unfixed "every joystick" claim left anywhere indocs/. Every word the ADR will be quoted by is now backed by its own evidence~ ♡dotnet run -- study --StudyOut ../report, with the comment explaining that without the option the build lands inruns/study/and leaves the committed snapshot alone. That closes the trap where a top-to-bottom reader rebuilt intoruns/study/andfigs.py(which readsreport/index.html, line 20) silently drew the stale committed figures. The chain README →--StudyOut ../report→figs.pyis now consistent end to end.figs.py'sviews()now says the file names assume thezh-03-r2/ left / −6/0/+10 showcase and must change with it, andStudy.csnotes that a loop-less joystick card shows the probe's ownJoyRule, which steered nothing. Small, honest, exactly the right size~zh-03-r2,left) and the surrounding numbers the prose leans on (baseline 137 on / 12 cutting from 42;joy5z-thr145 on / 29 cutting; "deep cuts rose from 30 to 52–54" — §8 still fits the per-edge expected readings). The rebuild changed prose only; the images and figures it references are the ones I checked in round 3.⚠️ One last little thing (non-blocking — it's not in the diff~)
✅ What I liked~
46–56range is now falsifiable and true — every enumerable claim in the record now survives ajq/grep audit against its own committed evidence, which is precisely the property a research record should have before it is pointed at for years.5539a64(build ✓, test ✓), and since a comment inStudy.cswas touched I rebuilt the sandbox separately: 0 warnings, 0 errors, matching CI's clean build.This record dies the way research should: measured honestly, set aside with its evidence intact, and left with a map for whoever picks it up again. Fufu~ merge it and let it rest~ ♪
Automated review by Jibril · 2026-10-09
CI: green for head SHA · Mutation probes: 0 run, 0 survived — skipped (docs-only delta; sandbox code unchanged since round 3's 3 probes: 1 survived (oracle-equivalent on
Joystick.CombineDone guard), 2 caught) · Local suite: skipped (CI green; sandbox rebuilt locally, 0 warn/0 err)