--- id: CB-WP-0012 kind: product title: "Stage 1, second slice: the rendering port" status: done state_hub_workstream_id: "761f626f-9770-4e23-aaba-94574c41ff04" --- # Purpose Tier **L**, the declaration CB-WP-0011 deferred and owed. ``` structural tier L (INTENT stage 1 creates a new capability port) chaos d4 = 1 → no override declared tier L ``` Declaration 7 of 12 in the calibration window. The CHAOS gate earned its first `caught` entry last pass by rolling this same declaration down to S; this time it rolls nothing, so the pass runs at full weight — survey, adversarial review, ADR, then code. The hard gate applies as stated: **no implementation code for the render capability exists before ADR-0007 is committed.** ## What this pass owes INTENT stage 1 is *"Inspectable 2D table — card/token/hand/relationship- graph visualization, drag-to-propose, debug inspector, hot-seat play."* CB-WP-0011 shipped the debug inspector. This pass owes the other three, and the port they hang from: ``` cb-render-api ├── cb-render-null └── cb-render-??? <- the survey's question ``` **The leading constraint is AM-4a**: 246,250 lines of 250,000, measured — **3,750 lines of headroom**. Every candidate implementation of a 2D table is a five- or six-figure line count. This is the first capability in the project whose obvious implementation costs more than the entire remaining budget, so the survey's first duty is to establish whether that is a real obstacle or an artifact of how AM-4a is instrumented. ## Task: survey the render port and its dependency cost ```task id: CB-WP-0012-T01 status: done priority: high state_hub_task_id: "61515aae-c6ff-4c19-b4c8-017c81d29ef7" ``` Write `research/CB-RES-0006-render-port.md`. **Measure, do not estimate.** The candidate line counts must come from the same method `tools/dep-weight.py` uses — `cargo tree --edges normal` over a real resolved graph, then counted `.rs` lines in vendored source — so the numbers are comparable to the AM-4a figure rather than merely adjacent. Last pass's correction was a hand-counted number; this pass has no excuse for one. **Measure the marginal cost, not the total.** What AM-4a charges is what a candidate adds to a graph that already holds 23 crates. A candidate's headline size overstates its cost by whatever it shares with the base. Cover at minimum: an immediate-mode GUI (`egui`/`eframe`), a 2D game framework (`macroquad`), the raw stage-2 stack (`wgpu` + `winit`), a CPU rasterizer (`softbuffer` + `tiny-skia`), a terminal UI (`ratatui`), and at least one option that is not a Rust toolkit at all. **Ask whether the instrument is right.** If AM-4a's figure counts code that never reaches a shipped binary, the headroom it reports is wrong, and that has been true for every pass that has cited it. Check this before recommending any budget change — a survey that argues for raising a target it has not first audited is arguing in its own favour. **Done 2026-08-02.** [CB-RES-0006](../research/CB-RES-0006-render-port.md). Two findings, and they point opposite ways. The cheapest candidate that opens a window (`macroquad`) costs a marginal **480,501** lines — **128×** the headroom. `egui` + `eframe` costs 2,782,849. `ratatui`, the option one expects to be cheap, costs *more than macroquad* (1,067,013) because `rustix` pulls `linux-raw-sys` at 479,901 lines. And AM-4a is mis-instrumented: **36.2% of the shipped-runtime figure (89,048 lines) is proc-macro crates** — `syn` alone is 66,916 — which run in the compiler and never reach a binary. Real headroom is **92,798**, not 3,750. Every pass that cited 3,750, this workplan's own Purpose included, cited a number wrong in the conservative direction. The recommendation survives the correction: `macroquad` is still 5.2× over at 92,798. That was the condition for proposing the correction at all, and the correction is filed separately rather than bundled with the decision it would unblock. Recommended: `cb-render-api` + `cb-render-null` + **`cb-render-html`** — emitted HTML/SVG/JS, marginal AM-4a cost **zero**, with `cb-render-wgpu` left to stage 2 where it becomes the interface's second use. ## Task: adversarial review of the survey ```task id: CB-WP-0012-T02 status: done priority: high state_hub_task_id: "5c36ed13-2a1a-467c-b359-5a0e84388173" ``` Step 2 of the inner loop, run against T01's recommendation rather than for it. The review must name, at minimum: - what the recommended option **cannot** do that a windowed toolkit can, stated concretely rather than as a caveat; - the failure mode of building the port around a single implementation when the second-use rule exists precisely to stop that; - whether the survey's dependency argument would survive if AM-4a's target were simply wrong, i.e. whether the recommendation is load-bearing on a number the same pass proposes to correct. A review that ratifies the survey without producing a control the survey lacked has not run. **Done 2026-08-02.** [challenge](../history/260802-render-port-challenge.md), [response](../history/260802-render-port-response.md). **Not approvable as written** — four of six challenges conceded, and the survey's two main arguments replaced. - **C1**: the survey concluded the sub-100k region was empty without measuring it. It is not empty. `tiny-skia` fits at 83,956; the real windowed floor is `fltk` at **140,079 — 1.5×** corrected headroom, not 128×. "Two orders of magnitude" withdrawn. - **C1b**, which the concession exposed: `wgpu` + `winit` is **1,741,979** marginal lines against a 250,000 target. **AM-4a is incompatible with INTENT stage 2** — 7× the whole target — and has been since both were written. Raised for the maintainer, deliberately not decided here. - **C2**: "marginal cost zero" was scored on an axis chosen to produce zero, and the same move already flattered `sdl2`/`fltk`. One acquisition rule now covers all three, and it *raises* two candidates' cost. - **C3**: the survey contradicted itself on the second-use rule. **`cb-render-api` and `cb-render-null` are withdrawn from this pass.** - **C4/C5**: six controls adopted as binding on ADR-0007 — a loopback token with a mutation-backed refusal test, JS barred from constructing commands, and the coverage gate crossing the language boundary. - **C6**: the candidate measurements did carry a positive control (empty `unlocated` for all seven, verified). The C1 batch did **not** — it copied the measurement function without the guards — and was re-measured under them before being cited. The HTML row is relabelled *0 by construction, not by measurement*. **Fidelity note:** the review ran in the same session as the survey rather than a separate one, per this environment's standing instruction not to spawn agents unasked. It therefore inherits the author's sampling and is a lower bound on what a separate reviewer would find. **Note on tier:** this pass no longer creates a capability port, which was its structural trigger for tier L. The declaration and its roll stand — a tier that changes because review shrank the work would be a function of the outcome. Recorded as the CHAOS window's second entry. ## Task: ADR-0007 — the render port ```task id: CB-WP-0012-T03 status: done priority: high state_hub_task_id: "75b33156-cd04-440b-ab0e-02609484944a" ``` Record the decision, the rejected alternatives with their measured costs, and the controls the implementation must carry. No implementation code lands before this commits. **Done 2026-08-02.** [ADR-0007](../decisions/ADR-0007-render-html-not-a-port.md) — *render to HTML, and do not declare the port yet.* Five decisions, eight rejected alternatives with measured costs, six binding controls, and **two items reserved for the maintainer**: AM-4a's 7× incompatibility with stage 2, and whether the acquisition rule proposed by the pass that benefits from it is the right rule. The hard gate held: no render implementation code existed before this commit. ## Task: the port and its first implementation ```task id: CB-WP-0012-T04 status: done priority: high state_hub_task_id: "07dc04f0-8f0b-4285-8e1a-d5f9203a924b" ``` `cb-render-api` plus `cb-render-null` and the implementation ADR-0007 chooses, delivering visualization, drag-to-propose and hot-seat play. **K13 binds here.** The renderer consumes a `Project`ion and can never feed back into validation; a drag that proposes a move must go through the same command path a CLI move takes. The inspector's coverage gate is the precedent for the control this needs. **Done 2026-08-02.** `crates/cb-render-html` (doc/input/serve) and `tools/cb-play/src/hotseat.rs`, behind `cb-play --serve PORT`. Per ADR-0007 D2 there is **no `cb-render-api` and no `cb-render-null`**. Marginal AM-4a cost, measured rather than claimed: **0 new third-party crates** (23 before, 23 after). AM-4a unmoved at 246,250. Eight mutations, each red for its stated reason. Two results worth more than the six that behaved: | control | result | |---|---| | a field present in neither list | fired **for real on the first run** — `ground_choices.*.choice`, `ground_choices.*.problem`, and `players.*.blame_from`, the last being an **empty vector**, a leaf path a fully-populated fixture would never produce | | `Sec-Fetch-Site` arm removed | **stayed green** — the `Origin` check caught it independently. Both had to go before the control bit; a control that passes for an unintended reason has not been demonstrated | **Never executed: the emitted JavaScript.** The socket loop is tested end to end with synthetic HTTP and the page is asserted against as a parsed document, but no browser engine has run `SCRIPT`. ## Task: evidence ```task id: CB-WP-0012-T05 status: done priority: medium state_hub_task_id: "7ef422c6-1c5e-4d55-8325-d171ef8db5ad" ``` `evidence/CB-EV-0010-render-port.md`. Carry the standing items: the falsifiable prediction from CB-EV-0009 §4 that the trailing-3 meta share drops to 0% this pass, SH-3 at 0.0% for a sixth pass, and whether tier L at full weight produced anything the rolled-down pass would have missed — the CHAOS window's second data point, and the first from a non-override. **Done 2026-08-02.** [CB-EV-0010](../evidence/CB-EV-0010-render-port.md). - **Tier L deleted its own deliverable.** Review withdrew the port. A tier-S pass has no step 2 and would have shipped it. - **The prediction held: meta budget reads 0%**, published in advance and unfalsified. - **A correction, and a pattern.** CB-EV-0009 reported CB-WP-0011 at `45 responses / $4.23 / 0.094`; final is `71 / $7.02 / 0.099`. Still the cheapest pass, so its conclusion stands. But this is the **second** consecutive evidence file to report its own pass's cost low — a pass cannot measure its own cost, and every evidence file quoting its own is quoting a floor. - **Priced tier comparison, first ever on one subject**: 0.123 $/response at L against 0.099 at S — 24% more, for a pass that found errors of 25× and 85×. - **SH-3 at 0.0% for a sixth pass.** Oldest unargued number in the project; owed a declaration of its own. - **INTENT stage 1 stays open** — all four deliverables now exist, and none of the browser half has ever been run.