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: Three product targets plus tests:
- `lib/` — simulation + config. Depends on Qt Core, toml++, tinyexpr. No QtWidgets. - `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`. - `app/` — thin `main()` that creates the simulation, the UI, and wires them together. Depends on `ui`.
- `tests/` — Catch2 tests. Links only against `lib`. - `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. 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 ## Testing
- Catch2 tests link against `lib` only. No QApplication, no display. - Catch2 tests link against `lib` only. No QApplication, no display.

View File

@@ -9,6 +9,7 @@ Config files use the TOML format. The following config files drive game paramete
- **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities. - **recipes.toml** — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities.
- **ships.toml** — ship stats (HP, speed, damage, attack range, attack rate, sensor range) as formulas of ship level, required materials per blueprint, threat cost formula, and whether each blueprint is available from the start or unlocked via loot. - **ships.toml** — ship stats (HP, speed, damage, attack range, attack rate, sensor range) as formulas of ship level, required materials per blueprint, threat cost formula, and whether each blueprint is available from the start or unlocked via loot.
- **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level. - **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level.
- **visuals.toml** — rendering-only config (not game parameters): fill and outline colors, glyphs, and tile tints for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
### Surface Mask Format ### Surface Mask Format