feat(ui): F8-12 — owner feedback round 2 (#55)
Tests · UI / test (push) Has been cancelled
Tests · UI / test (pull_request) Successful in 3m18s

* Bug fix: theme flip no longer leaves planets oversized. The
  camera-preserving remount now calls a new
  `RendererHandle.refreshCameraDerivedDraws` explicitly after the
  manual moveCenter/setZoom pair so the post-mount geometry tracks
  `viewport.scaled` even if pixi-viewport's `'zoomed'` listener
  races the next Ticker tick.
* Doc #3: clicks on a planet label route through the same hit-test
  path as a click on the disc. The label `Container` now has a
  pointer hit area sized to the text + frame padding; pointertap
  simulates a click at the planet centre, so selection and
  pick-mode resolution behave identically.
* Doc #4: battle X-crosses + cargo arrowhead wings grow
  sub-linearly with zoom (PLANET_SIZE_ZOOM_ALPHA). New
  `Style.softLengthAnchor` ('center' / 'start') makes the renderer
  treat the recorded endpoints as the geometry "at the reference
  scale" and rescale around the midpoint (X-cross) or the start
  endpoint (arrow wings). Arrowhead base length is halved from 6
  to 3 world units to match the owner's "in half" request.
* Doc #5: picker overlay loses the anchor ring at the source, the
  cursor line drops to a cargo-route-thin 0.6 px stroke, and the
  hover ring around the destination is replaced by a planet-style
  outline (visible disc + 1 px padding) in the `pickHighlight`
  accent — so candidate destinations read like selection in warm
  yellow.
* Doc #6: regression test pins the in-disc hit zone.
* Perf #1: camera-driven redraws are throttled onto the next
  Ticker tick. A rapid wheel / pinch burst now coalesces into at
  most one `clear() + redraw` pass per painted frame, which keeps
  the 500-planet map responsive on zoom and toggle flips.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-28 09:40:20 +02:00
parent 6c3cd25476
commit eb5018342e
10 changed files with 334 additions and 61 deletions
+22 -8
View File
@@ -137,17 +137,31 @@ describe("buildCargoRouteLines", () => {
);
const lines = buildCargoRouteLines(report);
expect(lines.length).toBe(12);
const styleByPriority = new Map<number, typeof lines[number]["style"]>();
// F8-12 / #4 follow-up: shafts and wings now use different
// Style objects so the arrowhead wings can carry
// `softLengthAnchor: "start"`. Colour / priority remain shared
// across both, which is what the de-dupe loop here verifies.
const colourByPriority = new Map<number, number | undefined>();
const softLengthByLineId = new Map<number, string | undefined>();
for (const line of lines) {
const existing = styleByPriority.get(line.priority);
if (existing === undefined) styleByPriority.set(line.priority, line.style);
else expect(existing).toBe(line.style);
const existing = colourByPriority.get(line.priority);
if (existing === undefined) {
colourByPriority.set(line.priority, line.style.strokeColor);
} else {
expect(line.style.strokeColor).toBe(existing);
}
softLengthByLineId.set(line.id & 0xf, line.style.softLengthAnchor);
}
// Shaft (offset 0) stays linear; wings (offsets 1/2) get the
// new softening anchor so the arrowhead grows sub-linearly.
expect(softLengthByLineId.get(0)).toBeUndefined();
expect(softLengthByLineId.get(1)).toBe("start");
expect(softLengthByLineId.get(2)).toBe("start");
// Default (dark) palette colours, one per load type.
expect(styleByPriority.get(8)?.strokeColor).toBe(DARK_THEME.routeCol);
expect(styleByPriority.get(7)?.strokeColor).toBe(DARK_THEME.routeCap);
expect(styleByPriority.get(6)?.strokeColor).toBe(DARK_THEME.routeMat);
expect(styleByPriority.get(5)?.strokeColor).toBe(DARK_THEME.routeEmp);
expect(colourByPriority.get(8)).toBe(DARK_THEME.routeCol);
expect(colourByPriority.get(7)).toBe(DARK_THEME.routeCap);
expect(colourByPriority.get(6)).toBe(DARK_THEME.routeMat);
expect(colourByPriority.get(5)).toBe(DARK_THEME.routeEmp);
});
test("uses the supplied palette's stroke colours", () => {