clay-borg/history/260802-render-port-challenge.md
tegwick c5fa610e59 CB-WP-0012-T02: adversarial review — not approvable as written
Four of six challenges conceded; the survey's two main arguments are
replaced rather than clarified.

C1  The survey concluded the sub-100k region was empty without ever
    measuring it. It is not empty: tiny-skia fits at 83,956, and the
    real windowed floor is fltk at 140,079 — 1.5x corrected headroom,
    not 128x. "Two orders of magnitude" is withdrawn.

C1b Following that concession: wgpu+winit is 1,741,979 marginal lines
    against a 250,000 target. AM-4a is incompatible with INTENT stage 2
    by 7x, and has been since both were written. Raised for the
    maintainer; deliberately not decided by this pass.

C2  "Marginal cost zero" was scored on an axis chosen to produce zero,
    and the same relocation already flattered sdl2 and fltk in the
    survey's own table. One acquisition rule now covers all three, and
    it raises two candidates' cost rather than lowering the winner's.

C3  The survey contradicted itself on the second-use rule. cb-render-api
    and cb-render-null are withdrawn; cb-render-html ships against the
    existing Project trait, and the port waits for stage 2's second use.

C6  The candidate measurements did carry a positive control. The C1
    batch did not — it copied dep-weight.py's measurement function
    without its guards — and was re-measured under them before being
    cited. Figures reproduced unchanged. The HTML row is relabelled
    0 by construction, not by measurement.

C4/C5 become six controls binding on ADR-0007.

The recommendation stands. The reasoning that reached it does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 04:12:22 +02:00

10 KiB
Raw Blame History

260802 — challenge to CB-RES-0006

Adversarial review, one round, per InnerLoop §Step 2.

Fidelity note, first, because it caps everything below. The spec asks for a separate session or agent. This review was run in the same session as the survey, because this environment's standing instruction is not to spawn agents unless asked. So it does not have the property the step exists to provide: the reviewer inherits the author's sampling, the author's framing, and the author's blind spots. Every prior instance of this class in the repo — CB-WP-0002's dedup sample, CB-WP-0005's AM-7 assertion — was a shared blind spot between author and reviewer who sampled the same way. Treat the challenges below as a lower bound on what a genuinely separate reviewer would find.

The claim rests on numbers, so per the table in §Step 2 the reviewer's job is to reproduce them independently and to mutate the assertions behind them — not to argue with the prose.


C1 — The survey concluded the sub-100k region was empty without looking

This is the challenge that lands.

The survey measures seven candidates. The cheapest windowed one costs 480,501. It then reports the corrected headroom as 92,798 and concludes that no windowed toolkit fits — a conclusion about the interval 0 < x < 92,798 drawn from a sample whose smallest windowed member is 480,501. Nothing was measured in the interval the conclusion is about.

The candidate list is not random, either. It is the list a 2026 Rust developer reaches for, which is a list of GPU-era stacks: everything on it pulls wgpu, winit, or rustix. Cheap, old, thin bindings to system libraries were structurally absent from the sample.

Measured, same method, marginal against the same base:

candidate marginal lines vs 92,798 corrected headroom
tiny-skia alone 83,956 fits, at 90% of it
fltk 140,079 1.5×
sdl2 191,973 2.1×
sdl3 276,445 3.0×
raqote 286,852 3.1×
minifb 774,389 8.3×
pixels 863,528 9.3×
speedy2d 1,328,008 14.3×

The survey's central rhetorical move — "this is not a near miss to be negotiated; it is two orders of magnitude" — is false. It is two orders of magnitude for the candidates the survey chose. The real floor for a windowed toolkit is fltk at 140,079, which is 1.5× the corrected headroom. That is exactly a near miss to be negotiated.

tiny-skia at 83,956 actually fits — it is a pure-Rust CPU rasterizer with no windowing, so it is not by itself a table, but it disproves the survey's stated interval claim on its own.

Required: the survey must either withdraw the "two orders of magnitude" framing and re-argue against a 1.5× floor, or concede that its recommendation rests on something other than dependency cost.

C2 — "Marginal cost zero" is scored on an axis chosen to produce zero

AM-4a measures vendored third-party Rust. The HTML option scores zero because it relocates the renderer into a runtime AM-4a cannot see. The browser is tens of millions of lines of unaudited third-party code. Calling that zero is true under the metric and false in substance.

If a proposal may satisfy a dependency target by moving the dependency somewhere the target does not look, then the target constrains nothing, and this pass is the first to demonstrate it. That is a worse outcome for the project than buying macroquad honestly.

And the same critique convicts C1's cheap candidates. sdl2 at 191,973 and fltk at 140,079 are thin Rust bindings to large C libraries; their .rs line counts exclude the actual implementation for exactly the same reason the browser's is excluded. So the metric already permits this move, and the survey's own table already rewards it — it simply did not notice, because it never measured a binding.

Required: one rule, applied to all three. Either relocating an implementation into an unmeasured runtime is permissible (and then sdl2 and fltk are legitimately cheap and must be compared on their merits, not excluded on cost), or it is not (and then HTML is not free either). The survey cannot have it one way for the browser and another for sdl2 without stating the distinction. It states none.

C3 — The survey contradicts itself on the second-use rule

Recommendation 1 declares cb-render-api a canonical port now. Recommendation 4 says cb-render-wgpu at stage 2 is "where the port interface gets its second use and only then becomes canonical."

Both cannot hold. INTENT: "No concept becomes canonical merely because it looks general. It becomes canonical after surviving a second concrete use." A port interface written against exactly one implementation, which emits whole documents, will acquire a document's shape — retained mode, full state per update, string identity, no frame timing — and stage 2 will find it unimplementable and rewrite it. The survey names this risk in §6 and mitigates it with "write it to be implemented twice," which is a wish. CommitWindow is already on record in this repo as the concept that looked general, has a delete-by date, and has declined two second-use opportunities.

Required: either defer cb-render-api until stage 2 supplies a second implementation, and ship cb-render-html directly against the existing Project trait — or state why this port is exempt from a rule INTENT states without exception.

C4 — The 250-line HTTP server is undersold, and "loopback only" is not a boundary

The survey bounds the risk with "listens on loopback only and speaks to a page it emitted itself." Neither clause holds:

  • Any process on the machine can reach a loopback listener. Hot-seat play is explicitly several people at one machine.
  • Any web page the user visits can reach it too. A page on the open internet can issue requests to 127.0.0.1 — that is what DNS rebinding and cross-origin POST are. The browser running the table is the same browser reading the internet; that is the whole premise of the option.
  • "A page it emitted itself" is an assumption the server cannot check unless it is written to check it, and the survey specifies no such check.

An unauthenticated loopback endpoint that accepts POST /command and mutates authoritative game state is a remote-controlled game from any tab the user has open.

Required, as concrete controls the survey lacks: an unguessable token minted per process and required on every request; Origin and Sec-Fetch-Site checked and rejected by default; the listener bound to 127.0.0.1 explicitly rather than 0.0.0.0; and a test that a request without the token is refused. The last one is the one that matters — the other three are claims until something asserts them.

C5 — The proposal regresses the finding the previous pass paid for

CB-WP-0011's finding, in its own words: a renderer's defect class is silent omission, no natural renderer assertion catches it, and the fix is to walk the shape of the input and make silence cost a build.

This proposal moves drag hit-testing and command construction into JavaScript embedded in emitted strings — a language cargo test cannot run, clippy cannot lint, and M-D1-MUT cannot mutate. The gate that CB-WP-0011 built stops at the language boundary, and the survey proposes putting the interactive half of stage 1 on the far side of it, one pass after paying to learn why that is dangerous.

§6 lists this as "the strongest argument against, and the one T02 should press." Naming a challenge in advance is not answering it.

Required, as controls:

  • JS may not construct commands. The emitted page reports raw pointer facts — "pointer down on element id, up on element id" — and Rust decides what command that is. That confines JS to input transport and makes the decision testable by feeding the Rust side synthetic events.
  • The coverage gate must cross the boundary. every_view_field_is_ classified must have an HTML counterpart asserting over the parsed emitted document, not over the Rust that emits it. Emitting a <div> per field is not evidence the field is visible; the assertion must be that the token is in the document.

C6 — One number in the survey is reproduced; one is not independently checkable

Reproduced: the proc-macro delta. --edges normal,no-proc-macro gives 18 crates / 157,202 lines against 23 / 246,250. The 89,048 / 36.2% figure is correct, and the five crates named are the five that differ.

Not independently checkable: every candidate figure, because the reviewer used the author's script. Per §Step 2 this is precisely the failure mode — "the reviewer re-derives on a different sample than the author used" — and no different sample was used. The C1 table above is new data from the same instrument, which tests the survey's coverage but not its instrument.

What the harness would report if it silently stopped: source_lines() returns 0 for a crate it cannot locate, and the probe scripts sum without a positive control. A registry path change would report every candidate as 0 marginal lines — i.e. as fitting comfortably — which is the direction that flatters the recommendation. tools/dep-weight.py has exactly this control (unlocatable crate measures zero (so the guard fires)); the probe scripts copied the measurement function and not the guard.

The svg-emit row in the survey's table reads 0 marginal for reasons that are correct, but a reader cannot distinguish that 0 from a harness that stopped. A zero that means "costs nothing" and a zero that means "measured nothing" are printed identically. That is HDN, in the survey's own instrument, in the row carrying the recommendation.


Verdict

Not approvable as written. C1, C2, C3 and C6 each require a change to the survey, not a clarification. C4 and C5 require controls that must land in ADR-0007 as conditions on the implementation.

The recommendation may well survive — none of these establishes that a windowed toolkit is affordable, only that the survey's argument for why it is not was overstated and partly unmeasured. But the survey as written argues from a false interval claim, an inconsistently applied metric rule, an internal contradiction, and a zero that cannot be distinguished from a dead harness.