schematic selection dialog

This commit is contained in:
2026-06-13 12:00:05 +02:00
parent 1641189b75
commit 49f7129bd5
25 changed files with 453 additions and 268 deletions

View File

@@ -60,22 +60,25 @@ Simulation types shared across subsystems:
- `Port``struct Port { QPoint tile; Rotation direction; }`. Identifies a belt-adjacent cell and the direction of flow across that cell.
- `MovementIntent``struct MovementIntent { int priority; QVector2D target; }`. Priority follows the order declared under Movement Arbitration. Cleared at the start of each tick; the highest-priority write wins; `tickMovement` reads the winner.
- `FireEvent``struct FireEvent { EntityId shooter; EntityId target; Tick emittedAt; }`. Transient record emitted each time a weapon fires (REQ-SHP-FIRING, REQ-SHP-FIRING-BEAM). Buffered in a sim-owned queue and drained by the renderer; see Sim → UI Events.
- `SchematicDropEvent``struct SchematicDropEvent { ShipSchematicId schematic; int newLevel; bool wasNewUnlock; }`. Emitted when a destroyed enemy-defence-station set awards a schematic (REQ-DEF-SCHEMATIC-DROP). The UI renders a toast (REQ-UI-SCHEMATIC-TOAST); `wasNewUnlock` chooses between the "unlocked" and "level → N" wording.
- `SchematicChoiceOption``struct SchematicChoiceOption { string schematicId; SchematicType type; string displayName; bool isNewUnlock; int targetLevel; }`. Describes one option in the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP). Up to three are generated when an enemy station set is destroyed. `SchematicType` is `Ship`, `Module`, or `Recipe`.
- `SchematicChoicesAvailableEvent` — EventManager event carrying a `vector<SchematicChoiceOption>`. Sent by the UI each frame when pending choices are detected; handled by `MainWindow` which opens the schematic choice dialog.
## Sim → UI Events
The sim owns a small set of per-frame event queues that the UI drains on each render. These carry one-shot signals that are not derivable from persistent state — currently weapon fires (REQ-SHP-FIRING-BEAM) and schematic drops (REQ-UI-SCHEMATIC-TOAST). Additional event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern.
The sim owns a per-frame event queue for weapon fires that the UI drains on each render. `FireEvent` records are one-shot signals not derivable from persistent state. Additional one-shot event types can be added here later (e.g., building-complete, unit-death flashes) without changing the pattern.
Implementation: a plain `std::vector<FireEvent>` owned by `Simulation`, one vector per event type. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early.
Implementation: a plain `std::vector<FireEvent>` owned by `Simulation`. Combat resolution (tick-order step 8) appends to it. The UI calls `simulation.drainFireEvents()` once per rendered frame, which returns the accumulated vector by move and clears the internal one. Beams are tracked by the renderer for 0.3 s of wall time (9 ticks at 30 Hz) using the events' `emittedAt` tick, then discarded. If either the shooter or target entity is gone when the renderer looks them up, the beam is dropped early.
We deliberately do **not** use `QObject` signals/slots or `QEvent`:
Schematic drops use a different pattern: when an enemy station set is destroyed, the simulation generates up to 3 `SchematicChoiceOption` entries and stores them as pending state. The UI polls `hasSchematicChoicesPending()` each frame and, when true, sends a `SchematicChoicesAvailableEvent` via the EventManager. `MainWindow` handles this event by pausing the game and opening a modal `SchematicChoiceDialog`. The player's selection is fed back via `applySchematicChoice(index)`, which applies the chosen schematic and clears the pending state.
We deliberately do **not** use `QObject` signals/slots or `QEvent` for the fire-event queue:
- **Determinism.** A plain ordered vector preserves tick-order exactly; the queue is part of per-tick state, inspectable in tests.
- **Sim/UI seam.** The sim exposes pull-style access only; the UI never subscribes into the sim, keeping the simulation/presentation split clean.
- **Headless testability.** Catch2 tests read the queue directly after `tick()`; no event loop, no `QApplication`.
- **Zero overhead.** Sim types remain plain structs — no `QObject`, no moc, no signal dispatch machinery.
If the number of event types grows past a handful, we can wrap them in a small `EventQueue<T>` template, still owned by the sim. Signals/slots would only be warranted if we needed multiple independent subscribers or cross-thread dispatch, and we need neither.
If the number of event types grows past a handful, we can wrap them in a small `EventQueue<T>` template, still owned by the sim.
## Tick Order
@@ -89,7 +92,7 @@ Within a single simulation tick, subsystems run in this fixed order. The order i
6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER).
7. **Ship behavior systems** — clear `MovementIntent` on each ship, then run `tickThreatResponse`, `tickScrapCollector`, `tickRepairBehavior`, `tickHomeReturn` in any order (arbitration is via intent priority).
8. **Combat resolution** — ships and defence stations acquire targets, fire, apply damage; queue deaths. Each fire appends a `FireEvent` to the sim's fire-event queue (REQ-SHP-FIRING-BEAM).
9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, award one schematic (REQ-DEF-SCHEMATIC-DROP) and append a `SchematicDropEvent`; remove entities.
9. **Deaths & loot** — process queued deaths: drop scrap (REQ-RES-SCRAP-DROP); if a full enemy-defence-station set was destroyed this tick, generate up to 3 schematic choice options (REQ-DEF-SCHEMATIC-DROP) stored as pending state for the UI to present; remove entities.
10. **`tickMovement`** — advance ship positions based on final `MovementIntent`.
11. **Scrap despawn** — decrement scrap timers; remove expired scrap (REQ-RES-SCRAP-DROP).
@@ -280,7 +283,7 @@ The game world is rendered by a single `GameWorldView` widget that inherits `QOp
### 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()` / `drainSchematicDropEvents()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice.
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()` / `getPendingSchematicChoices()` / `applySchematicChoice()` / `forEachVisualItem()` APIs already support a clean snapshot-and-render split; a single mutex at the sim boundary would suffice.
### Layer Order (back to front)
@@ -291,7 +294,7 @@ Sim and UI run on the same thread for v1. `paintEvent` reads sim state directly
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** schematic toasts (REQ-UI-SCHEMATIC-TOAST) and any other screen-anchored elements, drawn after resetting the world-space transform.
8. **Screen-space UI** — screen-anchored elements, drawn after resetting the world-space transform.
### Coordinates and Scrolling