# T10 — Migrating the old AI mod's proven behaviour under HQ control Read `docs/living-island/README.md` first, including the rules section. Depends on T1, T2, T5, T8, T9. Mostly **ai** addon. The predecessor project at `/home/jakub/devel/escapists/escapists-ai` already implements much of what the living-island plan describes — hunting UAZ convoys, a timed reconnaissance helicopter, reactive roadblocks, guard check-ins, body discovery, mortar sites and supply convoys. That work is the maintainer's own design and is the reference for this task. This brief says what to carry across, what the real parameters were, and — most importantly — how the architecture must change on the way. --- ## 1. Provenance and licensing — read before opening any source file | Category | Files | What you may do | | --- | --- | --- | | Maintainer's own design | Most of `escapists-ai` | Carry the **design**: timings, distances, sequences, failure handling. Re-express the code against vanilla and ERR interfaces. | | Calls into protected API | 36 of 48 files reference `ESCT_*` | Those calls cannot come across — ERR has no Escapists dependency. Replace each with a vanilla or ERR equivalent. A call is not a copy, but the replacement must be written fresh. | | **Contains copied protected code** | `EAI_GroupLookup.c` | **Do not port, do not adapt, do not read as a model.** An independent comparison found 666 matching token sequences with the protected `ESCT_GroupLookup` / `ESCT_CharacterLookup`. Build group composition from vanilla entity catalogs from scratch, as `ERR_FactionCatalog` already does. | Also binding: - `tools/check.py:39` rejects any `EAI_*`, `ESCT*`, `SCRTC_*`, `SCRT_*` identifier. Everything is renamed to `ERR_*`, and no old prefix survives in a comment either. - Every `{GUID}path` must be in `docs/vanilla-resources.json` or owned here. The prefabs quoted below are vanilla and were verified against the installed `resourceDatabase.rdb`; add any that are missing from the manifest. - Do not copy the old files wholesale and rename the symbols. Each system below is re-implemented against the ledger, the belief layer and the presence budget. --- ## 2. The architectural change — this is the point of the task In the old mod, each behaviour was an **independent subsystem with its own timer and its own spawning**: `EAI_AirReconSystem` ran a launch clock, the response system held a five-minute radio cooldown, the roadblock planner ran its own placement loop, mortars managed their own crews. They competed for the same world with no shared budget and no shared picture. In this project every one of them becomes a **capability the HQ planner selects**, under one force pool, one agent budget and one belief: ``` ERR_HQ ── belief (T8) ── what HQ thinks it knows │ ├── plans against objectives and beliefs (T5 scoring, T9 alert states) │ └── chooses a capability and spends the pool: ground response · air search · roadblock · mortar fire · supply convoy · garrison deployment (O1–O6) ``` Concretely, for every system below: - **No private timers.** A capability offers "available / not available, ETA, cost"; the planner decides when. The old per-system cooldowns become availability rules the planner reads. - **No private spawning.** Units come from the ledger and the pool. A capability requests a record; `ERR_PresenceBudget` decides what is physical. - **No private world picture.** Capabilities act on beliefs (T8). Nothing queries player positions directly. - **Losses hit the shared pool.** A destroyed hunter UAZ is a section gone from the same pool that garrisons checkpoints. Delete rather than port: `EAI_Performance.NearPlayer` / `ConvoyLOD`. That was an embryonic presence system; T2 replaces it, and two competing presence systems is worse than either alone. --- ## M1 — Detection: check-ins, losses and bodies **Source:** `EAI_HQWatch.c`, `EAI_HQBodySearch.c`, `EAI_HQStatus.c` This is the missing trigger for the occupation alarm, and it already exists. - **Periodic check-ins.** Each unit owes HQ a status report every `CHECK_IN_MS` (180 s), with `REPLY_MS` (30 s) to answer. A unit that misses its check-in is not assumed dead — HQ opens an *investigation* and sends someone to look. That is the mechanism `OCCUPATION.md` referred to as "a guard check-in missed". - **Loss confirmation.** `OnControllableDestroyed` records a loss as unconfirmed; HQ only *knows* once a unit reports it. `SquadLimit(baseline, confirmedLosses)` shrinks what HQ can commit as its confirmed losses mount. - **Body discovery.** Bodies are found by units that can actually see them — `InView(distance, facing)` plus a visibility trace, scanned at most every 15 s per unit, capped at 16 tracked bodies. A found body queues radio evidence: *casualty evidence, cause unknown*. Quiet kills stay quiet until someone walks past the corpse, which is exactly the stealth counterplay worth keeping. **Port as:** an ingest path into T8's belief and an alarm source for the occupation plan. A discovered body creates a low-confidence belief at that position; a missed check-in creates an investigation belief at the unit's last known location. Keep the trace budget (`m_Traces`) — it is what stops body scanning from costing a fortune. --- ## M2 — Ground response: the hunter convoy **Source:** `EAI_AIResponseSystem.c` The proven behaviour, with its real numbers: - Two armed UAZs — `{0B4DEA8078B78A9B}Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et` — spawned on roads `EAI_RESPONSE_DISTANCE` (1000 m, ±100 m tolerance) from the contact, on **opposite sides**: candidate bearings must be at least 30° from directly opposite (`dot ≤ -0.866`), up to 12 placement attempts. - Each vehicle is filled with spec-ops and exactly one medic, all seats occupied. - **One convoy is kept alive and re-tasked** when a later report gives a new contact, rather than spawning a fresh one per sighting. It is replaced only when a vehicle is lost. - A global radio cooldown of 300 s gates how often a response can be triggered. - Convoy gunners were given maximum AI skill. Keep a *deliberate* skill choice, but make it a setting rather than a constant — see the pacing rules in T9. **Port as:** the ground hunting capability in T9. The 1000 m opposite-approach placement is exactly T9's "at most two bearings, escape vector left open" rule — implement it once, here, and have T9 use it. Respect T9's `minHunterSpawnMetres` (700 m): never place a response vehicle closer than that to any player, which the old `EAI_ResponseSafety` and `EAI_SettlementSafety` checks were already approximating. --- ## M3 — Air search: the reconnaissance helicopter **Source:** `EAI_AirRecon.c` (1335 lines — the richest system in the old mod) This *is* T9's hunter helicopter. Build one system, not two. Behaviour worth carrying: - **Launch timer.** `heliTimerMinutes` (default 15, range 1–1440) schedules the first departure and each subsequent one. Losing the aircraft schedules a replacement rather than ending air search permanently. - **Airport discovery, spread over time.** The island is scanned in 2000 m columns across successive updates — never in one frame — matching runway and control-tower prefabs, clustering structures within 1000 m into one airport and tracking the cluster centre. A named marker entity short-circuits the search. **Keep the incremental scan pattern**; it is a good model for any map-wide query in this project. - **Landing pad selection.** 12 attempts at 35–220 m from the centre, rejecting water, requiring flatness within 1.5 m across a 12 m cross, a 14 m radius cylinder clear of low obstacles, and a clear box up to 150 m overhead. - **Phased flight** with a landing retry: if a landing has not settled within 180 s, search for a new pad (re-checked every 30 s within 150 m of home). - **Air observation feeds HQ** through the same report path as ground units. **Change on the way across:** - Fold into T9's air search: one airborne at a time, endurance-capped, observing under the same line-of-sight rules as infantry. - The launch timer becomes *availability*, not a trigger. The planner asks for air search when a belief justifies it; the timer only says whether an airframe is ready. A helicopter must not launch into an empty sky because a clock fired. - Draw the airframe from the shared pool, so searching competes with lifting. - Replace `ESCT_Misc.IsPositionInWater` and `ESCT_RoadNavigation` calls with vanilla equivalents (`SCR_TerrainHelper`, `SCR_WorldTools`, vanilla road API). --- ## M4 — Reactive roadblocks **Source:** `EAI_HQRoadblocks.c` Distinct from T3's authored checkpoints: these are placed *in response to a contact*, ahead of where HQ thinks the player is going. - `Ahead(position, velocity, site)` rejects any site that is not in front of the contact's travel. `FreshDirection(distress, hasVelocity, age, speed)` decides whether a heading estimate is trustworthy enough to predict from at all. - Sites are snapped to real roads via a road-approach search, within 200 m of the chosen point, with a 25 m probe to orient the block along the road. - Placement requires ground clearance, distance from players, and being outside settlements. **Port as:** the cordon mechanism in T9. T8's belief gives a better input than the old raw velocity: use the uncertainty circle and the objective-route inference to choose chokepoints between the belief and its likely destination. Keep `Ahead` and `FreshDirection` as pure rules — they are good, testable logic. --- ## M5 — Mortar sites **Source:** `EAI_MortarSystem.c`, `EAI_MortarEngagement.c`, `EAI_MortarFiring.c`, `EAI_MortarPrediction.c`, `EAI_MortarSalvo.c` Much of this is already migrated (`ERR_Mortar*`). What is missing is **site placement as an HQ capability**: the old system chose where a battery stands, spawned a 2B14 with crew and an RTO, equipped nine HE shells per crewman, pulled shells from nearby donors when the gunner ran dry, reserved airspace over the impact area, and audited site status (claimed / startup / operational / crew fighting / shells / radio connected / cooldown). **Port as:** a `MORTAR_SITE` objective in the occupation plan (O1). HQ deploys a battery to a site like any other garrison — it costs a section from the pool, travels by the normal methods, and takes time to become operational. A mortar battery that is spent, overrun or out of shells stays spent. Keep the airspace reservation and the existing fairness: a battery fires at *beliefs*, delivered by radio, never at live player positions. --- ## M6 — Logistics and the threat extras Lower priority; schedule after M1–M5. | Source | What it gives | Port as | | --- | --- | --- | | `EAI_SupplyConvoys.c` (1463 lines) | Road convoy logistics with real routing | The convoy method in O4; reuse the road-following and slot logic, not the ESCT calls | | `EAI_Resupply.c`, `EAI_AmmoSupply.c`, `EAI_DepotLoading.c`, `EAI_LogisticsVehicle.c` | Ammunition and depot chains | A resupply capability that keeps garrisons and batteries fighting — finite, so cutting a depot matters | | `EAI_StingerEquipment.c` | A rare `Launcher_FIM92_Stinger` on enemy infantry, one in N | Anti-air threat against the player's stolen helicopter — the escape must not be trivially safe. Keep it rare and keep it a setting | | `EAI_ImprovisedPrisons.c` | Recapture rather than only killing | A strong escape-fiction feature: an unconscious escapist can be taken, not just killed. Worth its own task later | | `EAI_Mines.c`, `EAI_MineTask.c` | Mines laid on routes | Optional; only with clear warning cues, or it is pure frustration | | `EAI_AirAssault.c`, `EAI_AssaultParachute.c` | Airborne insertion | Already covered by O3 air lift; do not build a second insertion path | | `EAI_HelicopterRecovery.c`, `EAI_Landing.c`, `EAI_AircraftAmmo.c` | Aircraft servicing and recovery | Feed into the transport provider rather than a separate system | --- ## Acceptance criteria 1. No `EAI_*`, `ESCT_*` or `SCRTC_*` identifier appears anywhere in `addons/`; `python3 tools/check.py` passes. 2. `EAI_GroupLookup.c` has not been ported in any form; group composition comes from vanilla catalogs through `ERR_FactionCatalog`. 3. Every migrated capability draws units from the ledger and the shared pool. Grep proves it: no capability calls a spawn helper directly. 4. No migrated system owns a private timer that triggers behaviour. Timers only report availability to the planner. 5. A missed check-in produces an investigation; a discovered body produces a low-confidence belief; both are visible in the journal. 6. A contact triggers **one** re-taskable hunter convoy, not one per sighting, and never places a vehicle within 700 m of a player. 7. Air search launches because a belief justifies it, never because a clock fired into an empty sky. 8. Destroying hunters, the helicopter or a mortar battery permanently reduces what HQ can do next — verified in the metrics. 9. Active agents stay inside `activeAgentBudget` with every capability in play. ## Tests to write `tests/test_detection.py` — check-in timing and reply windows; `SquadLimit` shrinks with confirmed losses; `InView` rejects a body behind the observer; the body-scan interval and tracking cap hold. `tests/test_ground_response.py` — opposite-bearing placement satisfies the 30° rule and the 1000 m ± 100 m band; the 700 m minimum to players holds; a second contact re-tasks the existing convoy instead of spawning another; cooldown gates repeats. `tests/test_air_search.py` — the launch timer reports availability but never triggers a launch on its own; pad selection rejects water, slopes over 1.5 m and obstructed overhead; landing retry fires after 180 s. `tests/test_roadblock_siting.py` — `Ahead` rejects sites behind the contact; `FreshDirection` refuses a stale or speedless heading; chosen sites lie between the belief and its inferred objective. ## Do not - Do not port a file by renaming its symbols. Re-implement against the ledger, the belief and the budget. - Do not recreate the old parallel-subsystem architecture. One planner, one pool, one budget, one picture. - Do not port `EAI_GroupLookup.c`, and do not use it as a reference while writing the replacement. - Do not let any migrated capability read player positions directly. Everything acts on beliefs earned through observation and radio.