Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions apps/mapgen-studio/.interface-design/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,10 @@ supersedes the Pass-4 dock placement, keeps the console split + icon contract:
geometry — regular pointy-top hexes on the odd-R row-offset lattice (the
hex-convention audit proved `tile.hexOddQ` mislabels that grid; the model's
column-offset projection is not a regular tiling, hence the "squished"
look it produced). Tile borders use ONE graphite ink (`#0d0d11`, α200) in
both themes — dark seams against the fills; never a mid-luminance color
that competes with the data palette. Unfilled tiles draw nothing.
look it produced). Border ink: see the Y-wave amendment below — the X6
constant-graphite ink (`#0d0d11`, α200) is superseded by the fill-derived
border rule (a page-colored seam dissolved the lattice into dots at fit
zoom). Unfilled tiles draw nothing (unchanged).

## DAG-tab amendment (2026-06-12, handoff-mandated): stage views

Expand All @@ -278,3 +279,42 @@ Decisions (see `openspec/changes/mapgen-studio-dag-tab/design.md`):
view activation, staleTime ∞) — fetch on first activation, cached per
recipe; pipeline selection/expansion live in `viewStore` as
pipeline-prefixed fields, distinct from map-explore selection.

## Y-wave amendment (2026-06-12, user-grounded): tile border rule, flat config accordion, selector drift

- **The one tile-border RULE (supersedes X6's constant ink):** a tile's
border is its OWN fill pulled toward black (`fill × 0.55`, fully opaque) —
self-grout, owned by `tileBorderColorForFill` in `presentation.ts`. The X6
page-substrate constant was invisible between dark fills at fit zoom
(Huge-map tiles are a few pixels wide), dissolving the tessellation into
dots; a fill-derived seam is darker than its fill BY CONSTRUCTION, so the
lattice reads at every zoom, both themes, every palette — and still
recedes like etched grout up close. Unfilled tiles still draw nothing.
- **Default layer preference: the map studio defaults to the MAP.** When
layer selection resets (step/stage switch, fresh manifest), prefer the
step's tile-space GRID layer over whichever layer the worker emitted
first (points/mesh). Owned by `useVizState`'s selection fallback.
- **The graticule is CANVAS substrate, not layer furniture.** Once a
manifest exists, the background grid follows the user's grid toggle on
EVERY stage — kind-independent, including steps with no visible layers
(the canvas stays a ready survey field, never dead space); a layer may
opt out via `meta.showGrid: false`. The old points/segments-only gate
made the grid vanish on most stage switches. Pre-manifest, the
awaiting-matter overlay carries its own graticule.
- **Flat config accordion (supersedes the Pass-3 stage CARD):** top-level
config objects lay flat on the panel — full-bleed disclosure rows,
hairline dividers (`divide-border-subtle`), zero inter-item margin, no
card chrome. Expanding opens a RECESSED slab (`bg-surface-sunken/60`,
hairline top) — the interaction reads as a door opening INTO the
graphite, never a card lifting off it. Group wells keep their one
machined-slot tier inside the slab; the Pass-3 4/8px field rhythm is
unchanged; the 12px inter-card step is retired.
- **Selector drift affordance (categorical):** any selector that names a
source of truth (saved game config; world/map config) must SHOW when the
current state has drifted from that source — warning(orange) ring on the
control + a warning "Modified" pill whose click re-applies the source
(sync back). Drift for a saved game config = "re-applying the file would
change the state" (`studioSetupDriftsFromSavedConfig`): the file governs
the options it specifies + its player options; ungoverned keys never count
as drift. The game-setup dropdowns may never silently supersede a selected
saved config.
33 changes: 20 additions & 13 deletions apps/mapgen-studio/src/features/configOverrides/rjsfTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export type BrowserConfigFormContext = {
// is gone. The theme now follows the single `.dark` class via design-system
// tokens (`card`/`muted`/`border`/`accent`/…), so there is no `lightMode` read.
const FORM = {
card: "bg-card border-border",
// Group well: nesting is a surface, not an indent (Pass-3 config-surface
// spec). One recess below the stage card — tinted toward the page token so
// groups read as machined slots in the slate. `background` sits below `card`
Expand All @@ -56,12 +55,12 @@ const FORM = {
groupHeading: "text-label font-semibold uppercase tracking-wider text-muted-foreground",
subGroupHeading: "text-label font-semibold uppercase tracking-wider text-muted-foreground/70",
// Rhythm on the 4px base (Pass-3): 4px inside a field block, 8px between
// sibling fields, 12px between groups/stage sections. Group wells carry
// `my-1`, which composes with the sibling gap to the 12px group step.
// sibling fields. Stage sections carry NO inter-item rhythm (Y4 flatten):
// the accordion is tight — hairline dividers separate rows, not margins.
// Group wells carry `my-1`, composing with the sibling gap inside a slab.
rhythm: {
field: "gap-1",
siblings: "gap-2",
sections: "gap-3",
groupPull: "my-1",
},
} as const;
Expand Down Expand Up @@ -207,8 +206,11 @@ export function BrowserConfigObjectFieldTemplate(
const textClass = FORM.text;

if (isRoot) {
// Y4 flatten: the stage list is a TIGHT accordion — flush rows separated
// by hairlines, no inter-card margins, no card chrome. Expansion is the
// only volume change (a recessed slab opens under the row).
return (
<div className={`flex flex-col ${FORM.rhythm.sections}`}>
<div className={`flex flex-col divide-y divide-border-subtle border-y ${FORM.borderSubtle}`}>
{properties.filter((p) => !p.hidden).map((p) => p.content)}
</div>
);
Expand Down Expand Up @@ -236,35 +238,40 @@ export function BrowserConfigObjectFieldTemplate(
);

if (isStage) {
// Y4 flatten: stage objects lay FLAT on the panel — no card chrome, no
// raised surface. The header is a full-bleed disclosure row; expanding it
// opens a RECESSED slab (`surface-sunken` — below the panel, toward the
// page) so the interaction reads as a door opening into the graphite,
// not a card inflating off it. Group wells keep their one machined-slot
// tier inside the slab.
return (
<section
className={`rounded-lg border p-2.5 ${FORM.card}`}
data-config-section=""
data-config-pointer={pointer}>
<section data-config-section="" data-config-pointer={pointer}>
{collapse ? (
<CollapsibleHeader
pointer={pointer}
title={prettyTitle}
titleClass={`text-sm font-semibold ${textClass}`}
expanded={expanded}
collapse={collapse}
className="px-2.5 py-2 hover:bg-muted/20 transition-colors"
/>
) : (
<header className="flex flex-col gap-1">
<header className="flex flex-col gap-1 px-2.5 py-2">
<div className={`text-sm font-semibold ${textClass}`}>{prettyTitle}</div>
{renderGsComments({ schema, className: labelClass })}
{description ? <div className={`text-data ${labelClass}`}>{description}</div> : null}
</header>
)}
{expanded ? (
<div id={collapse ? configContentId(pointer) : undefined}>
<div
id={collapse ? configContentId(pointer) : undefined}
className={`border-t bg-surface-sunken/60 px-2.5 pb-2.5 pt-2 ${FORM.borderSubtle}`}>
{collapse ? (
<div className="flex flex-col gap-1 pt-1">
<div className="flex flex-col gap-1 pb-1.5">
{renderGsComments({ schema, className: labelClass })}
{description ? <div className={`text-data ${labelClass}`}>{description}</div> : null}
</div>
) : null}
<div className={`my-1.5 border-t ${FORM.divider}`} />
{content}
</div>
) : null}
Expand Down
45 changes: 45 additions & 0 deletions docs/projects/mapgen-studio-redesign/00-GOAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,48 @@ addressed to this lane
effect core — adopting v3 platform now buys a guaranteed second migration.
Full evidence + revisit triggers:
`research/04-effect-native-substrate-spike.md`.

## Studio fixes wave Y (2026-06-12, user-reported)

- [x] **Y1 — "Waiting for Proof" zombie (run-in-game proof never matched).**
Root cause: `gen:maps` runs inside turbo's CACHED `build` task and
`SWOOPER_STUDIO_RUN_ID` was never declared in `turbo.json` — strict env
mode stripped it / the cache replayed a bundle without it, so the deployed
map script logged `[mapgen-proof] {"requestId":null,…}` (observed live)
and the marker waiter could never match: every Run in Game sat the full
90s log timeout in "Waiting for Proof" while the game reached turn green.
Fix: `turbo.json` `build.env: ["SWOOPER_STUDIO_RUN_ID"]` (cache-correct:
the output genuinely differs) + a post-deploy fail-fast guard
(`mapScriptEmbedsRequestId`, code `run-request-id-not-materialized`) so a
missing embedded id can never masquerade as a proof wait again. Verified
live end-to-end: Run in Game → Complete, proof line carries the request id.
- [x] **Y2 — saved-config precedence affordance.** Verified the suspected
partial overwrite is NOT real (dropdown changes merge cleanly); the real
gap was silent drift. New categorical rule (system.md Y-wave amendment):
drift = "re-applying the saved file would change the state"
(`studioSetupDriftsFromSavedConfig`; governed keys + player options only);
the header selector gets a warning ring + orange "Modified" pill, click
re-applies the saved config. 5 new drift pins. Verified live (Speed →
Quick → pill; click → Standard restored, pill cleared).
- [x] **Y3 — tile grid restored on every stage.** X6's constant graphite
border ink (#0d0d11 = the page substrate) was invisible between dark fills
at fit zoom — Huge-map tiles are a few px wide, so the tessellation
dissolved into dots everywhere except the Delaunay mesh (its own slate
ink), i.e. "the grid disappears for every stage besides the first". Fix:
the one tile-border RULE — border = own fill × 0.55, opaque
(`tileBorderColorForFill`), legible at every zoom/palette/theme. Plus:
layer-selection default now prefers the step's tile-space GRID layer over
first-emitted (the map studio defaults to the map). New border-rule pin;
verified live across stages 2/10/13 (contiguous lattice at fit zoom).
Second user report ("when I switch stages, the grid goes away") exposed
the OTHER grid: the canvas graticule was gated on layer kind
(points/segments only), so it vanished for tile/mesh selections — i.e. on
most stage switches. New rule: the graticule is CANVAS substrate — once a
manifest exists it follows the grid toggle on every stage (including
zero-layer steps like Placement); layers may opt out via meta. Verified
live across stages 1/2/10/13/17.
- [x] **Y4 — flat config accordion.** Stage cards retired: top-level config
objects are full-bleed disclosure rows with hairline dividers and zero
inter-item margin; expansion opens a recessed `surface-sunken` slab (door
INTO the graphite); wells keep their one tier inside. Verified live, dark
+ light. system.md Y-wave amendment carries all three design rules.