# Living island — plan and agent rules Goal: Everon should feel occupied — patrols that actually move, manned roadblocks, soldiers doing something other than standing — **without** raising the active AI count. The target is a constant, bounded agent budget (default 60 enemy agents) regardless of how much "activity" exists on the map. ## The core idea: simulate many, spawn few Most of the island's forces exist as **records, not entities**. A record has a position, a route, a strength and a duty. A slow tick advances records along their routes with plain arithmetic — no AI, no navmesh, no entity. A record is materialised into a real `SCR_AIGroup` only when a player is close enough to observe it, or when HQ needs it in contact. When the player leaves, it goes back to being a record, keeping its position and losses. This is what buys the "living island" feeling cheaply: twelve patrols can be crossing the map at once while only the two near players cost anything. ``` ERR_ForceLedger records: id, kind, position, route, strength, duty, importance | |-- ERR_ForceSimulation advances dormant records along routes (2 s tick, pure math) |-- ERR_PresenceBudget decides which records are physical right now (3 s tick) | |-- spawns -> SCR_AIGroup.SpawnMembers() | |-- dormant -> SCR_AIGroup.DespawnMembers() | +-- consumed by ERR_HQ (ai addon) for tasking, ETA and availability ``` **Hard invariant — fairness:** only *physical* units observe and report contacts. A dormant record never generates a sighting. This protects the HQ design, where enemy knowledge must be earned through line of sight and radio. ## Layer 2: presence without population A site feels occupied because of evidence, not head count. Evidence is free — static props cost no CPU. So roadblocks, barriers, parked vehicles and lit windows are **permanent**; the soldiers manning them are **conditional**. ## Layer 3: soldiers doing something Vanilla smart actions (`SCR_AISmartActionComponent`) let an existing soldier lean on a wall, smoke, or work on a vehicle instead of standing at a waypoint. This costs no extra agents — it changes what the agents you already pay for are doing. ## Task order Do these in order; each builds on the previous one. One agent per task. | Task | File | Depends on | | --- | --- | --- | | T1 | `T1-force-ledger.md` — records + route simulation | — | | T2 | `T2-presence-budget.md` — spawn/dormant arbitration under a cap | T1 | | T3 | `T3-checkpoints.md` — roadblocks and manned checkpoints | T1, T2 | | T4 | `T4-smart-actions.md` — idle activity at sites | T2 | | T5 | `T5-hq-integration.md` — HQ tasks ledger units, gets ETAs | T1, T2 | | T6 | `T6-ambient-signals.md` — cheap signs of life | T2 | | T7 | `T7-performance.md` — caps, metrics, thrash prevention | all | | T8 | `T8-hq-memory.md` — HQ belief: decaying contacts, searched ground | T5 | | T9 | `T9-pursuit.md` — alert states, hunting, air search, cordons | T5, T8 | | T10 | `T10-ai-migration.md` — port the old AI mod's proven behaviour under HQ | T5, T8, T9 | | O1–O6 | `OCCUPATION.md` — how the island gets occupied after the alarm | T1, T2 | T3 and T4 can run in parallel once T2 lands. T6 can run in parallel with T5. T8 and T9 are what make the island *hunt* rather than merely be occupied; T9 depends on T8, because HQ cannot search for what it does not remember. --- # Rules every agent must follow Paste this section into each agent along with its task file. ## Licensing — non-negotiable This project is an independent successor. The original Escapists mod and its `ScrtCore` / `ScrtSharedContent` dependencies are under SCRT-ND (an APL-ND variant) which **forbids derivatives**. Therefore: - Never copy code, config, layouts or assets from the original Escapists mod, from ScrtCore/ScrtSharedContent, or from `/home/jakub/devel/escapists`. - Never introduce identifiers matching `ESCT*`, `SCRTC_*`, `SCRT_*`, `EAI_*`. `tools/check.py` fails the build on these. - Only vanilla Arma Reforger resources and resources owned by this repository may be referenced. Every `{GUID}path` must either be in `docs/vanilla-resources.json` or be owned by a `.meta` file in this repo. - Reimplementing a *mechanic* from scratch is fine. Copying an implementation is not. ## Project constraints - Five addons only: `core`, `ai`, `intel`, `chat`, `map`. Do not add addons or external dependencies. - Dependency direction: `ai`, `intel`, `map` depend on `core`; `chat` depends on all. **`core` must never reference `ERR_HQ` or anything from another addon.** Core defines interfaces (see `ERR_CommandService`); companions implement them. - The living-island ledger belongs in **`core`**, because the base scenario must work without the AI addon. HQ integration (T5) goes in **`ai`**. ## Performance rules — the whole point of this work 1. **No per-frame work.** Every tick in this subsystem is ≥ 1 s. Stagger work by unit id (`id % N`) so all units never update on the same frame. 2. **No allocation inside loops.** Reuse member arrays; clear rather than reallocate. Do not allocate an `array` per unit per tick. 3. **Spatial bucketing for proximity.** 500 m cells, as already done in `ERR_HQNetwork.Add/Build`. Never do an all-pairs distance scan. 4. **Hard caps everywhere,** with a logged refusal when hit. Never let a count grow unbounded. 5. **Prefer vanilla behaviour over custom steering.** Issue a waypoint and let the engine drive; do not write per-frame movement code. 6. **Hysteresis and dwell time.** Spawn and despawn radii must differ, and a unit must stay in its state a minimum time before flipping. Thrash is the main performance risk in this design. ## Code style - Match the surrounding file's indentation (older files use tabs). - New lines stay under 120 characters. The repo already has 420 lines over 200 characters; do not add more. - Put decision logic in **pure static rule classes** (see `ERR_DiscoveryMapRules`, `ERR_MissionRules`) so it can be tested without the engine. Entity manipulation stays in the service class. - Small classes. Do not extend `ERR_GameMode` or add another 100-field object. ## Validation — required before you report done 1. `python3 tools/check.py` must pass. 2. Add Python tests using `tests/policy_harness.py`: `body(path, signature)` extracts a real function body from the `.c` file and `run(code)` compiles and executes it as C++. Test the pure rule classes this way. Follow the pattern in `tests/test_admin_session.py`. 3. Report honestly. This repo separates "compiles and passes scripted fixtures" from "verified in the real game". If you did not run it in Workbench, say so. Do not write evidence files claiming runtime verification you did not perform. ## Verified engine APIs you will need All confirmed present in the installed vanilla scripts (1.6.30): | API | Where | Notes | | --- | --- | --- | | `SCR_AIGroup.SetLifecyclePolicy(policy, spawnDist, despawnDist, veryNearBlockDist)` | `Entities/SCR_AIGroup.c:2921` | `Manual` (default) or `ProximityDriven` | | `SCR_AIGroup.SpawnMembers()` / `DespawnMembers()` | `:2783` / `:2870` | Dormant group **keeps its entity** and records alive/dead counts, so a later `SpawnMembers()` re-materialises the right number | | `ChimeraAIGroup.SetImportance(int)` / `AIGroup.GetImportance()` | `generated/AI/ChimeraAIGroup.c:22` | Pass `SCR_EAISpawnImportance` values | | `SCR_EAISpawnImportance` | `AI/SCR_EAISpawnImportance.c` | LOW 0.50, NORMAL 0.70, HIGH 0.90, CRITICAL 1.00 — fraction of AIWorld's active-AI limit the tier may consume, enforced by `AIWorld::CanActivateGroup` | | `SCR_EAIGroupLifecyclePolicy` | `AI/SCR_EAIGroupLifecyclePolicy.c` | `Manual` = we drive it; `ProximityDriven` = group self-manages against `ObserversSystem` | | `ObserversSystem.GetObservers(out array)` | `generated/System/ObserversSystem.c:59` | Cheap observer positions; also `GetObserversMP` | | `SCR_AIGroup.SetEliminateWhenReached(true)` + `GetOnEliminatedWhenReached()` | used at `SCR_AmbientPatrolSpawnPointComponent.c:212` | If a player reaches a patrol that could not spawn (budget full), the area counts as cleared and the group retires instead of popping in later | | `SCR_AISmartActionComponent` (`ReserveAction`, `ReleaseAction`, `GetOnActionEnd`, `GetOnActionFailed`) | `AI/Components/SCR_AISmartActionComponent.c` | Idle activities | | `SCR_SmartActionWaypoint.SetSmartActionEntity(IEntity, string tag)` | `AI/Behavior/SCR_SmartActionWaypoint.c:31` | Sends a group to perform a tagged smart action | Reference implementation worth reading (vanilla, not the original mod — safe to read and learn from): `Components/Locations/SCR_AmbientPatrolSpawnPointComponent.c` and `Systems/SCR_AmbientPatrolSystem.c`. ## Existing repo helpers — use these, do not reinvent - `ERR_Actors.Spawn/DeleteSpawned/Ground/Players/Alive/CanFight/Group` — `addons/core/Scripts/Game/ERR/ERR_Actors.c` - `ERR_Resources.SearchWaypoint/MoveWaypoint/CycleWaypoint/DefendWaypoint` — waypoint prefab GUIDs - `ERR_SiteService` / `ERR_SiteDefinition` / `ERR_LocationComponent` — authored sites and their guards - `ERR_HQNetwork` — the 500 m spatial cell pattern to copy - `ERR_Settings.Get("key")` — integer settings; add new keys here - `ERR_HQJournal` via `ERR_HQ.Event(kind, message)` — **ai addon only**; core code logs with `PrintFormat("Reintroduced [ledger]: ...")`