# CB-RES-0006 — the render port, and what a 2D table costs CB-WP-0012 T01. Step 1 of the inner loop. Measured 2026-08-02 at `331e7e9`. The question: INTENT stage 1 needs `cb-render-api` and a first real implementation. AM-4a reports **3,750 lines of headroom** (246,250 of a 250,000 target). Is that a real obstacle, or an artifact of the instrument? Both, and not in the proportions I expected. --- ## 1. Method Every figure below comes from the same method `tools/dep-weight.py` uses for AM-4a: resolve a real dependency graph with `cargo tree --edges normal --prefix none`, drop path dependencies, and count `\n` in every `.rs` file of the vendored source. Reusing the method verbatim is the point — a survey that measures candidates one way and the budget another produces two numbers that cannot be subtracted. Probe crates were built in a scratch directory, one per candidate, and resolved against the live registry. The script is `$CLAUDE_JOB_DIR/tmp/survey/measure.py`; it is not committed, because a throwaway probe promoted to a tool is how the second-use rule gets broken by accident. **Marginal, not total.** A candidate's headline size overstates its cost by whatever it already shares with the base graph. What AM-4a charges is the delta. ## 2. What a 2D table costs Base: `games-ground --no-default-features`, 23 third-party crates, 246,250 lines. | candidate | total | shared with base | **marginal** | vs 3,750 headroom | |---|---:|---:|---:|---:| | `egui` + `eframe` | 2,946,121 | 163,272 | **2,782,849** | 742× | | `wgpu` + `winit` | 1,866,820 | 124,841 | **1,741,979** | 465× | | `softbuffer` + `tiny-skia` + `winit` | 1,271,922 | 124,841 | **1,147,081** | 306× | | `ratatui` + `crossterm` | 1,146,363 | 79,350 | **1,067,013** | 285× | | `egui` alone (no windowing) | 778,067 | 110,542 | **667,525** | 178× | | `macroquad` | 480,729 | 228 | **480,501** | 128× | | HTML/SVG emission, no Rust toolkit | — | — | **0** | 0× | The cheapest candidate that opens a window costs **128 times** the entire remaining budget. This is not a near miss to be negotiated; it is two orders of magnitude. **A surprise worth recording:** `ratatui` — a *terminal* library, the option one reaches for expecting it to be the cheap one — costs more than `macroquad`, a 2D game framework with a GPU backend. The reason is `linux-raw-sys` at 479,901 lines and `libc` at 129,990, pulled through `rustix`. Neither is code anyone audits by hand; both are largely generated constant and type definitions. Which raises the next question. ## 3. The instrument is wrong, and has been all along AM-4a counts the `--edges normal` graph. That graph includes **proc-macro crates**, which run in the compiler and never reach a shipped binary. | `games-ground --no-default-features` | crates | lines | |---|---:|---:| | as AM-4a measures it | 23 | **246,250** | | excluding proc-macro crates (`--edges normal,no-proc-macro`) | 18 | **157,202** | | the difference | 5 | **89,048** (36.2%) | The five are `syn`, `quote`, `proc-macro2`, `unicode-ident` and `serde_derive`. `syn` alone is 66,916 lines — the single largest entry in the shipped-runtime figure, and it is a parser for Rust source that exists only at build time. **So 36.2% of what AM-4a calls "what a game ships" is not shipped.** Under a corrected metric the headroom is not 3,750 but **92,798**. Every pass that has cited the 3,750 figure — including CB-WP-0011's own reasoning for deferring this declaration — cited a number that was wrong in the conservative direction. **This does not change the recommendation, and that is the point of checking it before recommending.** Under the corrected metric `macroquad` still costs 5.2× the available headroom, and every other windowed candidate is worse. The dependency argument survives the correction of the number it rests on, which is the only condition under which a survey may propose correcting a number in its own favour. The correction is proposed as a separate, narrow change — the metric should measure what ships — and it must not be bundled with the render decision, because a budget correction that arrives attached to the request it unblocks is indistinguishable from motivated reasoning even when it is right. ## 4. What stage 1 actually asks for > *card/token/hand/relationship-graph visualization, drag-to-propose, > debug inspector, hot-seat play* Read plainly: draw a table, let a human point at it, and let several humans take turns at one machine. It does **not** ask for a GPU, a frame loop, physics, or 60fps. Those are stage 2 (*"Physical 3D tabletop — wgpu renderer, Rapier-backed physics"*), and stage 2 is where the 1.7M lines of `wgpu` + `winit` get argued for on their merits. Buying a GPU toolkit now would mean paying stage 2's dependency bill to satisfy stage 1's requirements, one stage early, against a budget that cannot cover it, and before the port interface has ever had a second implementation to be validated against. ## 5. The option that is not a Rust toolkit Emit HTML with inline SVG and inline JavaScript; open it in the browser the machine already has. - **visualization** — SVG draws cards, tokens and a relationship graph natively, and the relationship graph is the one element every Rust 2D toolkit would have made us hand-roll anyway. - **drag-to-propose** — pointer events in the emitted page. - **debug inspector** — already shipped (CB-WP-0011), and its renderer is already a total function from a projection to text; HTML is a second output format for the same walk. - **hot-seat play** — one browser tab, seats taking turns, the same commit/reveal the CLI player uses. **Marginal AM-4a cost: zero.** Not "small" — zero. Emitting HTML is string formatting; the 2,897-line `svg` crate measured above is not needed and is not proposed. The input path costs no dependency either: a loopback HTTP/1.1 listener sufficient for `GET /` and `POST /command` is `std::net::TcpListener` plus roughly 250 lines of our own code, and our own code is not what AM-4a governs. This is the central rule applied literally — *own the semantics; assimilate the implementation*. The browser is the most thoroughly assimilated 2D renderer available, and it is the one implementation we are guaranteed not to have to vendor, audit, or keep on a target. **Own-code cost is a real cost.** ~250 lines of hand-rolled HTTP is code we own, test and carry, and hand-rolled HTTP has a bad security history. It listens on loopback only and speaks to a page it emitted itself, which bounds the exposure but does not erase it. That trade — 250 own lines against 480,501 third-party ones — is the decision ADR-0007 has to make explicitly rather than inherit from this survey. ## 6. What this cannot do Stated concretely, because the adversarial review (T02) should not have to discover it: - **No frame loop.** Animation, drag *feedback* at pointer-event rates, and anything requiring sustained redraw are the browser's problem, not the port's — the Rust side emits state and receives commands. If stage 1 later wants tweened card motion, it is written in the emitted JS, in a language this repo otherwise does not use and has no gates for. - **No offline binary.** A game that "ships" ships a process that wants a browser. For a headless-first rules engine that is nearly free; for a consumer product at stage 4 it is not. - **A second language enters the build.** JavaScript in emitted strings is untested by `cargo test` and invisible to every gate this repo has. This is the strongest argument against, and the one T02 should press. - **It is not stage 2's renderer.** Nothing here carries forward to wgpu except the port interface — which is the argument *for* doing it, under the second-use rule, but only if the interface is written to be implemented twice rather than written around HTML. ## 7. Recommendation 1. **`cb-render-api`** — a canonical port over the existing `Project`ion: state in, drawable primitives out, proposed commands back. No assimilated types cross it (ArchitectureBlueprint §Never leak). 2. **`cb-render-null`** — the reference no-op, so tests and agents need neither GPU nor browser. 3. **`cb-render-html`** — the first real implementation, marginal AM-4a cost zero. 4. **`cb-render-wgpu` is stage 2's**, and is where the port interface gets its second use and only then becomes canonical. 5. **AM-4a's proc-macro defect is filed separately** and is not a precondition for any of the above. **The honest summary:** the constraint that looked like it would force this decision turns out to be overstated by 36%, and the decision does not change. The recommendation is what it would have been at 92,798 lines of headroom, which is the only reason it is worth trusting at 3,750.