perf(ui): F8-12 — pixel-space planet sizing + single-copy label/outline layers (#55)
Tests · UI / test (push) Has been cancelled
Tests · UI / test (pull_request) Successful in 3m20s

* Planet size formula moves to pixel-space:
  `pointRadiusBasePx = 2 + 2 * cbrt(size / SIZE_NORMALIZER)`. The
  on-screen disc now reads ~4-7 px at the reference zoom regardless
  of how large the world rectangle is — the previous `world-units`
  formulation blew up on small maps and made Source-class planets
  swallow their neighbours.
* Labels + outlines live in the origin copy only. The 9× replication
  across torus copies was the dominant cost on a 100+ planet map
  (Pixi.Text creation + Graphics rebuilds on every zoom step); the
  origin-copy layout is what the camera-wrap listener guarantees
  the user actually sees.
* `setPlanetLabels` and `setPlanetOutlines` skip Pixi-object
  rebuilds when the input fingerprint is unchanged — toggle flips
  and selection changes now keep the existing Text / Graphics
  instances alive and only repaint the affected pieces.
* `renderer.md` updated to the new contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-28 00:39:19 +02:00
parent 75a4211373
commit 6996a79286
6 changed files with 218 additions and 167 deletions
+18 -13
View File
@@ -32,21 +32,24 @@ describe("displayPointRadiusWorld — pixel-space (pointRadiusPx)", () => {
});
});
describe("displayPointRadiusWorld — softened by zoom (pointRadiusWorld)", () => {
test("at scale=scaleRef the visible radius equals the base radius", () => {
describe("displayPointRadiusWorld — softened by zoom (pointRadiusBasePx)", () => {
test("at scale=scaleRef the on-screen pixel size equals the base", () => {
const radius = displayPointRadiusWorld(
{ pointRadiusWorld: 6 },
{ pointRadiusBasePx: 6 },
0.2,
0.2,
);
expect(radius).toBeCloseTo(6);
// world units → 6 (base px) / 0.2 (scale) = 30
expect(radius).toBeCloseTo(30);
// confirm pixel-space: world * scale ≈ 6.
expect(radius * 0.2).toBeCloseTo(6);
});
test("zooming in grows the radius sub-linearly", () => {
const r1 = displayPointRadiusWorld({ pointRadiusWorld: 6 }, 0.2, 0.2);
const r10 = displayPointRadiusWorld({ pointRadiusWorld: 6 }, 2.0, 0.2);
// On-screen pixel size grows by scale^α (α = 0.33) instead of
// linearly: 10x zoom~10^0.33 ≈ 2.15x growth.
test("zooming in grows the on-screen pixel size sub-linearly", () => {
const r1 = displayPointRadiusWorld({ pointRadiusBasePx: 6 }, 0.2, 0.2);
const r10 = displayPointRadiusWorld({ pointRadiusBasePx: 6 }, 2.0, 0.2);
// On-screen pixel size grows by scale^α (α = 0.33): 10x zoom
// → 10^0.33 ≈ 2.15x growth.
const onScreenAt1 = r1 * 0.2;
const onScreenAt10 = r10 * 2.0;
expect(onScreenAt10 / onScreenAt1).toBeCloseTo(
@@ -55,14 +58,16 @@ describe("displayPointRadiusWorld — softened by zoom (pointRadiusWorld)", () =
);
});
test("ignores pointRadiusPx when pointRadiusWorld is set", () => {
test("ignores pointRadiusPx when pointRadiusBasePx is set", () => {
const r = displayPointRadiusWorld(
{ pointRadiusPx: 99, pointRadiusWorld: 4 },
{ pointRadiusPx: 99, pointRadiusBasePx: 4 },
0.4,
0.2,
);
// World radius is the base softened by (0.4/0.2)^(α-1).
expect(r).toBeCloseTo(4 * Math.pow(2, PLANET_SIZE_ZOOM_ALPHA - 1), 4);
// On-screen pixel size: 4 * (0.4 / 0.2)^α = 4 * 2^0.33
// In world units: (4 * 2^0.33) / 0.4.
const expected = (4 * Math.pow(2, PLANET_SIZE_ZOOM_ALPHA)) / 0.4;
expect(r).toBeCloseTo(expected, 4);
});
});