Commit Graph

411 Commits

Author SHA1 Message Date
d7c6734a2b show the controls panel's rows before measuring them
A widget created under an already-visible parent starts hidden, and a layout
counts a hidden item as empty -- it adds nothing to the size hint. Nothing
showed the freshly built rows until the event loop next ran, long after the
panel had measured itself, so every rebuild measured an empty card.

That one fact explains both symptoms. Originally refit() read a stale cached
hint, which described the previous context and so drew the card one rebuild
behind. Invalidating that cache to fix it replaced a plausible wrong answer
with the true one for a card whose rows were all still hidden, which is why the
panel then collapsed to its heading on every mode change.

Measured on a forced rebuild, with the rows shown and without:

  shown:   rowItems=10 hidden=0  rowsHint=188  needed=140x221
  hidden:  rowItems=10 hidden=10 rowsHint=6    needed=72x39

39px being heading plus margins -- the collapsed card exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 19:36:24 +02:00
1604dc02a2 measure the controls panel against its content, not its old geometry
The previous fix went too far: it activated the panel's own layout before
resizing, which lays the heading and the rows out inside the geometry left over
from the previous context. The panel then took that stale frame as its answer
and collapsed to almost nothing whenever the mode changed.

Only the rows layout is activated now -- that part was right, and is what makes
freshly added rows visible and so measurable. The size comes from
layout()->totalSizeHint(), which says how big the content needs to be without
reference to how big the panel currently is; setGeometry re-runs the outer
layout afterwards on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 19:28:15 +02:00
b346f3dfc1 size the controls panel to the rows it is actually showing
The card was drawn one rebuild behind: selecting something for the first time
sized it for the context before it, and the next selection sized it for that
one. refit() activated the panel's outer layout but never the rows layout
underneath it, so it read a size hint describing rows that were no longer
there, while the freshly added ones were not yet shown and so counted for
nothing. Both layouts are now invalidated and re-run innermost first, and the
rows are polished before being measured -- the badge chips carry border and
padding, which a label reports only once the stylesheet has reached it.

Also drops letter-spacing from the stylesheet. Qt has no such property and
warned once per widget it was applied to, which was most of the console. The
heading and the caption set it on their QFont, where it works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 19:21:28 +02:00
b634da70fb record that additive selection is offered only once something is selected
Ctrl+click works with an empty selection -- it picks the object under the
cursor much as a plain click does -- so leaving it out of the General card is
an omission rather than a claim the panel declines to make. The catalog already
implied that by listing it under Selection only; the accuracy rule now says so
outright, alongside the three omissions it already named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 19:06:24 +02:00
37d9166348 divide the always-available rows in the General context too
The General card was the one that ran its context rows and the global ones
together as a flat list. That made it the exception a player has to notice: the
same six rows sit under a caption everywhere else, so leaving them uncaptioned
here asks the reader to work out that they are the same six.

Requirement wording corrected with it -- it claimed the always-available rows
were the General context's entire content, which was never true. General has
Select, Select area and Deconstruct mode of its own above the divider, exactly
like the other contexts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 19:06:01 +02:00
fe69921b8e show the available controls in a panel over the world
The panel decides nothing about what the controls are: it asks the same
resolver the key handling and the mouse dispatch ask, and renders each badge
from the binding that resolver matches. A chip cannot claim a key that does
nothing, and a label cannot describe a click that does something else, because
neither is written here.

Display text is the one part that is not shared -- lib/core says what is
available and what triggers it, ControlActionText.cpp says what it is called.
That split is why the table can stay free of UI strings and still be the single
source of the pairing.

It refreshes on a timer rather than by subscribing. Two things that change a
row -- a belt drag starting, the ghost crossing a transfer target -- happen on
mouse movement and publish nothing, and they must still show while the game is
paused, so there is no event and no tick to hang it on. Resolving is comparing
two vectors of enums, and the rebuild is skipped unless they differ.

Left edge, bottom-aligned in the same band the selection panel is confined to,
so it clears the build bar's strip and never meets the panel on the opposite
edge. Clicking the heading collapses it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 17:30:22 +02:00
3e79b09fec resolve mouse gestures through the action table too
The panel has to say what the left button does right now -- "Place" or "Apply
settings", "Select" or "Toggle deconstruct" -- and that answer lived in the
if-chain at the top of mousePressEvent, where the panel could not reach it.
So the chain becomes a switch on the resolved action, and the panel will read
the same resolver. Which branch runs is now decided in one place; what each
branch does is untouched, drag state machines and all.

Right-click gets the clearest win: cancel-the-drag versus leave-the-mode was
two nested conditions inspecting belt state, and is now the two actions the
table already distinguishes for the panel's sake.

Ctrl variants fall back to the plain gesture wherever nothing claims them,
which is what keeps Ctrl+click placing a building in builder mode and Ctrl+drag
deconstructing an area -- the modifier means something only where an action
says it does, rather than every handler re-deciding whether to ignore it.

Behaviour is unchanged. Full suite passes; the app runs clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 17:24:25 +02:00
77bbd58d02 resolve keyboard shortcuts through one action table instead of a switch
The controls panel needs to say what each key does right now, and a panel that
keeps its own list of that is a list that goes stale. So the list moves into
lib/core/ControlAction.h: which actions exist, what each is bound to, and when
each does something. InputMapper stops deciding that and switches on the
resolved action instead, so the panel and the key handling cannot disagree
about what Q means -- there is only one place that says.

The table declares; it never performs. It holds no simulation access, fires no
events, and names nothing: display strings live in the ui target, which formats
the bindings this hands it, so a badge is rendered from the real binding rather
than typed beside it. What an action *does* stays exactly where it was.

ControlContext is the snapshot the rules read, which is what keeps this
testable without a world. Two of its facts come from BlueprintLibrary, which is
built after the world view and so arrives by setter; one comes from the new
hovered-transfer flag on BuildModeController, resolved once on mouse-move
through the same classifier the click and the ghost colour already use.

Behaviour is unchanged, deliberately. Ctrl still separates the chords and every
other modifier is still ignored, so Shift+A pans as before; matching modifiers
exactly would have silently swallowed those presses. Build hotkeys and F3/F4
stay outside the table -- the first are advertised on the build buttons and
already derive their badges from the handler's own table, the second are
development controls the panel must never offer.

The tests are the point of putting this in lib: every row's bindings must
resolve back to that row's action in that same context, which fails the moment
a shown row and its handler part ways.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 17:20:44 +02:00
02f2314588 specify a context-sensitive controls panel floating over the game world
Adds REQ-UI-CONTROLS-PANEL/-CARD/-CONTENT/-ACCURACY: a left-anchored panel,
bottom-aligned within the same band the selection panel uses, collapsed and
expanded by clicking its header. Five control contexts derived from the build
mode and the selection, each with its own rows above a shared always-available
block.

The accuracy rule is the load-bearing part: a row must have the effect its
label names, or not be shown -- no greyed rows. That makes four rows
conditional on more than the context (C/Ctrl+C on the selection holding a
placeable building, the belt-drag row on the builder type, the RMB/Q split on a
drag being in progress, and Place vs Apply settings on the hovered ghost
resolving to a configuration transfer), and it makes the context-to-rows
mapping testable against the real bindings.

Also names the panel in the layout diagram, REQ-UI-WORLD-SIZE, and
REQ-UI-MODAL-DIM, and records in REQ-UI-HOTKEYS that the panel and the build
button badges display bindings rather than define them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-07 14:43:46 +02:00
246cfc3935 give the selection cards their own parts instead of label blobs
The cards were assembled from plain labels carrying whole blocks of text.
Replace those with the widget vocabulary the requirements describe, so a part
means the same thing wherever it appears and a card is a list of parts rather
than a string builder.

The parts, all free of Simulation and GameConfig -- they take prepared values,
and the contents work out what those are:
- StatRow, BarRow, SectionBox: label/value line, captioned fill bar, captioned
  group. The bar is one part for three things: construction progress,
  production progress, and HP.
- ItemChip / ItemChipRow: buffered items as icon, count and sub-line
  (REQ-UI-SINGLE-SELECTION). An input chip carries its per-cycle amount, an
  output chip its count against the buffer capacity. The chips are rebuilt only
  when the set of items changes, so a 30 Hz refresh moves numbers rather than
  widgets.
- RecipeSummaryRow: inputs, arrow, outputs, cycle time (REQ-UI-RECIPE-SUMMARY),
  which is now the panel's only display of the cycle time.
- CountRow, StatusPill, EmptyNote.

Behaviour that changed with them:
- The station card shows damage, range and fire rate as the requirement asks
  (REQ-UI-STATION-STATS-PANEL) rather than the combined DPS it showed before.
- A ship's behaviour moves from a stats row into the card header
  (REQ-UI-SHIP-BEHAVIOR). ShipStatsPanel keeps setBehavior for the balancing
  tool's inspect window, which has no header to put it in.
- A construction site's card shows a progress bar and the "no buffers until
  built" note (REQ-UI-SELECTION-CARD), and now also its recipe summary, since
  that is configuration and a site carries it (REQ-BLD-SITE-CONFIG). Costing a
  shipyard site's schematic needed computeShipyardRequiredMaterials to take a
  stored configuration as well as a live building -- one overload, so the
  module sum still exists once.

ShipStatsPanel is rebuilt on StatRow, BarRow and SectionBox, so the selection
card, the layout dialog's design preview and the balancing tool read alike.
Those three parts are compiled into the balancing target, which does not link
the ui library; keeping them sim-free is what makes that possible, and the
build enforces it.

Build clean, 541 tests pass, app and balancing tool both run with no Qt
warnings. Visual check pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-07 12:37:11 +02:00
89e984ec76 split the selection panel into one card per kind of selection
The panel rendered every selection out of a single pool of member widgets,
hidden and shown per branch, so each build path had to remember to hide the
other branches' widgets. That coupling produced the two defects fixed in
668ce0f, and the per-type detail the requirements now ask for would only add
more of it.

The pool is gone. SelectionPanel keeps the category arbitration, the float and
the hide-when-empty behaviour, and hosts exactly one SelectionContent at a
time; SelectionContentFactory picks which one from the selection alone. Each
card is one row of the catalog in REQ-UI-SELECTION-CONTENT and owns only its
own widgets.

The card structure (REQ-UI-SELECTION-CARD) lives in the base class: a header
with an identity symbol, a name and one right slot, then a configuration group
and a runtime group. A construction site keeps its configuration and has its
whole runtime group replaced by the construction progress, decided once there
rather than in every card (REQ-BLD-SITE-CONFIG).

Also implemented here:
- REQ-UI-SELECTION-STATUS: the header status dot, taken from the simulation's
  own getProductionStatus() so the panel and the world's status light cannot
  disagree.
- REQ-UI-SELECTION-AGGREGATE: belt-subsystem tiles and debris-only selections
  collapse into one card with a count instead of a count summary.
- REQ-UI-HQ-PANEL: the HQ shows the global block stock and its HP, neither of
  which is a buffer.
- BuildingIconCache, extracted from BuildButtonBar's file-local chip loading so
  the card headers and the build buttons rasterize the same SVGs once.

FieldSelectionPanel is deleted: ships, stations, debris and the field count
summary are four more cards in the same factory, so the two-panel arbitration
collapses into one decision.

The card parts are still today's labels and buttons; the item chips, bars,
recipe summary and stat rows follow.

Build clean, 541 tests pass, app runs with no Qt warnings. Visual check
pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-07 12:18:33 +02:00
90e40dddbc define the selection panel as a catalog of per-selection contents
The panel's content requirements described one panel that changes shape.
Restate them as one card structure plus a catalog of contents picked by
what is selected, so each selection has a named content and the parts
mean the same thing wherever they appear.

New:
- REQ-UI-SELECTION-CARD: header (symbol, name, one optional right slot),
  configuration group, runtime group. A construction site replaces the
  whole runtime group with a Construction bar and keeps its configuration
  group, per REQ-BLD-SITE-CONFIG.
- REQ-UI-SELECTION-CONTENT: the catalog table.
- REQ-UI-SELECTION-STATUS: the header status dot, derived from
  REQ-UI-STATUS-LIGHT's evaluation rather than a second definition.
- REQ-UI-SELECTION-AGGREGATE: a homogeneous multi-selection collapses into
  one content with a count, but only where every part aggregates -- belt
  subsystem tiles and debris. Splitters mixed with belts and several
  production buildings fall back to the count summary.
- REQ-UI-RECIPE-SUMMARY: the inputs -> outputs, duration line, including a
  shipyard's module contributions.
- REQ-UI-HQ-PANEL: global block stock plus HP, because blocks bypass the
  buffers into the global stock (REQ-HQ-BELT-INPUT).

Rewritten: REQ-UI-SINGLE-SELECTION (item chips; an output chip's
denominator is now the buffer capacity, not the per-cycle amount),
REQ-UI-PRODUCTION-PROGRESS (bar, and no longer the cycle time's home),
REQ-UI-MULTI-SELECTION (count rows and a total cost row), REQ-UI-BELT-CLEAR,
REQ-UI-SHIP-STATS-PANEL, REQ-UI-STATION-STATS-PANEL, REQ-UI-SHIP-BEHAVIOR
(moves into the header slot), REQ-UI-FIELD-MULTI-SELECTION and
REQ-UI-DEBRIS-PANEL (debris-only selections now aggregate).

Requirements only; no code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-07 09:59:26 +02:00
668ce0fcb8 float the selection panel over the game world instead of a side column
Implements REQ-UI-SELECTION-PANEL and the full-width REQ-UI-WORLD-SIZE /
REQ-UI-HEADER: MainWindow drops the 25% side column and its 75/25 math, so
the header bar and world view span the window, and the panel joins the
build button bar as a widget floating over the world.

The panel follows the bar's pattern -- an opaque sibling built after the
world view, so it sits above the vignettes, below the dim overlay, and
swallows the mouse events that would otherwise reach the world. It sizes
itself to its content within a band the window hands it (the world view
less the bar's strip, so the bar never has to move for it), right-aligned
and centered in that band, and scrolls once the content outgrows it. Its
content moved into a scroll area for that; the width is capped at 320 px
because the wrapped labels and the splitter filter lists have no natural
width of their own. With nothing selected the panel now hides entirely
rather than showing an empty box (REQ-UI-EMPTY-SELECTION).

Two defects that content sizing exposed: buildEmpty() left the selected
ids behind when the building vanished under the panel, which would have
held an empty panel on screen, and buildMulti() let a shipyard's layout
preview survive from a previous single selection (REQ-UI-MULTI-SELECTION).

Renames SelectedBuildingPanel to SelectionPanel throughout, matching the
requirements: the panel has long shown ships, stations, and debris too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 21:58:36 +02:00
7ddb4a1bb5 float the selection panel over the game world instead of a side column
Remove the 25% side panel column: the header bar and game world view now
span the full window width, and the selection panel floats over the world
at its right edge, content-sized and shown only while something is
selected.

REQ-UI-PANEL-COLUMN is replaced by REQ-UI-SELECTION-PANEL, which defines
the panel's geometry, visibility, overlay, and input rules. The panel is
centered within the view height less its margins and the build button
bar's strip, so the bar never has to move out of its way. Rename the
"selected building panel" to "selection panel" throughout, since it has
long shown ships, stations, and debris too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-06 21:17:43 +02:00
3b37b0ecf8 target single-building transfers by hovering, not by footprint coincidence 2026-08-06 20:31:51 +02:00
98deab932a let any blueprint transfer configuration, not just single-building ones (if the orientation matches) 2026-08-06 20:29:20 +02:00
08d8b0dd90 re-cover copy-settings through single-building blueprints 2026-08-06 19:44:55 +02:00
9a3b6c10d6 fix bug where selecting the same layout for a shipyard discarded the current progress and buffers 2026-08-06 19:18:45 +02:00
cd31af2611 remove the Shift copy-building-settings gesture 2026-08-06 19:09:11 +02:00
fd6a7c5815 rekey the temporary blueprint to C, add V to re-place it 2026-08-06 19:07:13 +02:00
18cfe238f6 move blueprints out of the sidebar into Ctrl+C / Ctrl+V dialogs 2026-08-06 19:03:59 +02:00
2cbcf1554f add the ASCII-only source rule and the visual-verification note to CLAUDE.md 2026-08-06 08:28:57 +02:00
c1af58d80c float the build buttons as a horizontal bar over the game world and show key bindings inside build buttons 2026-08-06 08:28:40 +02:00
f39fe9a118 move the debug stats panel out of the renderer 2026-08-05 22:14:55 +02:00
e5dcb9de5f extract WorldRenderer 2026-08-05 22:14:55 +02:00
dc83add5c6 move two placement queries out of the view into PlacementRules 2026-08-05 22:14:54 +02:00
3a1951559d share the world shapes both views (game and balancing) draw identically 2026-08-05 22:14:54 +02:00
26d7448492 extract BuildModeController, fixing a silent blueprint exit 2026-08-05 22:14:54 +02:00
202f583067 extract SelectionController 2026-08-05 22:14:54 +02:00
d5ab44b9bf move the remaining hotkeys into the InputMapper 2026-08-05 22:14:53 +02:00
bb1ffab8fc fix a bug where view continues to pan when window lost focus while panning 2026-08-05 22:14:53 +02:00
e1445fe508 move the already-event-driven hotkeys into the InputMapper 2026-08-05 22:14:53 +02:00
caa810f66d move pan input into an InputMapper 2026-08-05 22:14:53 +02:00
f6df95abb2 extract the scroll position into WorldCamera 2026-08-05 22:14:52 +02:00
fa9dbd62ad use WorldCoordinates in ArenaView too 2026-08-05 22:14:52 +02:00
2af09d9eb1 extract the world<->widget transform into WorldCoordinates 2026-08-05 22:14:52 +02:00
4f7fdb8a4c add tone, critique and class layout rules to CLAUDE.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 22:14:37 +02:00
949937d2c2 re-use PlacementFixture in BuildingTests 2026-08-05 07:53:08 +02:00
4c166bf47f depend on the registry instead of DebrisSystem in the AI path 2026-08-05 07:25:59 +02:00
60260540cd make deconstruction its own system 2026-08-05 07:10:46 +02:00
1f4503176b make construction its own system (extracted from BuildingSystem) 2026-08-05 06:57:12 +02:00
fd85e8e10a free the buffer setup and belt registration from BuildingSystem 2026-08-05 06:55:13 +02:00
114a43b205 make BuildingSystem stateless: FactoryState becomes a parameter 2026-08-05 06:50:11 +02:00
d87d063b10 move the placement rules and the config-dependent queries off BuildingSystem 2026-08-05 06:49:49 +02:00
537597c854 delete the unused getAllBeltTiles and BeltTileInfo 2026-08-05 06:49:28 +02:00
9c3be0fbd0 extract the production rules as free functions over config and building 2026-08-05 06:49:15 +02:00
58b94223f7 migrate every factory query off BuildingSystem onto the free functions 2026-08-05 06:46:13 +02:00
1fb63cce4e move the asteroid width bound into FactoryState 2026-08-05 06:45:33 +02:00
0b7e94b4e4 drop CombatSystem's unused BuildingSystem parameter 2026-08-05 06:45:16 +02:00
0408336cf9 depend on factory data instead of BuildingSystem in the AI path 2026-08-05 06:44:49 +02:00