Measured with dep-weight.py's own method so the numbers subtract. Marginal cost against the 23-crate base graph, not headline size: egui+eframe 2,782,849 742x headroom wgpu+winit 1,741,979 465x softbuffer+tiny-skia 1,147,081 306x ratatui+crossterm 1,067,013 285x macroquad 480,501 128x HTML/SVG emission 0 0x ratatui costing more than macroquad is the surprise: rustix pulls linux-raw-sys at 479,901 lines. Separately, AM-4a counts proc-macro crates that never reach a binary: 89,048 lines, 36.2% of the shipped-runtime figure, syn alone 66,916. Real headroom is 92,798, not the 3,750 this repo has been citing. The recommendation is unchanged by that correction — macroquad is 5.2x over even at 92,798 — which is the only condition under which a survey may propose correcting a number in its own favour. The metric fix is filed separately, not bundled with the decision it unblocks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.7 KiB
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 testand 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
cb-render-api— a canonical port over the existingProjection: state in, drawable primitives out, proposed commands back. No assimilated types cross it (ArchitectureBlueprint §Never leak).cb-render-null— the reference no-op, so tests and agents need neither GPU nor browser.cb-render-html— the first real implementation, marginal AM-4a cost zero.cb-render-wgpuis stage 2's, and is where the port interface gets its second use and only then becomes canonical.- 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.