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
This commit is contained in:
@@ -357,7 +357,8 @@ Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly
|
||||
|
||||
### Coordinates and Scrolling
|
||||
|
||||
- `GameWorldView` holds a continuous `scrollXTiles` (float), the world X at the *center* of the viewport. A / D input pans this smoothly (REQ-UI-SCROLL) at a position-dependent speed (REQ-UI-SCROLL-SPEED).
|
||||
- The horizontal view position lives in `WorldCamera` (`lib/core/`) as a continuous view-center X in tiles. A / D input pans it smoothly (REQ-UI-SCROLL) at a position-dependent speed (REQ-UI-SCROLL-SPEED). The camera works purely in world units — tiles and tiles/second, never pixels — which is what keeps it independent of `WorldCoordinates`; the two meet only where `GameWorldView` feeds `getViewCenterXTiles()` into the transform.
|
||||
- The camera takes no simulation dependency. Its pan limits move with asteroid expansion and with pushes, so `GameWorldView` reads them from the sim each frame and passes them in as `ScrollBounds`; the camera clamps on every `advance()`, not only when panning, so the view follows the bounds inward when they shrink. Pan *intent* is likewise passed in as a `PanDirection` rather than read from key state, so the camera is unaffected if controls later become rebindable. Both properties are what make it a plain value with unit tests (`WorldCameraTest`) — notably over the two-ramp pan-speed curve, whose overlapping-band and zero-width-band cases are otherwise easy to break unnoticed.
|
||||
- The world↔widget transform itself lives in `WorldCoordinates` (`lib/core/`), not in the view. It is an immutable value, built through one of two named factories that differ only in how `tilePx` and the left edge are derived; everything downstream is shared. `scrolling(...)` is the game world: `tilePx` makes the world height fill the viewport (REQ-GW-TILE-SIZE) and the view pans horizontally. `fitToWorld(...)` is the balancing tool's arena: a fixed world shown whole, so `tilePx` is the tighter of the two axis fits and there is no scroll. Being a plain value with no Qt Widgets dependency, it is unit-tested (`WorldCoordinatesTest`) even though the widgets around it are not.
|
||||
- `GameWorldView::getCoordinates()` and `ArenaView::getCoordinates()` each build one per frame in `paintGL` and per event in the mouse handlers, and pass it down: every world-space `draw<X>` takes a `const WorldCoordinates&`, while the screen-space draws (vignette borders, replay overlay, debug text) take none. The snapshot is deliberately never cached in a member — a resize or a scroll would silently invalidate it.
|
||||
- Conversions are per-call arithmetic rather than a `painter.translate`, because hit-testing needs the inverse (`widgetToWorld` / `widgetToTile`, flooring for a tile) as often as drawing needs the forward direction. Asteroid tiles (`x < 0`) need no special casing — they share the coordinate system with space tiles, which is why the flooring must not be truncation.
|
||||
|
||||
Reference in New Issue
Block a user