Commit Graph

172 Commits

Author SHA1 Message Date
3eec462b4a move the debug stats panel out of the renderer
It was the one thing in WorldRenderer positioned in pixels rather than tiles,
kept there only to preserve its draw order. With the order agreed not to matter,
the world-space / screen-space split becomes exact.

The real payoff is translation. drawDebugOverlay was the only caller of tr() in
the renderer, so Q_DECLARE_TR_FUNCTIONS and the QCoreApplication include go with
it. Nothing left in the renderer draws translatable text - its text is
config-driven glyphs, ASCII port arrows and numbers - so it no longer needs a
tie to the meta-object system at all. That is the argument I should have weighed
originally instead of anchoring on draw order.

The panel now paints on top of ships and beams rather than under them, and the
widget gates it on its own m_debugDraw directly. The frame still carries
isDebugDrawEnabled, because the sensor ranges and target lines it also gates are
genuinely world-space.

Dropped the painter.resetTransform() the function opened with: it was vestigial
from the older design that drew the world through painter.translate, and has
been a no-op since every conversion became explicit arithmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 21:23:38 +02:00
9aed6d844e extract WorldRenderer
The last seam of the decomposition, and the one the earlier ones were groundwork
for: every draw method already took a WorldCoordinates, read mode state through
BuildModeController and selection through SelectionController, and used the
shared shapes in WorldPrimitives, so the move needed almost no rewriting.

paintGL is now a call sequence. The split is the world-space / screen-space line
already drawn by the WorldCoordinates work: the renderer draws everything
positioned in tiles, while the pause and deconstruct vignettes and the replay
overlay - which never took a WorldCoordinates because they are anchored to the
viewport - stay with the widget.

WorldRenderFrame is what keeps the renderer independent of the widget. It reads
the simulation directly, but the rest of what it draws is interaction state the
widget owns: the selection, the active build mode, live beams, the copy-settings
feedback, the box-select rectangle. Those are gathered per frame and passed by
reference, so the renderer holds no copy a later click could invalidate, and it
knows nothing about input.

Three more queries had to stop belonging to the view first, because the renderer
and the click path both need them: buildingsInBox and collectTunnelTiles move to
FactoryQueries, and makeTunnelLookup with the TunnelTileMap and QPointCompare it
needs move to TunnelCompletion, which already owned that concept.

Two small things fell out of leaving QWidget. The port-item clip region used
QWidget::rect() and now takes the painter's own viewport. drawDebugOverlay puts
translated text on screen, so the renderer declares tr() via
Q_DECLARE_TR_FUNCTIONS rather than becoming a QObject. drawDebugOverlay stays in
the renderer despite being screen-anchored: it is drawn mid-sequence, so moving
it out would put it on top of the ships instead of under them.

The simulation reference is non-const only because EntityAdmin's component
accessors are; the renderer never writes it.

Draw order is unchanged, and so is behaviour. GameWorldView.cpp is 1431 lines,
from 3265 when this branch started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 19:09:40 +02:00
286116d2b1 share the world shapes both views draw identically
Groundwork for the WorldRenderer extraction: lift the shapes GameWorldView and
ArenaView already draw the same way, before writing a renderer around them.

Four of them turned out to be identical down to the constants - the ship
triangle, the health bar, the debris marker and the sensor-range circle. The
health bar was written three times: once in GameWorldView::drawHpBar and inlined
twice in ArenaView, for stations and for ships.

The argument for sharing is not de-duplication - it is about fifty lines. It is
that the arena exists to eyeball combat, so it only does its job while a ship
there looks like a ship in the game. Retuning the ship shape and having the
balancing tool silently keep the old one is a quiet way to make the tool lie.

Deliberately narrow. The two views differ on selection highlights, beams, target
lines, and all of the factory rendering, and those stay where they are; the
arena has already diverged on some of that and nobody minded, which is a reason
to keep the shared set to shapes that are genuinely the same rather than to
aspire to more. Free functions over explicit values, no state and no simulation,
so each view keeps its own iteration and layer order.

The balancing target does not link the ui library, so it compiles
WorldPrimitives into itself - the mechanism already used for VisualsLoader and
ShipStatsPanel.

Two shared getters come with it: the ship's forward extent, which the selection
ring and health bar are positioned from, and the debris radius, which the debris
selection ring previously tracked via a comment saying "matching drawDebris".

Behaviour is unchanged. WorldRenderer is next and will be built on these.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 17:56:50 +02:00
b0fffdb00f extract the scroll position into WorldCamera
Second seam of the GameWorldView decomposition: the view no longer owns a scroll
position, only the pan intent and the bounds.

WorldCamera works purely in world units — tiles and tiles per second, never
pixels. That is what keeps it independent of WorldCoordinates: the two meet only
where GameWorldView feeds getViewCenterXTiles() into the transform, and neither
knows the other exists.

Two things are passed in rather than reached for, both so the camera stays a
plain value with no simulation dependency:

- ScrollBounds, because the pan limits move with asteroid expansion and with
  pushes. The camera clamps on every advance(), not only when panning, so the
  view follows the bounds inward when they shrink.
- PanDirection, because pan intent is not the camera's business. Today
  GameWorldView collapses its two held-key flags into it; if controls become
  rebindable the camera's interface does not change.

The config structs are referenced, not copied: they live inside the Simulation's
GameConfig, which is assigned in place on restart (REQ-CFG-RELOAD), so reloaded
scroll tuning takes effect without rebuilding the camera. A test pins that.

The pan-speed curve (REQ-UI-SCROLL-SPEED) had no coverage at all and is the
least obvious code in the file — two ramps combined by min, with a peak below
the fast speed where the bands overlap in a narrow contest zone, and a hard step
when the band width is zero. All of that is now tested.

Behaviour is unchanged, including the cases worth naming: holding both keys
still cancels out, and the moved/not-moved result that drives the box-select
refresh still counts movement caused purely by the bounds changing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 13:15:49 +02:00
8ec413e1b9 use WorldCoordinates in ArenaView too
ArenaView carried its own copy of the same five transforms. It differs from the
game view in exactly two respects: the arena is a fixed world shown whole, so
the tile size is the tighter of the two axis fits rather than the height fit,
and there is no scrolling, so the left edge is always 0.

That is small enough to absorb into WorldCoordinates as a second named factory.
The raw constructor becomes private and both call sites go through
scrolling(...) or fitToWorld(...), which also reads better than three
positional ints at the call site.

ArenaView is threaded the same way GameWorldView was: paintGL builds one
snapshot, every draw takes a const WorldCoordinates&, and mousePressEvent takes
its own.

ArenaView::widgetToWorld guarded against a near-zero tile size; that guard now
lives in WorldCoordinates as a tilePx fallback to 1.0, alongside the existing
degenerate-world-size fallback, so both factories are total and no conversion
can divide by zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 10:18:52 +02:00
4f6de79352 extract the world<->widget transform into WorldCoordinates
The nine coordinate helpers on GameWorldView were the first seam of the
planned decomposition: stateless math that every draw method, the placement
validation, and the belt-drag code reached into private state to get at.

WorldCoordinates is an immutable value built from the viewport size, the world
height, and the scroll center. It lives in lib/core rather than ui, following
BeltDragPath and TunnelCompletion — UI-only helpers kept there so Catch2 can
reach them. That is what lets the transform math be unit-tested at all, since
the test target links lib only.

Rather than leave delegating one-liners behind, the snapshot is threaded
through the call graph: paintGL builds one per frame, every world-space draw
takes a const WorldCoordinates&, and the screen-space draws (vignettes, replay
overlay, debug text) take none. Those methods no longer touch m_scrollXTiles,
width(), or height(), which is most of the groundwork for extracting
WorldRenderer later. The mouse handlers each take their own snapshot.

The snapshot is deliberately not cached in a member: a resize or a scroll
would silently invalidate it, and a stale transform surfaces as misaligned
hit-testing rather than as a visible failure.

widgetToTile and widgetToWorld had the same arithmetic inlined separately;
the former now goes through the latter.

The architecture doc's coordinate section was already stale — it described a
painter.translate approach and a hardcoded tilePx = 20, neither of which was
true — so it is rewritten to match the code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
2026-08-05 10:04:14 +02:00
64c344c3a3 correct the belt subsystem interface description in architecture.md 2026-08-04 18:12:02 +02:00
79b79ab7c3 Show building_block icon in header stock, expand button and build costs 2026-07-23 21:13:37 +02:00
f766ae4a86 Allow to draw produced-item icons in recipe dialog and game world 2026-07-23 20:54:04 +02:00
8b71fe1a03 Rename ship/station scrap drop entities to "debris" 2026-07-23 20:51:05 +02:00
11daa61714 Add refund-percentage tooltip to Deconstruct button 2026-07-23 20:46:39 +02:00
60d6767d93 draw building icons in the game world 2026-07-23 20:45:50 +02:00
7c1455b8a0 add icons for build buttons 2026-07-22 21:44:20 +02:00
e20a0bba67 Rename Demolish to Deconstruct 2026-07-22 21:40:42 +02:00
b2ce20e6ad Add deconstruction queue 2026-07-22 21:37:56 +02:00
a9082c57f3 Implement config-driven unlock groups so that multiple things can be unlocked at once (including buildings) 2026-07-22 21:34:59 +02:00
a8a6a04f1e Highlight tunnel connections in green when selected 2026-07-21 21:16:21 +02:00
a75222f111 Never rotate tunnels in place 2026-07-21 21:14:27 +02:00
9b63af6ccb Unify tunnel build mode into a single Tunnel button 2026-07-21 21:12:06 +02:00
4ca5b332cd Snap belt-drag end tile to a building's input edge 2026-07-21 21:09:07 +02:00
0c4eb480be Update belt drag L-path immediately on rotate 2026-07-21 21:06:54 +02:00
b2c1ea34fd Implement deferred L-shaped belt drag placement 2026-07-21 21:05:31 +02:00
1cbc695bc5 Add building status light 2026-07-20 22:19:58 +02:00
c7ecce6ac4 make empty layout cells pulse while module is selected 2026-07-20 22:15:26 +02:00
be475e2836 allow multi-select for ships/stations, mixable with scrap 2026-07-20 21:03:11 +02:00
1cdafb7bcd draw glyph for output port at the target tile during build mode 2026-07-19 22:30:03 +02:00
f205136e21 show tooltip for artifacts in the header bar 2026-07-19 22:16:04 +02:00
b708e1b29d Add demolish-mode vignette 2026-07-19 21:51:12 +02:00
c76ba07bc7 Correct top game speed step to 10x in requirements 2026-07-19 21:37:47 +02:00
b2e7e4897a Add paused-state vignette border to game world view 2026-07-19 21:34:56 +02:00
c21af63e84 deselect build tool when it becomes unaffordable and fix stale enabled button 2026-07-19 21:20:59 +02:00
9c1e948ab4 show selected ship's current behavior in sidebar panel 2026-07-14 21:19:14 +02:00
486296feee Allow direct output-to-input port coupling between adjacent buildings 2026-07-14 20:23:24 +02:00
c9f14970a1 Animate items entering building input ports 2026-07-14 20:21:25 +02:00
6a8c456aa1 Animate items emerging from building output ports 2026-07-14 20:18:47 +02:00
af8a2224c0 fix issue where items were accepted by a belt from opposite travel direction 2026-07-14 20:15:39 +02:00
4b149d97a7 dim the game behind dialogs and escape menu 2026-07-13 22:11:21 +02:00
535d4f8f24 allow to (multi) select scrap 2026-07-13 21:09:01 +02:00
8c4fb78fc9 Allow creating blueprints from construction sites 2026-07-13 20:50:13 +02:00
698dd4d13d auto-open layout dialog on manual schematic change 2026-07-13 20:47:37 +02:00
9d28175b17 Always show shipyard layout preview and Configure button, disabled until schematic selected 2026-07-13 20:45:03 +02:00
69fe607157 Show total building block cost in multi-selection panel (relevant for temporary blueprint) 2026-07-13 20:31:40 +02:00
ad3e73fdd8 Add tooltip for building blocks in header bar 2026-07-12 21:24:11 +02:00
177809fe1a Add tooltips for building buttons and module selection buttons 2026-07-12 21:21:14 +02:00
4986c1bac8 implement copy building settings with shift + click gesture 2026-07-09 20:29:20 +02:00
e110e7e413 Add T hotkey for temporary blueprint from selection 2026-07-09 20:25:15 +02:00
6274b5ce60 List unlocked recipes with tooltips in schematic choice dialog instead of unlocked item names 2026-07-09 20:22:17 +02:00
16ed6a5695 Show schematic id for all three types of drops in choice dialog 2026-07-09 20:18:38 +02:00
13dacab274 remove save and load buttons for factory blueprints to mirror the ship-layout blueprints 2026-07-09 20:15:30 +02:00
2ddf13238c Fix Salvage Bay drop-off not working by adding config-driven buffer capacity 2026-07-09 20:11:12 +02:00