feat(ui): F8-12 — smooth planet discs + ?debug=1 overlay (#55)
* Planet discs (and every other circle the renderer draws — outlines, picker hover ring, reach / bombing rings, etc.) trace a fixed 32-segment polygon instead of leaning on Pixi's adaptive bezier subdivision. PixiJS v8 picks the segment count from the world-space radius, which collapsed to 6-8 segments once the parent container's scale climbed — so the planet read as a visible polygon at high zoom. The custom path stays cheap (~64 floats per disc) and gives a perceptually round silhouette at every zoom level. * Opt-in dev overlay activated by `?debug=1` in the URL. A small bottom-left panel shows the current `scale`, the "whole world fits" reference scale, the current zoom ratio (scale / scale_ref), and the world-units rectangle visible in the viewport — so the owner can decide what `maxScale` to clamp to on the next iteration without guessing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,50 @@ preference the store already manages.
|
||||
let containerEl: HTMLDivElement | null = $state(null);
|
||||
let mountError: string | null = $state(null);
|
||||
|
||||
// F8-12 follow-up: an opt-in technical overlay activated by adding
|
||||
// `?debug=1` to the URL. Shows the current camera scale and the
|
||||
// world-units rectangle currently visible inside the viewport — so
|
||||
// the owner can decide what to clamp `maxScale` to once it lands.
|
||||
const debugOverlayEnabled = (() => {
|
||||
if (typeof window === "undefined") return false;
|
||||
return new URLSearchParams(window.location.search).get("debug") === "1";
|
||||
})();
|
||||
let debugInfo: {
|
||||
scale: number;
|
||||
scaleRef: number;
|
||||
viewWorldWidth: number;
|
||||
viewWorldHeight: number;
|
||||
} | null = $state(null);
|
||||
let debugFrame: number | null = null;
|
||||
function startDebugLoop(): void {
|
||||
if (!debugOverlayEnabled) return;
|
||||
const tick = (): void => {
|
||||
if (handle !== null) {
|
||||
const camera = handle.getCamera();
|
||||
const vp = handle.getViewport();
|
||||
const safeScale = camera.scale > 0 ? camera.scale : 1;
|
||||
const worldW = store?.report?.mapWidth ?? 1;
|
||||
const worldH = store?.report?.mapHeight ?? 1;
|
||||
debugInfo = {
|
||||
scale: camera.scale,
|
||||
scaleRef: Math.max(vp.widthPx / worldW, vp.heightPx / worldH),
|
||||
viewWorldWidth: vp.widthPx / safeScale,
|
||||
viewWorldHeight: vp.heightPx / safeScale,
|
||||
};
|
||||
} else {
|
||||
debugInfo = null;
|
||||
}
|
||||
debugFrame = requestAnimationFrame(tick);
|
||||
};
|
||||
debugFrame = requestAnimationFrame(tick);
|
||||
}
|
||||
function stopDebugLoop(): void {
|
||||
if (debugFrame !== null) {
|
||||
cancelAnimationFrame(debugFrame);
|
||||
debugFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
let handle: RendererHandle | null = null;
|
||||
let hitLookup = new Map<PrimitiveID, HitTarget>();
|
||||
// currentCategories / currentPlanetDependents are populated by
|
||||
@@ -797,6 +841,7 @@ preference the store already manages.
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
startDebugLoop();
|
||||
onResize = (): void => {
|
||||
if (handle === null || containerEl === null) return;
|
||||
handle.resize(containerEl.clientWidth, containerEl.clientHeight);
|
||||
@@ -814,6 +859,7 @@ preference the store already manages.
|
||||
|
||||
onDestroy(() => {
|
||||
mounted = false;
|
||||
stopDebugLoop();
|
||||
if (onResize !== null) {
|
||||
window.removeEventListener("resize", onResize);
|
||||
onResize = null;
|
||||
@@ -868,6 +914,30 @@ preference the store already manages.
|
||||
{#if store !== undefined && store.status === "ready"}
|
||||
<MapTogglesControl {store} />
|
||||
{/if}
|
||||
{#if debugOverlayEnabled && debugInfo !== null}
|
||||
<div class="debug-overlay" data-testid="map-debug-overlay" aria-hidden="true">
|
||||
<div class="debug-row">
|
||||
<span class="debug-key">scale</span>
|
||||
<span class="debug-val">{debugInfo.scale.toFixed(3)}</span>
|
||||
</div>
|
||||
<div class="debug-row">
|
||||
<span class="debug-key">scale_ref</span>
|
||||
<span class="debug-val">{debugInfo.scaleRef.toFixed(3)}</span>
|
||||
</div>
|
||||
<div class="debug-row">
|
||||
<span class="debug-key">scale_ratio</span>
|
||||
<span class="debug-val">
|
||||
×{(debugInfo.scale / debugInfo.scaleRef).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="debug-row">
|
||||
<span class="debug-key">view W×H</span>
|
||||
<span class="debug-val">
|
||||
{debugInfo.viewWorldWidth.toFixed(1)} × {debugInfo.viewWorldHeight.toFixed(1)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -911,4 +981,32 @@ preference the store already manages.
|
||||
border-color: var(--color-danger);
|
||||
color: var(--color-danger);
|
||||
}
|
||||
.debug-overlay {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
left: 0.5rem;
|
||||
min-width: 11rem;
|
||||
padding: 0.35rem 0.55rem;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
color: #f3f5fb;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: 5;
|
||||
}
|
||||
.debug-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.debug-key {
|
||||
color: rgba(243, 245, 251, 0.65);
|
||||
}
|
||||
.debug-val {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user