Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
97 lines
4.3 KiB
Markdown
97 lines
4.3 KiB
Markdown
# T5 — HQ commands the ledger
|
||
|
||
Read `docs/living-island/README.md` first, including the rules section. Depends on T1, T2.
|
||
This task is in the **ai** addon. Core must not learn about HQ.
|
||
|
||
## Goal
|
||
|
||
Today `ERR_HQ.Assign()` can only task groups that physically exist near a
|
||
contact. After this task, HQ tasks **ledger records** — including dormant ones
|
||
far away — so a response can come from across the map, travel virtually, and
|
||
materialise when it arrives. This is what makes the island feel coordinated
|
||
rather than reactive, and it costs nothing while the responder is off-screen.
|
||
|
||
## What to build
|
||
|
||
1. **A core-side interface, implemented where the ledger lives.** Add to
|
||
`ERR_ForceLedger` (core):
|
||
|
||
```
|
||
int Available(vector near, float radius, out array<int> ids);
|
||
float EstimateSeconds(int recordId, vector destination); // distance / Speed()
|
||
bool Order(int recordId, ERR_ForceDuty duty, vector target);
|
||
bool Pin(int recordId, bool pinned);
|
||
ERR_ForceKind KindOf(int recordId);
|
||
int StrengthOf(int recordId);
|
||
```
|
||
|
||
These are generic force-management calls, not HQ concepts, so core owning
|
||
them does not violate the dependency direction.
|
||
|
||
2. **HQ uses them.** In `ERR_HQ.Assign()`, replace first-fit-within-radius with a
|
||
scored choice over ledger candidates:
|
||
|
||
```
|
||
score = ContactPriority(report) / max(EstimateSeconds(id, target), 1)
|
||
- CostOfLeaving(KindOf(id))
|
||
```
|
||
|
||
`CostOfLeaving` is high for a `CHECKPOINT` or a garrison at an objective and
|
||
low for a roving `PATROL`. Put this in a pure `ERR_HQTaskingRules` class so it
|
||
is testable.
|
||
|
||
3. **Ordering a dormant record.** A dormant record receives the order through the
|
||
ledger and changes duty to `MOVE_TO`/`SEARCH`; it travels virtually and
|
||
materialises through T2's normal proximity rules when it nears players.
|
||
HQ pins the record once it is within 1 km of the contact so it cannot go
|
||
dormant mid-engagement.
|
||
|
||
4. **Radio realism is preserved.** The existing `ERR_HQNetwork` rules apply
|
||
unchanged to **physical** units — a carrier that dies mid-transmission still
|
||
kills the message. For a dormant record, evaluate the same relay geometry
|
||
against ledger positions and the record's `radioCapability`, with the same
|
||
hop limits and latency, but without entities. Log both paths to the journal
|
||
so the two are distinguishable.
|
||
|
||
5. **Fairness invariant, unchanged:** dormant records never observe and never
|
||
generate contacts. They only receive orders and move. Enforce it in code, not
|
||
by convention — `Observe()` must refuse a record without a physical group.
|
||
|
||
## Acceptance criteria
|
||
|
||
1. A contact reported near the radio station can be answered by a patrol 3 km
|
||
away, which travels while dormant and appears as real AI on arrival.
|
||
2. Checkpoint garrisons are not stripped to chase distant contacts — verify that
|
||
`CostOfLeaving` keeps them in place unless nothing else is available.
|
||
3. HQ's journal shows the ETA it used and the record it chose, so the decision is
|
||
auditable after the fact.
|
||
4. Killing the radio operator of a physical reporting unit still severs the
|
||
report, exactly as today.
|
||
5. `python3 tools/check.py` passes.
|
||
|
||
## Also fix while you are here
|
||
|
||
`ERR_HQUnit.Eligible()` allocates a fresh `array<AIAgent>` on every call, and
|
||
`Assign()` calls it inside a contacts × units nested loop every 5 s. Cache the
|
||
agent array on the unit and refresh it on the network tick instead. Likewise,
|
||
replace the six linear scans over `m_Units` keyed by group (`ReserveGroup`,
|
||
`LogisticsToken`, `ReserveLogistics`, `MortarConnected`, `Downlink`,
|
||
`QueueSupplyStatus`) with a `map<SCR_AIGroup, ERR_HQUnit>` lookup.
|
||
|
||
## Tests to write
|
||
|
||
`tests/test_hq_tasking.py`:
|
||
|
||
- `EstimateSeconds` scales with distance and with kind speed.
|
||
- The scored choice prefers a closer patrol over a distant one at equal priority,
|
||
and prefers a distant patrol over a checkpoint garrison at equal distance.
|
||
- With only checkpoint garrisons available and a high-priority contact, one is
|
||
eventually released — the cost is a weight, not a veto.
|
||
- A dormant record refuses to produce an observation.
|
||
|
||
## Do not
|
||
|
||
- Do not let HQ spawn units directly. It asks the ledger; the budget decides.
|
||
- Do not add `ERR_HQ` references to core files.
|
||
- Do not remove the existing radio latency or failure handling to simplify this.
|