Spaces:
Paused
Paused
File size: 25,984 Bytes
6dbb379 6f39bc5 6dbb379 91ad84b 832f494 6dbb379 4ed9122 89beffa 6f39bc5 89beffa 32d2559 06de929 ee2f17b 06de929 32d2559 89beffa 4ed9122 6f39bc5 32d2559 06de929 6dbb379 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | # Logistics overhaul β brainstorm & phased design
Goal (from the request): make **storage finite** so collection and production
buildings can fill up and stall, and make **workers haul** goods between them, so
logistics stays a live, mindful process that keeps workers relevant to the end of
the game. Plus **energy efficiency by Reactor proximity** with placement cues.
**Status:** the *energy* half is **done and merged** (see below). The *finite-storage
+ haulage* half is a core-economy reshape and is designed here for a dedicated,
phased implementation β this doc is the plan of record.
---
## 1. What's already shipped β energy grid efficiency
Energy is a per-tick **flow**, not a hauled good, so its "logistics" is a *placement*
decision β no workers needed. Implemented in `engine/industry.js`:
- `POWER_TIERS` β distance-to-nearest-Reactor bands: on-grid Γ1.0 β near Γ1.3 β
far Γ1.7 β isolated Γ2.3 (draw multiplier).
- `powerEfficiency(state, owner, x, y)` β the tier for a spot.
- `powerDraw` scales every consumer (factory, rig, charging Gate) by its tier, so a
remote building bleeds capacity to transmission loss and throttles the whole grid;
clustering around a Reactor lets the same Reactors run more of it.
Visual cues mirror the Spaceport's jump radius (`render.js`): a **selected Reactor**
shows concentric efficiency zones; a **build ghost** for a power consumer shows the
nearest Reactor's zones, a connector, and the tier it would land in ("Far Β· draw
Γ1.7"); a **selected factory/rig** gains a colour-keyed "Grid: β¦ Β· draws ΓN" line
(`hud.js`). Pure distance math β deterministic, DOM-free, no new stored state.
---
## 2. Finite storage + haulage β the hard half
### 2.1 The core tension (why this is a real reshape, not a patch)
Today there is ONE global stockpile per side: `player.resources` (a commodityβfloat
map). Everything reads/writes it:
| System | Uses `player.resources` for |
| --- | --- |
| `gather.js` | workers bank hauls here |
| `industry.js` | factories draw inputs here, bank outputs here |
| `rig.js` | banks dug raws here |
| `market.js` | buy/sell price + settle against it |
| `production.js` | `canAfford` / `payCost` for units, buildings, upgrades |
| `wonder.js` | the Gate's `feed` is drawn from here |
| `ai.js` | every AI economic decision reads it |
| `hud.js` | the resource topbar shows it |
"Finite per-building storage" means goods no longer live in one global pool β they
live in **building buffers** and only become *spendable* once hauled to a Command
Center. That touches every row above. It must be phased, determinism-first, or it
breaks the byte-identical replay law and the AI at once.
### 2.2 Scope insight β who builds what
The **AI builds none of the industry chain** (no smelter/assembler/chipfab/
machineworks/reactor/plasmarig/β¦/torpedoworks). So **production-side** storage +
haulage is **player-only and self-contained**. The **collection-side** (Command
Center + Refinery/Foundry/Arsenal drop-offs) IS used by the AI, so finite collection
storage cascades to AI worker logic β the riskier half.
### 2.3 Forgotten aspects surfaced by the brainstorm
1. **The AI must not deadlock.** If drop-offs can fill, the AI's gather loop must
reroute or it starves. Simplest safe rule: the AI's Command Center keeps an
effectively huge cap (tier-scaled), so the AI never softlocks while the player
opts into tighter forward logistics. Alternatively, exempt the AI entirely in
phase 1 and only make the *player's* buildings finite.
2. **What is "spendable"?** Decide up front: are build/unit/upgrade costs and the
market paid from (a) the global pool as today, fed by haulage into the CC, or
(b) summed across all buildings? Recommendation: **(a)** β the CC pool is the
treasury; buffers are in-transit inventory. Keeps `canAfford`/market/AI intact.
3. **Determinism.** Any "nearest full/needy building" or worker task-assignment must
be order-independent and tie-broken deterministically (by id/hash, never by Map
iteration + wall clock). Reuse `gather.js`'s `nearestDropoff` pattern + `hashStr`.
4. **Save/load.** New `store` / `input` buffers on buildings must be coerced in
`persist.js cleanEntity` (COM-valid keys, β₯0, clamped to cap) exactly like
`freight` and the rig's `digProgress`.
5. **Auto vs manual haulage.** Workers should auto-haul (RTS players won't micro
every crate), but the player should be able to *prioritise* a building. Phase 1:
fully automatic, nearest-worker/nearest-target, so it "just works".
6. **Idle-worker sourcing.** Haulage needs a labour pool. Reuse idle workers (no
gather order); if none, production simply stalls β which is the intended pressure,
not a bug. Surface it ("output full β assign a hauler").
7. **Deadlock & starvation UX.** A stalled factory (output full / input starved)
already has a status line; extend it with the *reason* and a one-click "haul now".
8. **Balance / caps.** Buffer sizes set the rhythm. Too big β no logistics pressure;
too small β constant micro. Start generous (minutes of runtime per buffer) and
tune. CC cap scales with a `storeTier` (Capital upgrade β bigger treasury).
9. **Market side-effect.** If output must be hauled before it's sellable, a player
with full rig buffers but no haulers can't sell β intended, but must read clearly
in the HUD, not as "the market is broken".
10. **Freighters.** Interplanetary freight already exists (`unit.freight`); keep it
orthogonal β freighters move goods *between worlds*, workers move goods *within* a
world. Don't conflate. **Superseded by Phase D below**, which deliberately DOES
connect the two: workers can now physically load/unload a landed freighter's hold
(the ship becomes a mobile collection point *within* a world), and a researched
freighter can join the *within-world* haul/service chain itself. The panel-driven
load/unload (this section) and the interplanetary jump (`unit.freight`, moved
*between* worlds) are untouched β Phase D adds a THIRD, physical path onto the same
hold, it doesn't replace the first two.
### 2.4 Phased plan (each phase is its own merge, suite-green + determinism-verified)
**Phase A β Output buffer + worker-haulage foundation on the Plasma Rig. β
DONE.**
- Added `building.store` (commodityβqty) + `def.storeCap`, with pure helpers
`storeTotal`/`storeRoom`/`storeCapOf` (`entities.js`).
- `updatePlasmaRig` banks each dig into `store` (topping off the final dig to exactly
`storeCap`, overflow spills) and **stalls** the moment the buffer is full β an
unlimited *source*, no longer an unlimited *sink*.
- New worker task `haul` (`engine/haul.js`): an idle **player** worker auto-assigns to
the nearest own producer whose buffer is β₯34% full (β€2 haulers each, tie-broken by
id), walks there, loads a cargo, carries it to the nearest drop-off, and banks it
into the treasury **1:1** (no gather multiplier β the goods were already extracted).
A per-tick hauler tally (`countHaulers`) is frozen before assignment for determinism.
- `render.js`: a gold/red output-buffer gauge under a producer's hull.
- `hud.js`: rig panel shows "Output NN/CAP (N%)" + a "buffer full β needs a hauler"
stall reason.
- `persist.js`: coerce `store` (COM-valid, β₯0, clamped to cap); strip the transient
`haulers` tally on serialize (like a unit's `_gi`).
- **Player-only & AI-safe:** the auto-haul call is gated to `owner === "player"`, and
no skirmish building has a buffer, so the AI and the byte-identical skirmish replay
are untouched. Tests: `test/haul.test.js` (+ rewritten `test/rig.test.js`).
- **Why the Rig first:** it's a *leaf* producer of raw goods β its output doesn't feed
another factory, so gating it doesn't half-break the two-hop chain. This proves the
whole haulage subsystem (store, task, auto-assign, render, persist, determinism) on
the safest surface before it touches the coupled factory chain.
**Phase B β Factory output + INPUT buffers = a real supply network. β
DONE.**
- Factories now get a default output buffer AND a local `input` larder (both cap 80,
via `storeCapOf`/`inputCapOf` β any building with a `recipe`). `updateProduction`
draws inputs from the **local larder**, banks output to the **local store**, and stalls
when either the larder is empty or the output buffer is full β so a factory only runs
while it's been *supplied* and has *room*.
- Haulage gained a `supply` leg (`engine/haul.js`): an idle player worker picks the
nearest own factory that's low on an input the treasury can provide, carries a cargo
from the treasury (the CC/warehouse) into the factory's larder. Output is drained by
the Phase A haul path, which now covers factories automatically. Idle workers try haul
first (clear backlogs / refill the treasury) then supply (feed the starving).
- The chain is now a real network routed through the treasury: raws β CC β supply β
smelter β metals β haul β CC β supply β assembler β alloys β haul β CC. Verified
end-to-end in the live sim + browser (alloys reach the treasury purely via worker
logistics).
- `persist.js` coerces the `input` buffer and strips the transient `suppliers` tally;
`hud.js` factory panel shows the larder + output buffer and a "needs it carried in"
starve reason; the map store bar (Phase A) now covers factories too.
- Rewrote the `industry.js` + `strategic.js` chain tests to the buffered model; added
supply tests to `test/haul.test.js`. Player-only & AI-safe as in Phase A.
**Balance note (both phases):** default caps are generous (80 / rig 120). Tune later β
smaller = more logistics pressure, larger = less micro.
**Phase C β Finite COLLECTION storage. β€Ί REVERTED β Refinery/Foundry/Arsenal are no longer
drop-offs.** The forward-collection capability described below shipped, then was later
removed: the Refinery/Foundry/Arsenal no longer carry `dropOff`/`storeCap`, `isGatherDropOff`
now resolves to the Command Center alone, and the AI's forward-Refinery placement logic
(`aiEconomy.js`) was deleted along with it. Every gather/haul trip is a straight run to a
Command Center again. Left below for the historical record of what Phase C built and why.
- The forward drop-offs (Refinery/Foundry/Arsenal β the pure `dropOff` buildings, via
the new `isGatherDropOff`) get a finite `storeCap` intake buffer. A PLAYER gatherer
banks its haul into that buffer (capped); a full one is skipped by `nearestGatherDrop`
so the gatherer reroutes to the next collection point (the Command Center never fills).
Phase A haulage clears the buffer to the CC automatically. Factories are excluded from
gather drop-offs (their `store` is refined OUTPUT, not a raw-intake bin).
- The **Command Center is the treasury/warehouse** β effectively unbounded ("very large
storage"): haulage delivers there and supply picks up there (`nearestCommandCenter`),
so goods flow *through* the CC, never sideways between drop-offs.
- **AI-safe by construction:** the finite-intake path is gated to `owner === "player"`.
An AI drop-off banks straight to the treasury as before, and the AI builds no
producers so its workers never haul/supply β so the AI economy and the byte-identical
`determinism`/`determinism-roster` replays are untouched (verified: full suite green).
- `render.js` store bar (Phase A) covers forward drop-offs; `hud.js` shows a selected
drop-off's intake buffer + "FULL: gatherers reroute" state. Tests in `test/gather.test.js`
(finite intake + reroute) and `test/haul.test.js` (AI drop-off stays bufferless).
**Known pre-existing issue (surfaced, not caused, by Phase C):** two+ workers rapidly
cycling a drop-off planted *very close* to a node can separation-deadlock in the `toNode`
walk (confirmed identical on the pre-Phase-C code). It's a movement/separation bug, out
of scope here; a real placement keeps the drop-off far enough from the node to avoid it.
**Phase D β Freighters join the local logistics chain. β
DONE (player-only, Odyssey-only).**
Revisits item 10 above: a landed freighter (`hauler`/`heavyhauler`/`bulkfreighter`) is now a
*physical* object on the world map, not just a menu-driven hold, in two ways:
- **Workers can load/unload it β FERRY (`engine/haul.js updateFerry`).** Right-click a
friendly freighter with workers selected (`engine/commands.js issueFerryFreighter`,
`input.js`) to assign them to it, the same idiom as `issueServiceBuilding`. A ferry
worker keeps carrying the nearest own producer's backlog straight onto the freighter's
`freight` hold β the freighter becomes its own **collection point**, no detour through
the Command Center β and once there's nothing left to load, draws some of the hold back
out and banks it at the treasury instead. Any freighter qualifies, no research needed:
this is a worker loading a crate onto a ship, not the ship acting on its own. Shares
`nearestBacklogProducer` with `assignHaul` (parameterised: a fresh auto-haul still waits
for a β₯34%-full buffer; a worker already dedicated to a freighter takes any backlog) and
the same per-producer `haulers` cap, so it can't out-compete regular haulage for labour.
- **A freighter can BE the logistics chain β AI-LOGISTICS (`FREIGHTER_AI_TECH`).** A new
Datacenter tech, `freighterai` (requires `aicores`, costs AI Cores to research), unlocks
a per-freighter toggle (`unit.aiLogistics`, HUD button, `engine/commands.js
issueSetAILogistics`). Toggled on, a freighter offers itself to the SAME
`assignHaul`/`assignService` idle-worker logic every tick (`engine/sim.js`) β it grabs
a haul or service job exactly like a worker would, but at its own trip capacity: with no
`cargoCap` of its own, `tripCapacity()` falls back to the ship's whole `cargoHold`
(250/650/1600 vs. a worker's 10), so one autonomous run clears a backlog a worker would
need many trips for. Running this way isn't free: every tick an autonomous job is active
it burns AI Cores from the treasury (`payAIUpkeep`, rate scaled by the ship's own
`cargoHold` β a bigger hold costs more to run unmanned). Out of AI Cores, it just pauses
in place for the tick (order and cargo untouched) rather than stranding anything;
toggling AI-logistics off stands it down immediately. A freighter with no AI Cores in
stock also never *claims* a fresh job in the first place, so it doesn't sit there hogging
one of a producer's β€2 hauler slots while unable to move.
- Both paths reuse the SAME `loadFrom`/cargo machinery workers already use (generalised to
take a plain commodityβqty store instead of a building, so it works against either a
building's `store` or a freighter's `freight`) β no parallel implementation to keep in
sync. `freightUsed`/`freightRoom` moved from `engine/galaxy.js` to `engine/entities.js`
(re-exported from galaxy.js for every existing caller) so `engine/haul.js` can read a
freighter's room without an import cycle (`galaxy.js` β `sim.js` β `haul.js`).
- Player-only and Odyssey-only by construction (freighters are `odysseyOnly`, the tech is
gated behind the Datacenter/`aicores`, and the sim only ever offers this to
`owner === "player"`) β the AI never builds a freighter, so the skirmish AI and its
byte-identical replay are untouched. Tests in `test/ferry.test.js`.
---
**Phase E β Command Center zone affinity + a worker REPAIR job. β
DONE.**
Revisits a gap the original four phases didn't cover: on a multi-base empire, every "nearest"
job-pick above (`nearestBacklogProducer`, `assignService`'s factory scan, `assignFerry`'s
freighter scan) searched the WHOLE empire from the idle worker's current position with no notion
of "which base do I belong to." Once a busy base's own β€2-per-target caps (`MAX_HAULERS`/
`MAX_SERVERS`) filled up, its surplus idle labour got "poached" by whichever OTHER base's job
happened to be nearest in that flat global search β a routine long, arbitrary cross-map commute,
not just an occasional loan. The same gap existed in the auto-repair Mender's roam target pick
(`engine/sim.js autoRepairRoam`), which also scanned globally by worn-first/distance with no
zone awareness.
- **Zone affinity (`engine/gather.js zoneFirst`).** A building/unit's "zone" is simply whichever
of its owner's Command Centers sits nearest to it β computed on demand, never stored/cached, so
founding or losing a CC instantly redraws every boundary with nothing to invalidate. `zoneFirst`
runs a caller-supplied scan TWICE: first restricted to the searcher's own zone, and only if that
comes back empty, once more with no restriction β today's plain global search, now a last
resort instead of the routine case. A one-CC game (nearly every skirmish, and Odyssey before a
player expands) has exactly one zone, so behaviour there is byte-identical to before this
existed β verified by a dedicated test alongside the cross-zone ones.
`nearestBacklogProducer`/`assignService`/`assignFerry` (`engine/haul.js`) and the Mender's
`autoRepairRoam` (`engine/sim.js`, via the new `pickRepairTarget` below) all adopt it.
- **A worker REPAIR job (`engine/repair.js`).** Buildings could only be healed passively by a
Mender in range, or by an `autoRepair` Mender roaming to the worst-off one β nothing stationed
labour there when no Mender was around, even though Odyssey structures wear down on their own
(`updateDecay`). A generalist worker can now be assigned a `repair` order β auto (idle workers
offer themselves the same zone-first way haul/service already do, after ferry/haul/service in
priority) or manual (right-click a damaged own building with workers selected,
`issueRepairBuilding` β any completed building qualifies, not just a logistics-buffered one). It
walks to the building and patches it at a flat `WORKER_REPAIR_RATE` (free, like a Mender's heal β
time, not resources), capped at `MAX_REPAIRERS` per building so labour spreads out, same shape as
the haul/service caps. Buildings only β a Mender already covers wounded mobile units, and a
stationary "post a request to the nearest CC" model fits a fixed structure, not a unit that
might have moved by the time labour arrives; that's a deliberate v1 scope line, not an oversight.
`pickRepairTarget` (zone-first, worst-hp-fraction-first, the shared `NEEDS_REPAIR`/`HEALED`
hysteresis thresholds) is exported so the Mender's own roam target-pick reuses the exact same
selection logic instead of a second copy.
- **AI-safe and deterministic by the same construction as Phases A-D:** zone affinity and the
repair job are pure functions of state (id-tiebroken, no wall clock), and the Odyssey AI's own
worker logistics (`engine/aiWorkers.js assignAiLogistics`, endless-only) picks up both the zone
affinity and the new repair job for free by calling the same `engine/haul.js`/`engine/repair.js`
functions the player's workers use β its skirmish behaviour is untouched (`assignAiLogistics` is
never invoked outside Odyssey). Tests: `test/zones.test.js` (cross-base zone preference for
haul/service/ferry, plus the single-CC byte-identical case) and `test/repairJob.test.js`
(auto/manual assignment, the repair cap, and zone preference).
**Phase F β home-base override + repairing mobile units too. β
DONE.**
Closes the two items Phase E deliberately left for later.
- **A player-assigned home base (`unit.homeCC`, `engine/commands.js issueSetHomeBase`).** Zone-first
(Phase E) still GUESSED a unit's home zone from raw nearest-CC distance β usually right, but the
player had no way to override it. Right-clicking a completed own Command Center with eligible
units selected (worker, Mender, or a freighter β anything that ever consults a zone) now pins
`unit.homeCC` to it; `engine/gather.js zoneFirst` takes an optional `homeId` and, when it resolves
to a live owned CC, uses it INSTEAD of the distance guess β the player decides which base's
territory a unit's jobs stay loyal to, not just proximity (directly answering "the player would
be in charge of where sufficient resources are, per area"). It's passive: setting it never
interrupts whatever order the unit is already running, only what its NEXT idle job search
prefers. A stale override (its CC destroyed, or never valid) is ignored, falling straight back to
the distance guess β self-healing, nothing to clean up. Threaded through every zone-first call
site: `nearestBacklogProducer`/`assignService`/`assignFerry`/`updateFerry`'s replan (`haul.js`),
`pickRepairTarget` (`repair.js`, so both the worker repair job AND the Mender's own `autoRepairRoam`
honor it). HUD: a selected unit with a home base shows a "π Home base assigned" note and a Clear
button; a selected Command Center shows how many units call it home.
- **Refinement β an explicit assignment is a HARD boundary, not just a tie-break.** The first cut of
`zoneFirst` fell back to a global search whenever the (overridden or guessed) home zone came up
empty, no matter which way `home` was picked. That's right for the DEFAULT distance guess (a mere
heuristic β widen the search rather than sit idle) but wrong once the player has deliberately
assigned a base: a hauler pinned to Command Center A should keep cycling through WHATEVER other
jobs A's own zone has to offer, and simply wait when it has none β never reach into Command Center
B's territory on its own. `zoneFirst` now tracks whether `home` came from a valid override or the
distance guess, and only widens to the whole empire in the latter case; a pinned unit with an empty
zone gets `null` (no job, stays idle) until the player reassigns it to a different CC (right-click
it β `issueSetHomeBase` overwrites the old assignment) or clears it outright (the HUD's "Clear home
base" button, or setting `homeCC = null` β "assign to nothing"). This is also exactly why a
building-level manual assignment (`issueServiceBuilding`/`issueRepair`/`issueFerryFreighter`,
`order.manual`) was left untouched: it was ALREADY a hard, single-target lock (the worker loops on
that one building/freighter forever once done, never auto-reassigned) β the contrast is
intentional. A CC assignment is a *territory* (flexible among whatever's inside it); a building
assignment is a *target* (one specific thing, no substitutes). Tests: `test/zones.test.js` (a
pinned empty zone stays idle rather than poaching; reassigning to a different CC immediately
redraws the boundary; clearing the override restores the old lenient default).
- **A worker can now repair a wounded mobile UNIT, not just a building.** `pickRepairTarget` already
supported `includeUnits`; `assignRepair` now leaves it at the default (true) instead of forcing
buildings-only, so a worker's auto-assigned or manually-issued (`issueRepair`, renamed from
`issueRepairBuilding` β now takes either a building or unit id) repair job can chase and patch a
hurt Ranger/Bastion/etc. the same way it patches a turret β walking toward the target's LIVE
position each tick, the same idiom the Mender's own roam already uses for a moving target. The
order's `buildingId` field became a generic `targetId` (resolved via `state.js getEntity`, building
OR unit) to match. A worker still never repairs itself (`exclude: unit`). This doesn't replace the
Mender β a Mender still heals passively/for free in an AoE and can roam on its own β it just means
a damaged unit isn't stranded with nothing to do about it when no Mender is nearby.
- Tests: `test/zones.test.js` (a home-base override wins over both raw proximity and the usual
nearest-CC guess; a stale override falls back gracefully), `test/repairJob.test.js` (a wounded
unit is now a valid auto-assigned AND chased-and-healed target; a worker never targets itself),
and `test/commands.test.js` (`issueSetHomeBase`'s role gating and its passivity; `issueRepair`'s
gating). Same AI-safety/determinism shape as every phase above β no wall clock, no unseeded
randomness, skirmish AI untouched.
## Outcome
All six phases shipped (Phase C was later reverted β see above: the Refinery/Foundry/Arsenal
are no longer drop-offs, and all collection now runs straight to the Command Center). Storage
is finite end-to-end β production output (rig + factories) and factory inputs β and workers
move every good between them (gather β CC β supply β factory β haul β CC). A landed
freighter now sits IN that chain too β a physical collection point workers can ferry to
directly, or (once teched) a large-capacity autonomous hauler in its own right, paid for
in AI Cores while it runs. Logistics is a standing demand on labour (and, for an automated
fleet, on AI Cores) to the end of the game, with the Command Center as the one bottomless
warehouse. On a multi-base empire, every job (haul, service, ferry, repair) is discovered
zone-first β and the player can pin a unit's home base outright, overriding the distance
guess β so labour stays loyal to its own base instead of routinely commuting across the map.
A worker can now actively repair a damaged building OR a wounded mobile unit, not just a
passing Mender. Energy stays a placement decision (grid efficiency), no workers. Everything
player-only (or, for zone affinity and repair, ALSO extended to the Odyssey AI's own logistics
workers, by construction never the skirmish AI) and deterministic; the AI and skirmish replays
are byte-identical.
### 2.5 Recommended order & risk
`Phase A` (low risk, player-only, self-contained, immediately satisfying) β
`Phase B` (medium, builds the supply network) β `Phase C` (higher, touches the AI &
market, needs the most careful determinism pass). Ship and play-test each before the
next; do not attempt all three in one pass.
|