184 lines
8.7 KiB
Markdown
184 lines
8.7 KiB
Markdown
|
|
# 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.
|