rendering requirements

This commit is contained in:
2026-04-18 22:47:08 +02:00
parent 13f4800191
commit 8d4fece87d
2 changed files with 89 additions and 1 deletions

View File

@@ -102,7 +102,7 @@ Within a single simulation tick, subsystems run in this fixed order. The order i
Three product targets plus tests:
- `lib/` — simulation + config. Depends on Qt Core, toml++, tinyexpr. No QtWidgets.
- `ui/` — QtWidgets code: header bar, game world view, selected building panel, build button grid. Depends on `lib`.
- `ui/` — QtWidgets + `QOpenGLWidget` code: header bar, game world view, selected building panel, build button grid. Depends on `lib` and on Qt's OpenGL widgets module.
- `app/` — thin `main()` that creates the simulation, the UI, and wires them together. Depends on `ui`.
- `tests/` — Catch2 tests. Links only against `lib`.
@@ -250,6 +250,93 @@ EnTT would be a reasonable fit for ships specifically, but at the scale of this
Buildings and the belt subsystem stay outside any entity model regardless of what ships do — they are the wrong shape for ECS.
## Rendering
The game world is rendered by a single `GameWorldView` widget that inherits `QOpenGLWidget` and uses `QPainter` for all drawing. This gives the same imperative paint API as a plain `QWidget` with GPU acceleration, comfortably handling the expected scale (hundreds of ships, thousands of belt items) without blocking the main thread on CPU rasterization.
### Render Loop
- A `QTimer` in `GameWorldView` fires at 60 Hz and calls `update()`, requesting a repaint. Render rate is fixed at 60 FPS regardless of game speed.
- The sim advances independently via an accumulator-based driver:
- Each frame, compute `accumulator += elapsedWallMs * gameSpeedMultiplier`.
- While `accumulator >= tickDurationMs` (= 1000/30 ≈ 33.33 ms), advance the sim by one tick and subtract.
- 0× clamps the multiplier to 0 (pause). 0.5× / 2× / 4× scale accumulation directly.
- This decouples render rate (60 FPS) from sim rate (30 Hz, see Fixed-Timestep Tick-Based Simulation above) and keeps all game speeds correct across variable frame timing.
### Threading
Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly without locks. If profiling later justifies moving the sim to a worker thread, the pull-style `drainFireEvents()` / `drainBlueprintDropEvents()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice.
### Layer Order (back to front)
1. **Tile background** — asteroid tiles and space tiles within the viewport.
2. **Buildings** — factory buildings, HQ, player and enemy defence stations.
3. **Belt items** — 10×10 colored squares emitted by `BeltSystem::forEachVisualItem`.
4. **Scrap** — glyphs at world positions.
5. **Ships** — colored arrows oriented by velocity; color keyed to role (player combat / salvage / repair / enemy).
6. **Laser beams** — lines derived from live `FireEvent`s kept by the renderer for 0.3 s (REQ-SHP-FIRING-BEAM).
7. **Build overlays** — ghost in builder mode (REQ-BLD-GHOST), demolish-mode tint, tile highlight under cursor, box-drag selection rectangle.
8. **Screen-space UI** — blueprint toasts (REQ-UI-BLUEPRINT-TOAST) and any other screen-anchored elements, drawn after resetting the world-space transform.
### Coordinates and Scrolling
- `GameWorldView` holds a continuous `scrollXTiles` (float). A / D input pans this smoothly (REQ-UI-SCROLL).
- At the start of `paintEvent`, a single `painter.translate(-scrollXTiles * tilePx, 0)` maps world tile units into widget pixels (`tilePx = 20`, per REQ-GW-TILE-SIZE).
- Mouse input converts the other way: `worldX = mouseX / tilePx + scrollXTiles`; apply `floor` for a tile. Asteroid tiles (`x < 0`) need no special casing — they share the coordinate system with space tiles.
### Culling
The renderer iterates only entities and tiles whose world X lies within the visible viewport. In particular, everything at or past the rightmost enemy defence station (i.e., the current enemy buffer zone) is culled — consistent with REQ-GW-SCROLL-LIMIT.
### Visual Parameters
Shapes are hardcoded in the renderer — a building is a rectangle per footprint tile, a ship is an oriented arrow/triangle, a belt item is a 10×10 square, scrap is a small circle, a beam is a line. These structural choices live in the `draw<X>(painter, entity)` functions of the UI and are not expected to change frequently.
Colors, outline widths, glyph text, and tile tints live in a separate config file, `visuals.toml`, loaded once by the UI at startup using the same pattern and lifetime as the sim config files (see Config Loading). The file is UI-scoped: the sim does not read it and does not depend on it.
Sketch of `visuals.toml`:
```toml
[tiles]
asteroid = { fill = "#4a4038" }
space = { fill = "#0a0a15" }
[buildings.miner]
fill = "#6b4a2c"
outline = "#ffffff"
glyph = "M"
# ... one [buildings.<type>] section per BuildingType
# ... one [stations.<player|enemy>] section
# ... one [items.<item_type>] section per ItemType
[ships.player_combat] { fill = "#3366ff", outline = "#ffffff" }
[ships.salvage] { fill = "#33cc66", outline = "#ffffff" }
[ships.repair] { fill = "#66ccff", outline = "#ffffff" }
[ships.enemy] { fill = "#cc3333", outline = "#ffffff" }
[beams]
color = "#ff6600"
width_px = 2
[overlays]
ghost_valid = "#ffffff44"
ghost_invalid = "#ff000044"
demolish_tint = "#ff000033"
selection_rect = "#00ff00"
[toast]
bg = "#000000cc"
fg = "#ffffff"
font_size = 14
```
Key names mirror sim identifiers (`buildings.miner``BuildingType::Miner`, `items.<x>``ItemType`). The UI builds a lookup indexed by the sim's enum or string id; a missing or malformed entry aborts startup with a clear error, same as sim configs. Adding a new `BuildingType` or `ItemType` to the sim requires adding a matching `visuals.toml` entry — the fail-on-missing rule catches the omission at startup rather than silently rendering invisible entities.
### Animation
v1 uses no sprite atlases, no anti-aliasing, and no tick-to-tick interpolation (ships snap to their 30 Hz positions). This is not an architectural constraint — sprite atlases, AA, and interpolation are all incremental upgrades behind the same `draw<X>` functions and the `visuals.toml` schema.
## Testing
- Catch2 tests link against `lib` only. No QApplication, no display.