"""Render the four-arm comparison as a self-contained booth page.""" import json, html, re, sys, statistics as st from pathlib import Path SP = Path("/tmp/claude-1000/-home-lkraven-development-eshpfi-management/d4d5ad1b-76a2-4698-9684-ea6045592d22/scratchpad") d = json.loads((SP / "booth-cells.json").read_text(encoding="utf-8")) OUT = Path(sys.argv[1]) COLS = [("voices-base-1200", "control", "unadapted carrier"), ("lv-bronte", "Brontë", "lv-bronte · ckpt475"), ("lv-yarros", "Yarros", "lv-yarros"), ("lv-hemingway", "Hemingway", "lv-hemingway · ckpt850")] SEEDS = [1234, 5678] def split(t): if "" in t and "" in t: return t.split("", 1)[1].split("", 1)[0], t.split("", 1)[1].strip() if "" in t: return t.split("", 1)[1], "" return "", t.strip() def paras(text): out = [] for p in re.split(r"\n\s*\n", text.strip()): p = " ".join(p.split()) if p: out.append("

" + html.escape(p) + "

") return "\n".join(out) or '

— the budget ran out before any prose was written —

' # headline numbers, computed not asserted short_base = [len(split(d["cells"][f"voices-base|{b}|{s}"]["text"])[1].split()) for b, _ in d["beats"] for s in SEEDS] stats = {} for model, label, _ in COLS: w = [len(split(d["cells"][f"{model}|{b}|{s}"]["text"])[1].split()) for b, _ in d["beats"] for s in SEEDS] th = [len(split(d["cells"][f"{model}|{b}|{s}"]["text"])[0].split()) for b, _ in d["beats"] for s in SEEDS] stats[model] = (int(st.median(w)), int(st.median(th)), sum(1 for x in w if x == 0)) blocks = [] for bid, beat in d["beats"]: cells = [] for model, label, sub in COLS: panes = [] for s in SEEDS: th, prose = split(d["cells"][f"{model}|{bid}|{s}"]["text"]) tw = len(th.split()) badge = (f'planned {tw}w first' if tw > 0 else 'straight to prose') panes.append( f'
' f'
seed {s} · {len(prose.split())}w {badge}
' f'
{paras(prose)}
') cells.append(f'
' f'

{html.escape(label)}' f'{html.escape(sub)}

' + "".join(panes) + "
") blocks.append(f'

{bid}' f'{html.escape(beat)}

' + "".join(cells) + "
") rows = "".join( f"{html.escape(l)}{stats[m][0]}{stats[m][1]}" f"{stats[m][2]}/12" for m, l, _ in COLS) HTML = f""" Four voices, one beat

Four voices, one beat

The same six beats and the same author-neutral prompt through the unadapted carrier and the three lv-* LoRA adapters, all served from one process on vllm-voices (fv-ml1 GPU 0 :8027). Only the adapter changes.

system — {html.escape(d["system"])}

user — BEAT: <the beat>
  • The prompt names no author, deliberately. Each adapter trained under a prompt naming its own — driving all four with any one of those would hand that arm a hint the others do not get, and the page would be measuring the prompt.
  • One disclosed asymmetry. Brontë and Hemingway trained on “a SHORT PASSAGE … may run to several paragraphs”; Yarros trained on “ONE paragraph”. The neutral prompt uses neither qualifier, so it sits slightly off-distribution for all three rather than for one.
  • The control gets a 4× larger token budget, and that is the fair thing to do. At the gate's 320-token budget the carrier spends 181–257 words thinking and 5 of 12 cells never reach the prose at all. The three adapters emit an empty think block in 12 of 12 — they learned to skip it. Publishing the starved control would conflate voice with budget discipline, so the control here runs at 1200 tokens and finishes every time.
  • Two seeds per cell, because one sample of a sampled process is an anecdote. Sampler matches the gate harness: temperature 0.9, top_p 0.95.
  • These 36 adapter generations were checked for verbatim reuse before this page went up, each arm against its own training corpus (scoring one author against another returns a clean zero that only means “different book”). Brontë 0, Yarros 0, Hemingway 2 of 12, longest run 8 words — and the run is “I don't know, I don't know”. Nothing on this page reproduces anything worth reproducing.
  • This is a reading, not a measurement. The numbers that decide anything are in the gate: persistent-memory.d/2026-09-17-lv-hemingway-gate.md.
{rows}
armmedian prosemedian planningnever wrote prose
column order is fixed: control · Brontë · Yarros · Hemingway
{''.join(blocks)}
""" OUT.write_text(HTML, encoding="utf-8") print(f"wrote {OUT} ({len(HTML):,} bytes)") for m, l, _ in COLS: print(f" {l:<11} median prose {stats[m][0]:>4}w · median planning {stats[m][1]:>4}w · empty {stats[m][2]}/12")