ui/phase-27: mass-based circles + cloud cluster + height fit
Three Phase-27 BattleViewer refinements on top of the radial scene:
1. Height fit. The viewer is pinned to `calc(100dvh − 80px)` so it
never pushes the in-game shell past the viewport. `.active-view`
gains `overflow: hidden` + flex column; `.viewer` becomes a
`flex: 1` child; the always-visible text log shrinks to a 30 dvh
ceiling with its own scroll. A global `body { margin: 0 }`
reset (added to `app.html`) plugs the 16 px the browser's
default body margin used to leak.
2. Mass-based ship-class circles. New `lib/battle-player/mass.ts`
carries the radius formula and the per-battle FullMass compute:
`MIN_RADIUS + (MAX_RADIUS − MIN_RADIUS) * sqrt(mass / max)`,
clamped to `[6, 24] px`. FullMass goes through the existing
wasm bridge (`emptyMass` → `carryingMass` → `fullMass`) — no
new wire fields. The viewer page resolves a
`(race, className) → ShipClassRef` lookup from the parent
GameReport's `localShipClass` + `otherShipClass` tables and
passes it to the viewer via context. Unknown class or
degenerate (weapons/armament) params fall back to MAX_RADIUS
so the bucket stays visible.
3. Cloud cluster layout. Cluster key shifts from per-group
`g.key` to `(raceId, className)` so tech-variants of the same
hull collapse into one visual bucket. The horizontal
classCircleX row is replaced by a Vogel sunflower spiral in
the local `(u, v)` basis — `u` points from the race anchor to
the planet, `v` is `u` rotated 90° clockwise. Buckets are
sorted by NumberLeft desc; the cluster anchor is pushed inward
by a quarter step so rank-0 sits closest to the planet. The
step is adaptive (`min(baseStep, MAX_CLUSTER_RADIUS / sqrt(N))`)
so clusters with many classes do not spill into neighbours.
Tests:
- Vitest: `radiusForMass` covering zero / max / quarter-mass /
out-of-range cases (6 cases).
- Playwright: new `battle-viewer.spec.ts` case asserts
`document.documentElement.scrollHeight - window.innerHeight ≤ 4`
at a 1280×720 desktop viewport. The existing fixture gains
`localShipClass` + `otherShipClass` so the lookup has data to
render proportional circles.
Docs: `ui/docs/battle-viewer-ux.md` rewrites the "Radial scene"
section (cloud layout, mass-based radius, height fit) and adds
a "Height fit" subsection. `docs/FUNCTIONAL.md` §6.5 (+ ru
mirror) get the one-line story about per-mass sizing, cluster
aggregation, and the viewport-locked layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-3
@@ -712,10 +712,18 @@ which forwards verbatim to the engine's
|
||||
|
||||
Visual model is radial: the planet sits at the centre, races are
|
||||
placed at equal angular spacing on an outer ring, and each race is
|
||||
rendered as a horizontal cluster of small ship-class circles
|
||||
labelled `<className>:<numLeft>`. Observer groups (`inBattle:
|
||||
rendered as a cloud of ship-class circles arranged on a Vogel
|
||||
sunflower spiral biased toward the planet (the largest group by
|
||||
NumberLeft sits closest to the planet, lighter buckets fan behind).
|
||||
Tech-variants of the same `(race, className)` collapse into one
|
||||
visual bucket labelled `<className>:<numLeft>`; per-class detail
|
||||
stays available in the Reports view. Circle radius scales with
|
||||
per-ship FullMass (range `[6, 24] px`, per-battle normalisation)
|
||||
so heavy ships visually dominate. Observer groups (`inBattle:
|
||||
false`) are not drawn. Eliminated races drop out and the survivors
|
||||
re-spread on the next frame.
|
||||
re-spread on the next frame. The viewer is pinned to the viewport
|
||||
(scene grows, log scrolls internally) so no page-level scroll
|
||||
appears.
|
||||
|
||||
Each frame is one protocol entry; the shot is drawn as a thin line
|
||||
from attacker to defender, red on `destroyed`, green otherwise.
|
||||
|
||||
Reference in New Issue
Block a user