6996a79286
* 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>
89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
// Coverage for the F8-12 sizing helpers in src/map/world.ts:
|
||
// `displayPointRadiusWorld` (the union of the pixel-space and the
|
||
// softened-by-zoom rules) and `displayStrokeWidthWorld` (pixel-space
|
||
// stroke widths). Both are pure math, so this file stays Pixi-free.
|
||
|
||
import { describe, expect, test } from "vitest";
|
||
|
||
import {
|
||
DEFAULT_POINT_RADIUS_PX,
|
||
PLANET_SIZE_ZOOM_ALPHA,
|
||
displayPointRadiusWorld,
|
||
displayStrokeWidthWorld,
|
||
} from "../src/map/world";
|
||
|
||
describe("displayPointRadiusWorld — pixel-space (pointRadiusPx)", () => {
|
||
test("returns pixel size divided by scale at scale=1", () => {
|
||
expect(displayPointRadiusWorld({ pointRadiusPx: 5 }, 1, 0.2)).toBe(5);
|
||
});
|
||
|
||
test("shrinks the world footprint as zoom grows", () => {
|
||
expect(displayPointRadiusWorld({ pointRadiusPx: 6 }, 3, 0.2)).toBeCloseTo(2);
|
||
});
|
||
|
||
test("falls back to DEFAULT_POINT_RADIUS_PX when the style is bare", () => {
|
||
expect(displayPointRadiusWorld({}, 2, 0.2)).toBeCloseTo(
|
||
DEFAULT_POINT_RADIUS_PX / 2,
|
||
);
|
||
});
|
||
|
||
test("zero-scale guard returns the raw pixel size", () => {
|
||
expect(displayPointRadiusWorld({ pointRadiusPx: 4 }, 0, 0.2)).toBe(4);
|
||
});
|
||
});
|
||
|
||
describe("displayPointRadiusWorld — softened by zoom (pointRadiusBasePx)", () => {
|
||
test("at scale=scaleRef the on-screen pixel size equals the base", () => {
|
||
const radius = displayPointRadiusWorld(
|
||
{ pointRadiusBasePx: 6 },
|
||
0.2,
|
||
0.2,
|
||
);
|
||
// 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 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(
|
||
Math.pow(10, PLANET_SIZE_ZOOM_ALPHA),
|
||
3,
|
||
);
|
||
});
|
||
|
||
test("ignores pointRadiusPx when pointRadiusBasePx is set", () => {
|
||
const r = displayPointRadiusWorld(
|
||
{ pointRadiusPx: 99, pointRadiusBasePx: 4 },
|
||
0.4,
|
||
0.2,
|
||
);
|
||
// 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);
|
||
});
|
||
});
|
||
|
||
describe("displayStrokeWidthWorld", () => {
|
||
test("returns width / scale at any zoom", () => {
|
||
expect(displayStrokeWidthWorld({ strokeWidthPx: 2 }, 1)).toBe(2);
|
||
expect(displayStrokeWidthWorld({ strokeWidthPx: 2 }, 4)).toBeCloseTo(0.5);
|
||
expect(displayStrokeWidthWorld({ strokeWidthPx: 2 }, 0.5)).toBeCloseTo(4);
|
||
});
|
||
|
||
test("falls back to 1 when strokeWidthPx is omitted", () => {
|
||
expect(displayStrokeWidthWorld({}, 2)).toBeCloseTo(0.5);
|
||
});
|
||
|
||
test("zero-scale guard returns the raw pixel value", () => {
|
||
expect(displayStrokeWidthWorld({ strokeWidthPx: 3 }, 0)).toBe(3);
|
||
});
|
||
});
|