diff --git a/apps/mapgen-studio/.interface-design/system.md b/apps/mapgen-studio/.interface-design/system.md index cb70e748bc..066fa956d1 100644 --- a/apps/mapgen-studio/.interface-design/system.md +++ b/apps/mapgen-studio/.interface-design/system.md @@ -232,13 +232,22 @@ supersedes the Pass-4 dock placement, keeps the console split + icon contract: the label) expanding pure game setup: Leader · Civ · Difficulty · Speed. **No map/world settings in the top bar, ever.** - **Bottom bar = THE World/Map console** (identity `Globe` + "World"): studio - status · History · Size · Players · Resources · Seed · reroll · auto-run · - Run. Map settings author here; the relocated selects ride the shared - operation gate. Resources is a map setting (`WorldSettings`), not game setup. + status · History · Size · Players · Seed · reroll · auto-run · Run. Map + settings author here; the relocated selects ride the shared operation gate. +- **Zone boundary rule (X7, settles "where does this control go"):** a + control belongs to the World console **iff the map pipeline reads it to + generate the map**; a setting only the Civ7 session reads belongs to Game + setup. Players qualifies (`playerCount` → `PlayersLandmass1/2` → landmass + balancing/placement); leader/civ/difficulty/speed don't. A setting NEITHER + reads (resources today) gets no authoring control at all — its state may + still flow through runs (`WorldSettings.resources` does, deliberately + reserved for the resources vertical), and the rule re-admits the select + the day a pipeline reader exists. - **History affordance:** the last-run cluster compresses into one icon button - (`History`); hover tooltip presents the run (seed/size/players/resources), - the accessible name mirrors it, click copies the last seed (the affordance - the old inline seed button carried). Tooltip, never a popup. + (`History`); hover tooltip presents the run (seed/size/players), the + accessible name mirrors it, click copies the last seed (the affordance + the old inline seed button carried). Tooltip, never a popup. It speaks the + console's own vocabulary: only parameters the console authors appear. - **Tile grid (X6, user-grounded):** tile layers render the GAME's plot 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 diff --git a/apps/mapgen-studio/src/ui/components/AppFooter.tsx b/apps/mapgen-studio/src/ui/components/AppFooter.tsx index 7e96d8b327..795ce323c7 100644 --- a/apps/mapgen-studio/src/ui/components/AppFooter.tsx +++ b/apps/mapgen-studio/src/ui/components/AppFooter.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { Bolt, Dices, Globe, History, Play } from 'lucide-react'; import { Button, Input, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/ui'; import { OptionSelect } from './OptionSelect'; -import { MAP_SIZE_OPTIONS, MAP_SIZE_SHORT, PLAYER_COUNT_OPTIONS, RESOURCE_MODE_OPTIONS, LAYOUT } from '../constants'; -import { formatResourceMode } from '../utils'; +import { MAP_SIZE_OPTIONS, MAP_SIZE_SHORT, PLAYER_COUNT_OPTIONS, LAYOUT } from '../constants'; import type { RecipeSettings, WorldSettings, GenerationStatus } from '../types'; import { CIV7_STUDIO_SEED_MAX, CIV7_STUDIO_SEED_MIN } from '../../features/civ7Setup/seedPolicy'; @@ -11,12 +10,16 @@ import { CIV7_STUDIO_SEED_MAX, CIV7_STUDIO_SEED_MIN } from '../../features/civ7S // APP FOOTER — the World/Map console (Pass-5 toolbar-architecture-v2 spec) // ============================================================================ // The footer is the WORLD/MAP zone: one centered console authoring the map -// (size · players · resources · seed) and driving the studio iteration loop -// (status · History · reroll · auto-run · Run). Everything that observes or -// commands live Civ7 lives in `GameConsole`, composed into the header's Game -// bar (top = game, bottom = world/map). The footer still receives the -// game-side in-flight booleans because its authoring/run controls share one -// operation gate across both zones (behavior parity). +// (size · players · seed) and driving the studio iteration loop +// (status · History · reroll · auto-run · Run). The console authors exactly +// the settings the map pipeline reads — a setting the pipeline does not +// consume gets no control here (resources lives in `WorldSettings` and keeps +// flowing through runs, but has no pipeline reader yet, so no select). +// Everything that observes or commands live Civ7 lives in `GameConsole`, +// composed into the header's Game bar (top = game, bottom = world/map). The +// footer still receives the game-side in-flight booleans because its +// authoring/run controls share one operation gate across both zones +// (behavior parity). // ============================================================================ export interface AppFooterProps { @@ -26,7 +29,7 @@ export interface AppFooterProps { lastRunSettings: RecipeSettings; /** World settings from the last completed run */ lastGlobalSettings: WorldSettings; - /** Current world settings (size, players, resources) */ + /** Current world settings (size, players) */ globalSettings: WorldSettings; /** Callback when world settings change */ onGlobalSettingsChange: (settings: WorldSettings) => void; @@ -101,7 +104,6 @@ export const AppFooter: React.FC = ({ 'Ready'; const displaySize = MAP_SIZE_SHORT[lastGlobalSettings.mapSize] || lastGlobalSettings.mapSize; - const displayResources = formatResourceMode(lastGlobalSettings.resources); const operationControlsDisabled = isRunning || isRunInGameRunning || isSaveDeployRunning; const updateSetting = ( key: K, @@ -125,7 +127,7 @@ export const AppFooter: React.FC = ({ // tooltip presents the run, the accessible name mirrors it (a11y + static // markup parity), and the click keeps the copy-seed behavior the inline // seed button used to carry. - const historyLabel = `Run history — last run: seed ${lastRunSettings.seed}, ${displaySize}, ${lastGlobalSettings.playerCount} players, ${displayResources} resources. Click to copy seed.`; + const historyLabel = `Run history — last run: seed ${lastRunSettings.seed}, ${displaySize}, ${lastGlobalSettings.playerCount} players. Click to copy seed.`; const handleCopySeed = async () => { try { await navigator.clipboard.writeText(lastRunSettings.seed); @@ -191,7 +193,7 @@ export const AppFooter: React.FC = ({ {lastRunSettings.seed} - {displaySize} · {lastGlobalSettings.playerCount}p · {displayResources} + {displaySize} · {lastGlobalSettings.playerCount}p Click to copy seed @@ -237,24 +239,6 @@ export const AppFooter: React.FC = ({ className="w-14" /> -
- - Resources - - - updateWorldSetting('resources', value as WorldSettings['resources']) - } - options={RESOURCE_MODE_OPTIONS.map((opt) => ({ - value: opt.value, - label: opt.label - }))} - ariaLabel="Resources" - disabled={operationControlsDisabled} - className="w-24" /> -
-
{/* Seed input */} diff --git a/apps/mapgen-studio/test/runInGame/AppFooter.test.tsx b/apps/mapgen-studio/test/runInGame/AppFooter.test.tsx index 6ae6b4f23d..8dff0b06bf 100644 --- a/apps/mapgen-studio/test/runInGame/AppFooter.test.tsx +++ b/apps/mapgen-studio/test/runInGame/AppFooter.test.tsx @@ -5,11 +5,14 @@ import { AppFooter } from "../../src/ui/components/AppFooter"; import { TooltipProvider } from "../../src/components/ui/tooltip"; import type { RecipeSettings, WorldSettings } from "../../src/ui/types"; -// The footer is the WORLD/MAP console (Pass-5 toolbar-architecture-v2 spec): -// map authoring (size · players · resources · seed) + the studio iteration -// loop, with the last run compressed into the History affordance. Live-game -// markup is covered by GameConsole.test.tsx; the footer keeps the shared -// operation gate: game-side operations disable the authoring/run controls. +// The footer is the WORLD/MAP console (Pass-5 toolbar-architecture-v2 spec, +// narrowed by world-console-map-params): map authoring (size · players · +// seed) + the studio iteration loop, with the last run compressed into the +// History affordance. The console only authors settings the map pipeline +// reads — resources stays in WorldSettings and keeps flowing through runs, +// but has no pipeline reader, so no control renders for it. Live-game markup +// is covered by GameConsole.test.tsx; the footer keeps the shared operation +// gate: game-side operations disable the authoring/run controls. const recipeSettings: RecipeSettings = { recipe: "standard", @@ -54,13 +57,19 @@ describe("AppFooter world/map console", () => { expect(html).toContain("Ready"); expect(html).toContain('aria-label="World size"'); expect(html).toContain('aria-label="Players"'); - expect(html).toContain('aria-label="Resources"'); expect(html).toContain("Re-roll: New seed and run"); expect(html).toContain("Generation seed"); expect(html).not.toContain("Run in Game"); expect(html).not.toContain("Civ7"); }); + it("renders no Resources control or mention (pipeline-input rule; state still flows)", () => { + const html = renderFooter(); + + expect(html).not.toContain("Resources"); + expect(html).not.toContain("resources"); + }); + it("compresses the last run into the History control's accessible name", () => { const html = renderFooter(); diff --git a/docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md b/docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md index 0bfa87b03c..c1768b4f9d 100644 --- a/docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md +++ b/docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md @@ -37,6 +37,10 @@ map console. Pass-4's E1 colocation is superseded (the frame predicted this: setup dropdown therefore carries pure Civ7 game setup only: Leader · Civ · Difficulty · Speed. *Falsifier: if the user treats resource mode as game setup, move one select back — the dropdown row still exists.* + **Superseded by X7 (same session):** the falsifier fired in a third + direction — resources is neither game setup nor a live map parameter + (no pipeline reader exists), so the select leaves the UI entirely while + ALL `WorldSettings.resources` plumbing stays (see X7). - **GameConsole stops being a panel and becomes the bar's command cluster.** It keeps its component boundary (props, tests, behavior parity intact) but renders an inline flex row with no border/background of its own; AppHeader @@ -151,6 +155,29 @@ pre-run drag real) would require moving the background texture into deck layers — unjustified now; X3's mesh standardization is the natural future hook if we ever want it. +## X7 — World console is map-parameter-only (Resources UI out, plumbing stays) + +User-flagged after X6. Original ask was an end-to-end resources removal; +**revised mid-flight: do NOT delete the backend plumbing** — the placement +stack carries the resources vertical (S3 demand planners, S5 resource-start +support, A2 live resource-policy evidence) and the codex-stack design doc +explicitly reserves the wire: "Studio `resourcesMode` is carried as +`MapInfo.StudioResourcesMode` in browser runs only (currently informational; +not consumed by the pipeline)." Verified: no reader of `StudioResourcesMode` +exists on any branch. + +- **Scope:** AppFooter only — Resources label + select removed; the History + tooltip/accessible name drop their resources line (the console speaks its + own vocabulary; the value stays recorded in run snapshots). Footer: + World · status · History · Size · Players · Seed · reroll · auto-run · Run. +- **Zone boundary rule (codified in system.md):** World console iff the map + pipeline reads it (`playerCount` → `PlayersLandmass1/2` → landmass + balancing qualifies Players); Civ7-session-only settings → Game setup; + no reader anywhere → no control (resources, until the vertical consumes + the reserved wire). +- **Players stays in the World bar** (user-delegated call) — the rule above + is the line the user asked me to draw. + ## Sequencing X1 (centerpiece, while the console context is hot) → X2 (small) → X3 → diff --git a/openspec/changes/mapgen-studio-world-console-map-params/proposal.md b/openspec/changes/mapgen-studio-world-console-map-params/proposal.md new file mode 100644 index 0000000000..089318ebf5 --- /dev/null +++ b/openspec/changes/mapgen-studio-world-console-map-params/proposal.md @@ -0,0 +1,57 @@ +# World console is map-parameter-only — Resources leaves the UI (plumbing stays) + +## Why + +The user removed Resources from the map console: it is not a parameter the +studio pipeline consumes today, so authoring it from the World bar promises +an effect the map doesn't deliver. The original ask ("clean it up all the +way through") was explicitly revised mid-flight: **do not delete the backend +plumbing** — another stack carries the resources vertical (placement S3 +demand planners / S5 resource-start support / A2 live resource-policy +evidence), and the `MapInfo.StudioResourcesMode` wire was deliberately +reserved for it. The codex-stack design doc records the intent verbatim: +"Studio `resourcesMode` is carried as `MapInfo.StudioResourcesMode` in +browser runs only (currently informational; not consumed by the pipeline)." +Verified before this change: no reader of `StudioResourcesMode` exists on +any branch; the only touch points are the worker write and that doc note. + +This slice also codifies the zone boundary that settles "where does this +control go" questions: **a control belongs to the World console iff the map +pipeline reads it to generate the map; a setting only the Civ7 session +reads belongs to Game setup.** Players stays in the World bar because +`playerCount` is a pipeline input (worker → `PlayersLandmass1/2` → landmass +balancing/placement in the standard runtime); leader/civ/difficulty/speed +have no pipeline reads and stay in the Game-setup disclosure. Resources, +having no pipeline read TODAY, gets no authoring control — when the +resources vertical starts consuming `StudioResourcesMode`, the same rule +re-admits the select. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md` (X1 frame — + the falsifier "if the user treats resource mode as game setup, move one + select" fired in the third direction: it is neither game setup nor a live + map parameter) +- `apps/mapgen-studio/.interface-design/system.md` (Pass-5 amendment — + zone-boundary rule) + +## What Changes + +- `AppFooter.tsx`: the Resources label + select leave the footer. The + History affordance drops its resources line too (label, `aria-label`, + tooltip) — the tooltip describes the last run in the console's own + vocabulary, and resources is no longer part of it. Footer reads: + World · status · History · Size · Players · Seed · reroll · auto-run · + Run. +- **Nothing else.** `WorldSettings.resources`, defaults, persistence, + fingerprints, run-request fields, proof identity, and the worker's + `StudioResourcesMode` write all stay byte-identical — runs keep carrying + `resources: "balanced"` exactly as before (behavior parity). +- Tests: the footer asserts the Resources combobox is gone and the gating / + History assertions adjust. + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/src/ui/components/AppFooter.tsx`, + `apps/mapgen-studio/test/runInGame/AppFooter.test.tsx` diff --git a/openspec/changes/mapgen-studio-world-console-map-params/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-world-console-map-params/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..5da8870b59 --- /dev/null +++ b/openspec/changes/mapgen-studio-world-console-map-params/specs/mapgen-studio/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: World Console Controls Are Pipeline Inputs Only + +The footer's World console SHALL author exactly the settings the map +pipeline reads to generate the map — Size, Players, and Seed — and SHALL +render no authoring control for a setting the pipeline does not consume. +Resources is removed from the console UI under this rule while its +underlying state, persistence, and run plumbing remain unchanged (runs keep +carrying the recorded resources value). This supersedes the +Resources-select scenarios of `mapgen-studio-toolbar-architecture-v2`; the +shared operation gating and `WorldSettings` flow of that change carry +forward unchanged. + +#### Scenario: Footer authors map parameters only + +- **WHEN** the footer renders +- **THEN** it contains Size, Players, and Seed authoring controls and no + Resources control +- **AND** changing Size or Players updates the same `WorldSettings` state + as before + +#### Scenario: Resources state survives the UI removal + +- **WHEN** a browser run or Run in Game starts after this change +- **THEN** the run request carries the same resources value it would have + carried before (the persisted/default `WorldSettings.resources`), + byte-identical plumbing + +#### Scenario: History describes the last run without resources + +- **WHEN** the History control renders its tooltip and accessible name +- **THEN** they present the last run's seed, size, and players, with no + resources line diff --git a/openspec/changes/mapgen-studio-world-console-map-params/tasks.md b/openspec/changes/mapgen-studio-world-console-map-params/tasks.md new file mode 100644 index 0000000000..13b5ad6bf4 --- /dev/null +++ b/openspec/changes/mapgen-studio-world-console-map-params/tasks.md @@ -0,0 +1,20 @@ +## 1. Implementation + +- [x] 1.1 Verify the resources vertical before touching anything: confirm + `StudioResourcesMode` has no readers on any branch and the + placement-stack work that reserves the wire (documented in proposal). +- [x] 1.2 `AppFooter.tsx`: remove the Resources label/select and the + resources line from the History label + tooltip; keep all + `WorldSettings` plumbing untouched. +- [x] 1.3 Codify the World/Game zone boundary rule in + `.interface-design/system.md` (Pass-5 amendment). +- [x] 1.4 Tests: footer asserts no Resources combobox; History assertions + updated. + +## 2. Verification + +- [x] 2.1 `bun run openspec -- validate mapgen-studio-world-console-map-params --strict` +- [x] 2.2 tsc + vitest green +- [x] 2.3 Visual on :5173 (dark + light): footer reads World · status · + History · Size · Players · Seed · reroll · auto-run · Run; run still + works end-to-end (resources default flows silently).