remove plan since it is already implemented
This commit is contained in:
410
docs/plan.md
410
docs/plan.md
@@ -1,410 +0,0 @@
|
|||||||
# Implementation Plan — Steps 4 through 8
|
|
||||||
|
|
||||||
Cross-references: `architecture.md` (design), `requirements.md` (REQ-* citations).
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
| Step | Scope | State |
|
|
||||||
|------|-------|-------|
|
|
||||||
| 1 | Config loading (Formula, ConfigLoader, all config structs) | ✅ done |
|
|
||||||
| 2 | Simulation shell + TickDriver + entity id allocator + event queues | ✅ done |
|
|
||||||
| 3 | Belt subsystem (placement, port interface, per-tile v1, splitter routing, clearTiles, visual iteration) | ✅ done |
|
|
||||||
| 4 | Buildings + placement + belt↔building transport | ✅ done |
|
|
||||||
| 5 | Scrap + ships skeleton (data + spawning, no AI) | ✅ done |
|
|
||||||
| 6 | Ship behavior systems + movement arbitration | ✅ done |
|
|
||||||
| 7 | Waves, threat accumulation, combat resolution, deaths & loot | ✅ done |
|
|
||||||
| 8 | UI layer (GameWorldView, visuals.toml, panels, build/demolish, speed controls) | ⬜ |
|
|
||||||
|
|
||||||
Tick order reference (architecture.md §Tick Order):
|
|
||||||
1. Wave scheduler — step 7
|
|
||||||
2. Threat accumulation — step 7
|
|
||||||
3. Belt→building pull — step 4
|
|
||||||
4. Building production — step 4
|
|
||||||
5. Building→belt push — step 4
|
|
||||||
6. Belt tick — step 3 ✅
|
|
||||||
7. Ship behavior systems — step 6
|
|
||||||
8. Combat resolution — step 7
|
|
||||||
9. Deaths & loot — step 7
|
|
||||||
10. `tickMovement` — step 6
|
|
||||||
11. Scrap despawn — step 5
|
|
||||||
|
|
||||||
Each new subsystem slots into `Simulation::tick()` in this exact order.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4 — Buildings + placement + belt↔building transport
|
|
||||||
|
|
||||||
Covers REQ-BLD-*, REQ-MAT-*. Introduces the first stateful gameplay loop: miners pull nothing, produce ore, push onto belts; smelters pull ore, produce ingots; etc.
|
|
||||||
|
|
||||||
### New types (`src/lib/sim/`)
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct InputBuffer {
|
|
||||||
std::map<ItemType, int> counts;
|
|
||||||
std::map<ItemType, int> caps; // per-material; = 2× per-cycle requirement
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputBuffer {
|
|
||||||
std::vector<Item> items;
|
|
||||||
int capacity; // 2× per-cycle output; 1× for ReprocessingPlant
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Production {
|
|
||||||
std::string recipeId;
|
|
||||||
Tick completesAt;
|
|
||||||
std::vector<Item> chosenOutputs; // resolved at cycle start for reprocessing
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Building {
|
|
||||||
EntityId id;
|
|
||||||
QPoint tile; // origin of footprint (top-left)
|
|
||||||
QSize footprint;
|
|
||||||
Rotation rotation;
|
|
||||||
BuildingType type;
|
|
||||||
float hp;
|
|
||||||
float maxHp;
|
|
||||||
std::string recipeId; // current recipe; empty = none selected
|
|
||||||
InputBuffer inputBuffer;
|
|
||||||
OutputBuffer outputBuffer;
|
|
||||||
std::optional<Production> production;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Surface-mask parsing (new utility in `src/lib/config/`)
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct ParsedSurfaceMask {
|
|
||||||
QSize footprint;
|
|
||||||
std::vector<QPoint> bodyCells; // relative to tile origin
|
|
||||||
std::vector<Port> outputPorts; // tile = adjacent cell OUTSIDE footprint
|
|
||||||
// direction = away from building
|
|
||||||
std::vector<QPoint> shipDockCells; // 'S' cells — for salvage bay / shipyard
|
|
||||||
};
|
|
||||||
|
|
||||||
ParsedSurfaceMask parseSurfaceMask(const std::vector<std::string>& rows,
|
|
||||||
Rotation rotation);
|
|
||||||
```
|
|
||||||
|
|
||||||
Conventions (inferred from `buildings.toml`):
|
|
||||||
- `A` = body cell
|
|
||||||
- `S` = ship dock cell (part of footprint; shipyard/salvage bay)
|
|
||||||
- `>`, `<`, `^`, `v` = direction marker on cell ADJACENT to body, NOT part of footprint
|
|
||||||
- space = empty within bounding box
|
|
||||||
- Rotation transforms the grid 90°/180°/270° around the mask origin
|
|
||||||
|
|
||||||
### Placement + BuildingSystem
|
|
||||||
|
|
||||||
Either a new `BuildingSystem` class in `src/lib/sim/` or methods on `Simulation`. Recommended: a `BuildingSystem` owned by `Simulation`, mirroring `BeltSystem`'s pattern.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
class BuildingSystem {
|
|
||||||
public:
|
|
||||||
BuildingSystem(const GameConfig& config, BeltSystem& belts,
|
|
||||||
std::function<EntityId()> allocateId,
|
|
||||||
std::mt19937& rng);
|
|
||||||
|
|
||||||
// Placement (called by UI commands in Step 8)
|
|
||||||
EntityId place(BuildingType type, QPoint tile, Rotation rotation);
|
|
||||||
void demolish(EntityId id);
|
|
||||||
void setRecipe(EntityId id, const std::string& recipeId);
|
|
||||||
|
|
||||||
// Tick hooks — called from Simulation::tick() in the correct order
|
|
||||||
void tickBeltPull(); // step 3
|
|
||||||
void tickProduction(); // step 4
|
|
||||||
void tickBeltPush(); // step 5
|
|
||||||
|
|
||||||
// Queries (for UI)
|
|
||||||
const Building* find(EntityId id) const;
|
|
||||||
std::vector<Building> all() const; // for rendering
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
Belts and splitters are registered with `BeltSystem` directly from `BuildingSystem::place` when type == Belt or Splitter — these don't get `Building` instances (architecture.md §Buildings).
|
|
||||||
|
|
||||||
### Production cycle (REQ-MAT-CYCLE)
|
|
||||||
|
|
||||||
In `tickProduction`:
|
|
||||||
```
|
|
||||||
for each building with recipeId set:
|
|
||||||
if building has active production:
|
|
||||||
if currentTick >= production.completesAt:
|
|
||||||
deposit chosenOutputs into outputBuffer
|
|
||||||
clear production
|
|
||||||
continue
|
|
||||||
|
|
||||||
// idle: try to start a new cycle
|
|
||||||
recipe = config.findRecipe(recipeId)
|
|
||||||
if inputs available in buffers AND outputs fit in outputBuffer:
|
|
||||||
consume inputs
|
|
||||||
if reprocessing: roll chosenOutputs via discrete_distribution on probabilities
|
|
||||||
else: chosenOutputs = recipe.outputs (expanded by amounts)
|
|
||||||
// re-check fit for reprocessing (chosen output must fit)
|
|
||||||
production = {recipeId, currentTick + secondsToTicks(recipe.durationSeconds), chosenOutputs}
|
|
||||||
```
|
|
||||||
|
|
||||||
Reprocessing uses `Simulation`'s `std::mt19937` + `std::discrete_distribution<>`. Do NOT use the legacy `WeightedRandomGenerator` (uses `auto` and float precision).
|
|
||||||
|
|
||||||
### Belt↔building interaction
|
|
||||||
|
|
||||||
`tickBeltPull` (step 3): for each building with `recipeId`, walk its footprint's edges; for each adjacent tile, construct `Port{adjTile, directionFromBeltToBuilding}` and call `belts.tryTakeItem(port)`. Accept if the item matches a required input AND the per-material buffer has space.
|
|
||||||
|
|
||||||
`tickBeltPush` (step 5): for each output port on each building with items in outputBuffer, call `belts.tryPutItem(port, item)`. On success, remove from buffer.
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
**New:**
|
|
||||||
- `src/lib/sim/Building.h`
|
|
||||||
- `src/lib/sim/BuildingSystem.h` / `.cpp`
|
|
||||||
- `src/lib/config/SurfaceMask.h` / `.cpp`
|
|
||||||
- `src/test/BuildingTest.cpp`
|
|
||||||
- `src/test/SurfaceMaskTest.cpp`
|
|
||||||
|
|
||||||
**Modified:**
|
|
||||||
- `src/lib/sim/Simulation.h` / `.cpp` — own `BeltSystem` + `BuildingSystem`; call their tick hooks in order
|
|
||||||
- `src/lib/sim/CMakeLists.txt`
|
|
||||||
- `src/lib/config/CMakeLists.txt`
|
|
||||||
- `src/test/CMakeLists.txt`
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- **Surface mask:** all four rotations of miner, smelter, splitter; output ports land on correct adjacent cells
|
|
||||||
- **Placement:** place miner, verify footprint occupies expected tiles; demolish removes it
|
|
||||||
- **Belt registration:** placing a Belt calls `BeltSystem::placeBelt`; demolishing calls `removeTile`
|
|
||||||
- **Miner cycle:** miner with `mine_iron_ore` recipe deposits iron_ore into outputBuffer after recipe duration ticks
|
|
||||||
- **Smelter cycle:** feed iron_ore into input buffer, 2 ore → 1 ingot in output after duration
|
|
||||||
- **Output buffer cap:** buffer fills to 2×, production stalls
|
|
||||||
- **Reprocessing cap:** buffer holds exactly 1× (REQ-MAT-OUTPUT-BUFFER-REPROCESSING)
|
|
||||||
- **Reprocessing RNG:** seed-deterministic weighted output pick; N trials match expected distribution within tolerance
|
|
||||||
- **Belt pull:** belt adjacent to smelter input edge delivers ore; smelter input buffer increments
|
|
||||||
- **Belt push:** miner outputBuffer drains onto adjacent belt each tick when space available
|
|
||||||
- **Recipe change:** `setRecipe` clears input + output buffers (REQ-MAT-INPUT-BUFFER, REQ-MAT-OUTPUT-BUFFER)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5 — Scrap + ships skeleton
|
|
||||||
|
|
||||||
Data structures + spawning only. No AI yet. Covers REQ-RES-SCRAP-DROP, REQ-SHP-STATS, REQ-BLD-SHIPYARD scaffolding.
|
|
||||||
|
|
||||||
### New types
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct Scrap {
|
|
||||||
EntityId id;
|
|
||||||
QVector2D position; // tile units; ship-center convention
|
|
||||||
int amount;
|
|
||||||
Tick despawnAt;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Weapon { float damage; float range; float fireRateHz;
|
|
||||||
float cooldownTicks; std::optional<EntityId> currentTarget; };
|
|
||||||
struct SalvageCargo { int capacity; int current; };
|
|
||||||
struct RepairTool { float ratePerTick; std::optional<EntityId> currentTarget; };
|
|
||||||
|
|
||||||
struct ThreatResponse { float engagementRange; /* CombatStance, CombatTargetPriority */
|
|
||||||
std::optional<EntityId> currentTarget; };
|
|
||||||
struct ScrapCollector { std::optional<QVector2D> scrapTarget; EntityId deliveryBay; };
|
|
||||||
struct RepairBehavior { /* RepairTargetPriority */ std::optional<EntityId> currentTarget; };
|
|
||||||
struct HomeReturn { float retreatHpFraction; QVector2D homePos; };
|
|
||||||
|
|
||||||
struct Ship {
|
|
||||||
EntityId id;
|
|
||||||
QVector2D position;
|
|
||||||
QVector2D velocity;
|
|
||||||
float hp;
|
|
||||||
float maxHp;
|
|
||||||
int level;
|
|
||||||
std::string blueprintId; // matches ShipDef::id
|
|
||||||
|
|
||||||
std::optional<Weapon> weapon;
|
|
||||||
std::optional<SalvageCargo> cargo;
|
|
||||||
std::optional<RepairTool> repairTool;
|
|
||||||
std::optional<ThreatResponse> threatResponse;
|
|
||||||
std::optional<ScrapCollector> scrapCollector;
|
|
||||||
std::optional<RepairBehavior> repairBehavior;
|
|
||||||
std::optional<HomeReturn> homeReturn;
|
|
||||||
|
|
||||||
MovementIntent intent;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### `ShipSystem` / `ScrapSystem`
|
|
||||||
|
|
||||||
Small classes owned by `Simulation`:
|
|
||||||
- `ShipSystem::spawn(ShipDef, level, QVector2D position)` — builds a Ship from the config by evaluating per-role formulas at `level`; components present iff corresponding `ShipDef` sections are present
|
|
||||||
- `ShipSystem::forEach(…)` — for Step 6 behavior systems to iterate
|
|
||||||
- `ScrapSystem::spawn(QVector2D position, int amount)` — tick step 9 caller
|
|
||||||
- `ScrapSystem::tickDespawn()` — step 11
|
|
||||||
|
|
||||||
Still no AI tick hooks; `Simulation::tick()` gains step 11 only.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- **Ship spawn:** combat ship has Weapon + ThreatResponse; salvage ship has SalvageCargo + ScrapCollector; stats evaluated from formulas at given level
|
|
||||||
- **Component absence:** salvage ship has no Weapon; combat ship has no SalvageCargo
|
|
||||||
- **Scrap spawn + despawn:** scrap created with `despawnAt = currentTick + secondsToTicks(world.scrapDespawnSeconds)`; after that many ticks `tickDespawn` removes it
|
|
||||||
- **Entity ids:** spawned ships/scrap receive strictly increasing ids from `Simulation::allocateId` (needs to be exposed to `ShipSystem`/`ScrapSystem` via constructor callback)
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
New: `Scrap.h`, `Ship.h`, `ShipSystem.h/.cpp`, `ScrapSystem.h/.cpp`, `ShipTest.cpp`, `ScrapTest.cpp`.
|
|
||||||
Modified: `Simulation.*`, `src/lib/sim/CMakeLists.txt`, `src/test/CMakeLists.txt`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 6 — Ship behavior systems + movement arbitration
|
|
||||||
|
|
||||||
All four behaviors + `tickMovement`, one at a time with focused tests. Movement intent priority (architecture.md §Movement Arbitration):
|
|
||||||
|
|
||||||
```
|
|
||||||
HomeReturn > ThreatResponse > RepairBehavior > ScrapCollector
|
|
||||||
priorities: 4 3 2 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Behaviors write `MovementIntent{priority, target}` on the ship; higher priority overwrites lower. `MovementIntent` is cleared at the start of the ship behavior step.
|
|
||||||
|
|
||||||
### Sub-steps (independent commits recommended)
|
|
||||||
|
|
||||||
**6a. `tickHomeReturn`** — if `hp/maxHp < retreatHpFraction`, write intent toward `homePos` with priority 4.
|
|
||||||
|
|
||||||
**6b. `tickThreatResponse`** — acquire enemy target within `engagementRange` if none; hold existing target if still valid. If target in weapon range, fire (emit FireEvent, apply damage to target's hp, start cooldown — stays in Step 7 combat resolution if we want to centralize damage; for modularity, fire here). Else write intent toward target, priority 3.
|
|
||||||
|
|
||||||
**6c. `tickRepairBehavior`** — find damaged friendly target; move toward if out of repair range, repair if in range. Priority 2.
|
|
||||||
|
|
||||||
**6d. `tickScrapCollector`** — if cargo full, intent = `deliveryBay.tile`; else find nearest scrap, intent = scrap.position. On arrival, consume scrap (calls into `ScrapSystem`), increment cargo. Priority 1.
|
|
||||||
|
|
||||||
**6e. `tickMovement`** — for each ship with an intent, advance position toward `intent.target` by `speedPerTick` (from ShipDef speed formula). No pathfinding v1 — straight line.
|
|
||||||
|
|
||||||
### Design decision: combat resolution split
|
|
||||||
|
|
||||||
Two options for where fire/damage happens:
|
|
||||||
- (A) Inside `tickThreatResponse` — simpler, atomic
|
|
||||||
- (B) In a separate `tickCombatResolution` step 8 — matches architecture.md exactly
|
|
||||||
|
|
||||||
Recommend (B) for fidelity to architecture.md. `tickThreatResponse` only sets target + writes movement intent. Step 7 runs combat resolution across ships + stations uniformly.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- Intent priority: ship with low hp + weapon + enemy in range routes to homePos, not enemy
|
|
||||||
- Target acquisition: closest enemy within engagementRange; unchanged while still valid
|
|
||||||
- Repair ship finds damaged ally, moves in, repairs
|
|
||||||
- Salvage ship picks up scrap, returns when cargo full, cargo empties at delivery bay
|
|
||||||
- Movement: ship travels exactly `speed × secondsToTicks(duration)` tiles over N ticks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 7 — Waves + threat + combat + deaths & loot
|
|
||||||
|
|
||||||
Fills tick steps 1, 2, 8, 9. Covers REQ-WAV-*, REQ-SHP-FIRING-*, REQ-DEF-*, REQ-PSH-*, REQ-RES-SCRAP-DROP.
|
|
||||||
|
|
||||||
### Tick step 1 — Wave scheduler
|
|
||||||
|
|
||||||
```
|
|
||||||
- advance m_waveTimer by 1 tick
|
|
||||||
- if between waves: at wave trigger (random gap within world.waves.gap_min/max_seconds),
|
|
||||||
compute wave composition by drawing ship picks up to threat budget
|
|
||||||
(REQ-WAV-TRIGGER, REQ-WAV-THREAT-COST) using world.waves.threat_rate_formula
|
|
||||||
- schedule spawn times across spawn_duration_seconds
|
|
||||||
- spawn any enemy ships whose scheduled tick has arrived
|
|
||||||
```
|
|
||||||
|
|
||||||
Ships eligible for waves: those with `threat.costFormula(elapsedSeconds) > 0`.
|
|
||||||
|
|
||||||
### Tick step 2 — Threat accumulation
|
|
||||||
|
|
||||||
`m_threatLevel += max(0.0, world.waves.threatRateFormula.evaluate(elapsedSeconds)) × kTickDurationSeconds`.
|
|
||||||
|
|
||||||
### Tick step 8 — Combat resolution
|
|
||||||
|
|
||||||
Unified across ships + defence stations (player + enemy). Each shooter has {damage, range, fireRateHz, cooldown, currentTarget}. If target in range and cooldown ≤ 0:
|
|
||||||
- apply damage to target's hp
|
|
||||||
- emit `FireEvent{shooter.id, target.id, currentTick}` into `Simulation::m_fireEvents`
|
|
||||||
- set cooldown = `kTickRateHz / fireRateHz`
|
|
||||||
|
|
||||||
Stations fire per REQ-DEF-PLAYER-FIRE and REQ-PSH-STATION-FIRE; stats from config formulas at their level / generation.
|
|
||||||
|
|
||||||
### Tick step 9 — Deaths & loot
|
|
||||||
|
|
||||||
- For each entity with hp ≤ 0: drop scrap at position (REQ-RES-SCRAP-DROP); amount from ShipDef.loot.scrapDrop or station scrap formula
|
|
||||||
- Track enemy defence station "sets": if a full set destroyed this tick, award player one blueprint (REQ-DEF-BLUEPRINT-DROP); emit `BlueprintDropEvent`
|
|
||||||
- Remove dead entities (ships, scrap, buildings)
|
|
||||||
|
|
||||||
### Push mechanic (REQ-PSH-*)
|
|
||||||
|
|
||||||
When enemy wave progresses beyond contest zone: `world.push` expansion triggers, enemy defence station set spawns at new front, scaling_factor applied to formulas. This may belong in a dedicated `PushSystem` or fold into the wave scheduler. Decide at implementation time.
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
New: `WaveSystem.h/.cpp`, `CombatSystem.h/.cpp`, maybe `PushSystem.h/.cpp`, corresponding `*Test.cpp`.
|
|
||||||
Modified: `Simulation.*` to wire in tick steps 1, 2, 8, 9; `ShipSystem` to expose iteration; `BuildingSystem` to expose defence stations for combat.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
- Threat accumulates per second from the formula
|
|
||||||
- Wave spawn count matches threat budget / ship cost
|
|
||||||
- Fire event emitted + drainable + cleared
|
|
||||||
- Shooter on cooldown does not fire
|
|
||||||
- Ship at hp ≤ 0 drops scrap; scrap amount matches ShipDef
|
|
||||||
- Full enemy station set destroyed → BlueprintDropEvent with correct newLevel / wasNewUnlock
|
|
||||||
- Damage to HQ decrements HQ hp — game-over condition emitted when hp ≤ 0 (if we model it that way)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 8 — UI layer
|
|
||||||
|
|
||||||
Big step. Break into sub-phases to keep each commit reviewable.
|
|
||||||
|
|
||||||
### 8a. Visuals config + window scaffolding
|
|
||||||
|
|
||||||
- New `visuals.toml` (REQ-UI, architecture.md §Rendering → Visual Parameters) — per-type fill/outline/glyph entries
|
|
||||||
- `src/ui/VisualsConfig.h/.cpp`, `src/ui/VisualsLoader.h/.cpp` — fail-fast on missing entries for any known sim id
|
|
||||||
- Main window widget: header bar + central game view + right-hand selected-building panel (QDockWidget or split layout)
|
|
||||||
- Wire `QApplication` + `Simulation + TickDriver` into `main.cpp` replacing the current stub
|
|
||||||
- Sim + UI share one thread; paintEvent reads sim state directly (no locks — architecture.md §Threading)
|
|
||||||
|
|
||||||
### 8b. GameWorldView (render only, no input)
|
|
||||||
|
|
||||||
- `QOpenGLWidget` subclass with `QPainter` drawing
|
|
||||||
- `QTimer` @ 60 Hz → `update()` + advances sim via `TickDriver::advance(elapsedMs, gameSpeedMultiplier)` → calls `sim.tick()` N times
|
|
||||||
- Layer order per architecture.md §Layer Order (tiles → buildings → belt items → scrap → ships → beams → overlays → screen-space)
|
|
||||||
- Scroll via `scrollXTiles` float, A/D keyboard input, clamped per REQ-GW-SCROLL-LIMIT
|
|
||||||
- Mouse→world conversion: `worldX = mouseX / 20 + scrollXTiles`
|
|
||||||
- Beam renderer: keeps `FireEvent`s for 0.3 s wall time (9 ticks @ 30 Hz), drops if either end entity is gone
|
|
||||||
- Blueprint toasts: keeps `BlueprintDropEvent`s for configured toast duration
|
|
||||||
|
|
||||||
### 8c. Input → sim commands
|
|
||||||
|
|
||||||
- Tile click: select building / select belt tiles (box drag)
|
|
||||||
- Builder mode: open from build button grid; shows ghost on cursor; click places construction site (REQ-BLD-PLACE); drag-to-place for belts (REQ-BLD-DRAG)
|
|
||||||
- Demolish mode: click building → demolish (confirm), returns refund (REQ-BLD-DEMOLISH)
|
|
||||||
- Selected-building panel: recipe picker, clear-belt button (REQ-UI-BELT-CLEAR), splitter filter config, demolish button
|
|
||||||
- Speed controls: 0 / 0.5× / 1× / 2× / 4× (REQ-UI-SPEED) — bound to spacebar pause + number keys
|
|
||||||
|
|
||||||
### 8d. Header bar + polish
|
|
||||||
|
|
||||||
- Resource counters (building blocks, blueprint collection)
|
|
||||||
- Threat meter
|
|
||||||
- Wave countdown
|
|
||||||
- FPS / speed indicator
|
|
||||||
- Minor polish: hover highlights, keyboard shortcuts, tooltip on build buttons
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
New: `src/ui/` populated — `MainWindow.*`, `GameWorldView.*`, `HeaderBar.*`, `BuildButtonGrid.*`, `SelectedBuildingPanel.*`, `VisualsConfig.*`, `VisualsLoader.*`, `Toast.*`, etc.
|
|
||||||
Modified: `src/ui/CMakeLists.txt` — flip from INTERFACE library to regular static library; enable AUTOMOC; add `Q_OBJECT` macros where needed. `src/app/main.cpp` — construct sim + main window.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
UI code is largely visual; prioritize:
|
|
||||||
- Visuals loader fail-fast on missing entries
|
|
||||||
- Simulation + TickDriver integration test: at 1×, 60 render frames produce ~30 sim ticks (approximately — tolerate ±1 for accumulator residue)
|
|
||||||
- Manual smoke test checklist (in-repo markdown) for builder mode, demolish, recipe change, clear belt, speed toggling
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Things to revisit as needed
|
|
||||||
|
|
||||||
- **Pathfinding for ships:** straight-line in v1 is fine given open space; only revisit if enemy defence stations create obstacles
|
|
||||||
- **Belt segment compression (v2):** only if v1 per-tile profiling is bad
|
|
||||||
- **Worker thread for sim:** only if paint stalls become visible; `drain*` APIs already support it
|
|
||||||
- **ECS migration for ships:** only if component iteration becomes a bottleneck
|
|
||||||
- **Belt curves rendering:** derive from consecutive belt tile directions; sim logic is unaffected
|
|
||||||
Reference in New Issue
Block a user