Files
EscapistsReintroduced/docs/living-island/T5-hq-integration.md
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
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.
2026-09-20 16:42:52 +02:00

97 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.