rendering requirements
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user