From 17a3afd5e99777a38eb89e4c4d2efa27cabd9013 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 13 May 2026 16:44:46 +0200 Subject: [PATCH] ui/phase-27: viewer polish + phantom-destroy clamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine BattleViewer refinements from the latest review pass: 1. Mass radii were uniform in synthetic mode because `+layout.svelte` skipped `loadCore()` on the synthetic branch. The wasm bridge to `pkg/calc/ship.go` now boots in both modes so `computeBattleGroupMass` resolves a real FullMass and `radiusForMass` produces a per-battle scale. 2. Phantom-destroy clamp in `buildFrames`. Legacy emitters (KNNTS041 planet #7) log many more `Destroyed` lines against a group than the group's initial population — at frame 406 of 2317 the race totals previously hit zero on phantom shots and the scene blanked while playback continued silently. We now only shrink the per-group remaining count and the race totals when the group still has ships. The line still draws on phantom frames; only the counters stay sane. 3. Vogel sunflower positions are now reassigned by inward dot product before being handed to ranks: the rank-0 bucket — the one with the largest initial ship count — always lands at the most-inward spiral slot. The previous quarter-step anchor bias was too weak; ranks r ≥ 2 routinely overtook rank-0 toward the planet. The anchor offset is gone. 4. Bucket order inside a cluster is locked at battle start by each bucket's *initial* ship count (`num`), not its live `numLeft`. The position of every class circle stays put for the whole battle; only the label number changes as ships die. 5. Shot line + defender flash blink on a per-frame timer during play. The line stays on for the first 90 % of frame duration, off for the last 10 %, so two consecutive shots from the same attacker on the same defender look like two distinct pulses. On pause the line and flash stay drawn for inspection. 6. The defender's class circle now flashes red (destroyed) or green (shielded) in sync with the shot line, so the eye catches *who* was hit, not just where the line lands. 7. Battle log rows are buttons. Click / Enter / Space pauses playback and seeks to that shot. The list also auto-scrolls the current row into view so the highlight does not race off the bottom on long battles. 8. Race labels now sit above the cloud's bounding top instead of a fixed offset, so a dense cluster does not swallow its own race name. 9. Planet glyph + label switch to neutral grey (`#2a2f40` / `#4a5066` / `#6d7388`), keeping the planet "in the background" rather than competing with the combatants. Step-back icon switched to `◀︎◀︎` to mirror step-forward. Tests: two new Vitest cases cover the phantom-destroy clamp (single-race wipe, mixed-class race survives a class wipe). The existing 642 Vitest tests stay green; all four `battle-viewer` Playwright cases pass. Docs: `ui/docs/battle-viewer-ux.md` rewrites the cluster section (locked order + Vogel reassignment), adds Playback Details (blink + flash semantics), and a Phantom Destroys section explaining the clamp. Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/docs/battle-viewer-ux.md | 35 ++++ .../src/lib/battle-player/battle-scene.svelte | 168 +++++++++++++----- .../lib/battle-player/battle-viewer.svelte | 89 ++++++++-- .../battle-player/playback-controls.svelte | 2 +- ui/frontend/src/lib/battle-player/timeline.ts | 24 ++- .../src/routes/games/[id]/+layout.svelte | 14 +- ui/frontend/tests/battle-player.test.ts | 122 +++++++++++++ 7 files changed, 384 insertions(+), 70 deletions(-) diff --git a/ui/docs/battle-viewer-ux.md b/ui/docs/battle-viewer-ux.md index c82212a..956933a 100644 --- a/ui/docs/battle-viewer-ux.md +++ b/ui/docs/battle-viewer-ux.md @@ -47,6 +47,15 @@ hull. The per-bucket label `:` sums NumberLeft across the underlying groups; per-tech detail is available in the Reports view (Foreign Ship Classes / My Ship Types). +Bucket order inside a cluster is **locked at battle start** by the +initial ship count (`num` summed across tech variants, descending). +As ships die during playback only the label number changes — every +bucket keeps its slot in the Vogel spiral, so the user does not see +the cluster reshuffle when a class empties. Vogel positions are +then reassigned per rank by their inward distance toward the +planet, so the rank-0 bucket (the largest at battle start) always +sits at the most-inward spiral slot. + Circle radius scales with per-ship FullMass (Empty + Carrying via the per-ship `LoadQuantity`). The viewer resolves a `(race, className) → ShipClassRef` lookup from the surrounding @@ -95,6 +104,32 @@ the log instead of watching the SVG. The list is always present and never hidden, satisfying the original Phase 27 acceptance "the same data is accessible as a static text log". +Each log row is also a ` + {/each} @@ -180,11 +216,26 @@ is logically isolated: feed it any `BattleReport` matching min-height: 0; } .log li { - padding: 0.15rem 0; border-bottom: 1px solid #1c2240; } - .log li[data-current="true"] { + .log-row-btn { + display: block; + width: 100%; + text-align: left; + padding: 0.15rem 0.4rem; + background: transparent; + border: 0; + color: inherit; + font: inherit; + cursor: pointer; + } + .log-row-btn:hover, + .log-row-btn:focus-visible { + background: #131a36; + } + .log li[data-current="true"] .log-row-btn { color: #ffe27a; font-weight: 600; + background: #1a2240; } diff --git a/ui/frontend/src/lib/battle-player/playback-controls.svelte b/ui/frontend/src/lib/battle-player/playback-controls.svelte index f5ef0ea..87f40c6 100644 --- a/ui/frontend/src/lib/battle-player/playback-controls.svelte +++ b/ui/frontend/src/lib/battle-player/playback-controls.svelte @@ -57,7 +57,7 @@ already at its end. disabled={frameIndex === 0} aria-label={i18n.t("game.battle.controls.step_backward")} data-testid="battle-control-step-back" - >◀︎ + >◀︎◀︎