db415f8aa4
Stand up the vector map renderer in ui/frontend/src/map/ on top of PixiJS v8 + pixi-viewport@^6. Torus mode renders nine container copies for seamless wrap; no-wrap mode pins the camera at world bounds and centres on an axis when the viewport exceeds the world along that axis. Hit-test is a brute-force pass with deterministic [-priority, distSq, kindOrder, id] ordering and torus-shortest distance, validated by hand-built unit cases. The development playground at /__debug/map exposes a window debug surface for the Playwright spec, which forces WebGPU on chromium-desktop, WebGL on webkit-desktop, and accepts the auto-picked backend on mobile projects. Algorithm spec lives in ui/docs/renderer.md, which also pins the new deprecation status of galaxy/client (the entire Fyne client module, including client/world). client/world/README.md and the Phase 9 stub in ui/PLAN.md gain matching deprecation banners. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
101 lines
2.5 KiB
TypeScript
101 lines
2.5 KiB
TypeScript
// Fixture data for the map renderer playground and visual checks.
|
|
//
|
|
// sampleWorld() returns a 1000-primitive deterministic world built
|
|
// with a small linear-congruential RNG so the layout is reproducible
|
|
// across runs and across machines. The mix of primitive kinds
|
|
// exercises all draw paths: many points (planets), several stroked
|
|
// circles (orbits), several filled circles (zones), and a handful of
|
|
// lines (routes).
|
|
|
|
import {
|
|
type CirclePrim,
|
|
type LinePrim,
|
|
type PointPrim,
|
|
type Primitive,
|
|
World,
|
|
} from "./world";
|
|
|
|
const WORLD_W = 4000;
|
|
const WORLD_H = 4000;
|
|
|
|
// Tiny deterministic RNG so fixtures stay byte-identical regardless
|
|
// of host platform. Seed values picked to give a visually pleasant
|
|
// distribution; not cryptographically meaningful.
|
|
function lcg(seed: number): () => number {
|
|
let s = seed >>> 0;
|
|
return () => {
|
|
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
return s / 0x1_0000_0000;
|
|
};
|
|
}
|
|
|
|
// sampleWorld constructs the playground world. The result is stable
|
|
// across calls — it allocates fresh arrays but the data is identical.
|
|
export function sampleWorld(): World {
|
|
const rand = lcg(0x5eed1234);
|
|
const primitives: Primitive[] = [];
|
|
let nextId = 0;
|
|
|
|
// 950 stars (points).
|
|
for (let i = 0; i < 950; i++) {
|
|
const star: PointPrim = {
|
|
kind: "point",
|
|
id: nextId++,
|
|
x: rand() * WORLD_W,
|
|
y: rand() * WORLD_H,
|
|
priority: 1,
|
|
style: { pointRadiusPx: 2 + Math.floor(rand() * 3) },
|
|
hitSlopPx: 0,
|
|
};
|
|
primitives.push(star);
|
|
}
|
|
|
|
// 30 stroked circles (orbits / influence rings).
|
|
for (let i = 0; i < 30; i++) {
|
|
const orbit: CirclePrim = {
|
|
kind: "circle",
|
|
id: nextId++,
|
|
x: rand() * WORLD_W,
|
|
y: rand() * WORLD_H,
|
|
radius: 80 + rand() * 220,
|
|
priority: 2,
|
|
style: { strokeWidthPx: 1, strokeAlpha: 0.6 },
|
|
hitSlopPx: 0,
|
|
};
|
|
primitives.push(orbit);
|
|
}
|
|
|
|
// 10 filled translucent circles (zones).
|
|
for (let i = 0; i < 10; i++) {
|
|
const zone: CirclePrim = {
|
|
kind: "circle",
|
|
id: nextId++,
|
|
x: rand() * WORLD_W,
|
|
y: rand() * WORLD_H,
|
|
radius: 150 + rand() * 250,
|
|
priority: 0,
|
|
style: { fillColor: 0x37474f, fillAlpha: 0.25 },
|
|
hitSlopPx: 0,
|
|
};
|
|
primitives.push(zone);
|
|
}
|
|
|
|
// 10 lines (routes between random anchor points).
|
|
for (let i = 0; i < 10; i++) {
|
|
const route: LinePrim = {
|
|
kind: "line",
|
|
id: nextId++,
|
|
x1: rand() * WORLD_W,
|
|
y1: rand() * WORLD_H,
|
|
x2: rand() * WORLD_W,
|
|
y2: rand() * WORLD_H,
|
|
priority: 3,
|
|
style: { strokeWidthPx: 1, strokeAlpha: 0.8 },
|
|
hitSlopPx: 0,
|
|
};
|
|
primitives.push(route);
|
|
}
|
|
|
|
return new World(WORLD_W, WORLD_H, primitives);
|
|
}
|