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
21 changes: 15 additions & 6 deletions apps/mapgen-studio/.interface-design/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 14 additions & 30 deletions apps/mapgen-studio/src/ui/components/AppFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ 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';

// ============================================================================
// 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 {
Expand All @@ -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;
Expand Down Expand Up @@ -101,7 +104,6 @@ export const AppFooter: React.FC<AppFooterProps> = ({
'Ready';
const displaySize =
MAP_SIZE_SHORT[lastGlobalSettings.mapSize] || lastGlobalSettings.mapSize;
const displayResources = formatResourceMode(lastGlobalSettings.resources);
const operationControlsDisabled = isRunning || isRunInGameRunning || isSaveDeployRunning;
const updateSetting = <K extends keyof RecipeSettings,>(
key: K,
Expand All @@ -125,7 +127,7 @@ export const AppFooter: React.FC<AppFooterProps> = ({
// 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);
Expand Down Expand Up @@ -191,7 +193,7 @@ export const AppFooter: React.FC<AppFooterProps> = ({
</span>
<span className="font-mono">{lastRunSettings.seed}</span>
<span>
{displaySize} · {lastGlobalSettings.playerCount}p · {displayResources}
{displaySize} · {lastGlobalSettings.playerCount}p
</span>
<span className="opacity-70">Click to copy seed</span>
</div>
Expand Down Expand Up @@ -237,24 +239,6 @@ export const AppFooter: React.FC<AppFooterProps> = ({
className="w-14" />
</div>

<div className="flex items-center gap-2">
<span className={`text-label font-medium uppercase tracking-wider shrink-0 ${textMuted}`}>
Resources
</span>
<OptionSelect
value={globalSettings.resources}
onValueChange={(value) =>
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" />
</div>

<div className={`w-px h-5 ${dividerColor}`} />

{/* Seed input */}
Expand Down
21 changes: 15 additions & 6 deletions apps/mapgen-studio/test/runInGame/AppFooter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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();

Expand Down
27 changes: 27 additions & 0 deletions docs/projects/mapgen-studio-redesign/pass-5-design-fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 →
Expand Down
Original file line number Diff line number Diff line change
@@ -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`
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions openspec/changes/mapgen-studio-world-console-map-params/tasks.md
Original file line number Diff line number Diff line change
@@ -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).