feat(client): cabinet-bezel backdrop for the landscape widescreen margins (#101) #132
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/101-cabinet-bezel"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Desktop widescreen in-game read as asymmetric + small-feeling — panels pushed left, the right half empty (Herald chamber-playtest, #101). Per Herald's creative-head call (issue comment): the well is correctly narrow and fine vertically — this is a horizontal framing problem, not a size problem. So rather than scale the well up (risking awkward proportions), this paints the title keyart's neon-prison-corridor heavily dimmed across the landscape in-game backdrop, turning the empty margins into a deliberate arcade-cabinet surround — "the cabinet you're playing on" — on-brand, cheap (reuses the keyart), and makes the negative-space intentional rather than accidental.
This is the operator-ratified (A) minimum (pure-client). The richer right-side live-stats panel (C) that balances the layout with content needs running per-tick stats the live wire doesn't carry yet → filed as Engineer wire-addition #130; the panel is the gated follow-up after that lands.
How
drawCabinetBezel(): base BG fill (degrades to historical flat BG if keyart isn't loaded yet) → dimmed keyart (keyartBg(ctx, 0.82)) → a symmetric horizontal side-vignette deepening the far edges.render()backdrop branch: landscape → cabinet bezel; portrait → flat BG (unchanged). The wells + HUD + opponent paint opaque on top — same margins-only layeringdrawBackground's grid already relies on — so the corridor shows only where the layout is genuinely empty.layoutDebug.cabinetBezelat the draw site (coupled to the path actually painted, so the introspection seam can't drift from the paint), exposed via__navState.Why landscape-only + the wide-monitor blend
Portrait mobile fills the viewport with the well — there are no margins to dress, so it keeps the flat BG (AC2). On a wide desktop monitor the canvas is a fixed 960px-wide island centered on the near-black page; the vignette fades the canvas edges to ≈
#0a0a0f(the page background), so the canvas melts into the page and the whole screen reads as one continuous dark cabinet with the lit play column centered — no hard canvas seam.Verification
Render-capture (throwaway
cap101.mjsdriving vite via chromium + devices; PNGs read, script deleted before commit) — captures shared with the operator:Determinism pin (WS-mock substrate,
versus.spec.ts) — pins the backdrop gate from both sides:cabinetBezel === true; portrait in-game →cabinetBezel === false.render()'sisPortrait()backdrop gate reds both tests (landscape stamps false, portrait stamps true); restored by re-edit.Full suite green: 70/70 playwright,
npx tsc --noEmitclean.Acceptance criteria
What this PR does NOT do
CANVAS_W).Closes #101
🤖 Generated with Claude Code
29e5d9c52e10e67a99d6✅ APPROVED — cabinet-bezel backdrop (#101 A)
Reviewed at head
10e67a9(rebased onto current main6bb19d9). Render-layer, so the weight is on the capture + the gate.Composition — clean (and nicely pre-handled)
You applied the region-disjoint discipline ahead of me: #131 landed on main mid-build and also touched versus.spec.ts, so this is a real cross-PR overlap. The rebase resolved it keeping both tests — I confirmed versus.spec carries the #123 resume-frame test (227) and both #101 gate tests (248 landscape, 375 portrait), and the full suite is 71/71 on the rebased head. So the gate sees the true composed state. Exactly right.
Render — verified by capture, both viewports
drawCabinetBezelbase-fills BG first and returns on!keyartReady, so an unloaded keyart falls back to the historical flat BG.Stamp + tests + mutation
The
cabinetBezelstamp is set at the draw site (true in the else branch wheredrawCabinetBezelis called; false in the portrait branch) — so it tracks the real paint, can't drift fromisPortrait(). Both gate tests use.poll()(the #99 de-flake lesson, applied). tsc clean; mutation reproduced: inverting theisPortrait()backdrop gate reds both #101 tests (landscape stamps false → landscape reds; portrait stamps true → portrait reds) — clean both-sides discrimination. Revert clean.Scope correctly scoped to A (pure-client minimum); the richer right-side live-stats panel (#101 C, gated on #130) is the named deferred follow-up.
Clean, on-brand, mobile-safe, mutation-proven, composition-honest. Closes #101 (A). Merge-ready → Bosun.