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.
19 KiB
Island occupation — agent brief
Hand this file to a coding agent as-is. It is self-contained: concept, rules, verified engine APIs, work breakdown, acceptance criteria and tests.
Companion files (same directory) hold the supporting infrastructure this design
sits on: README.md (plan and rules), T1-force-ledger.md,
T2-presence-budget.md, T3-checkpoints.md, T4-smart-actions.md,
T5-hq-integration.md, T6-ambient-signals.md, T7-performance.md.
If the ledger (T1) and presence budget (T2) are not yet implemented, build those
first — this design assumes them.
1. The concept
The island does not start occupied. At mission start only the prison and its immediate surroundings hold troops. Everything else — the radio station, the helipad, the junctions, the depots — is empty ground with buildings on it.
Then the alarm goes off, and the enemy moves in while the player watches. Helicopters lift sections out to guard the objectives. Trucks drive to junctions and the soldiers get out and build roadblocks over several minutes. Patrols fan out along the roads. Over the next twenty to forty minutes the island closes around the player.
This gives three things at once:
- Atmosphere. Watching a transport helicopter flare and unload eight soldiers onto the hill you were about to cross is worth more than finding those eight soldiers already standing there.
- CPU. Almost nothing exists at T+0. Deployments travel as ledger records and only become real AI near players. Start cost is near zero, and the steady-state cost is the same agent budget as before.
- Counterplay and pressure. Every deployment is interruptible, every loss is permanent, and an island left alone gets steadily harder to cross. Hiding is punished; moving fast is rewarded.
The player must be able to change the outcome. Shoot down the transport and those eight soldiers never arrive — that junction stays open for the rest of the round. That is the whole point of the design; protect it in every decision.
2. Non-negotiable rules
Licensing
This project is an independent successor. The original Escapists mod and its
ScrtCore / ScrtSharedContent dependencies are SCRT-ND licensed (an APL-ND
variant) which forbids derivative works.
- Never copy code, configs, layouts, compositions 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:39fails the build on these. - Only vanilla Arma Reforger resources and resources owned by this repo may be referenced. Reimplementing a mechanic from scratch is fine; copying an implementation is not.
Addon boundaries
Five addons only: core, ai, intel, chat, map. ai/intel/map
depend on core; chat depends on all. core must never reference ERR_HQ,
ERR_AircraftService, or anything else from another addon.
Occupation logic lives in core, so the base scenario occupies the island
without the AI addon installed. Where it needs a companion capability it defines
an interface in core and lets ai register an implementation — the existing
ERR_CommandService and ERR_AdminProvider show the pattern. Without the AI
addon, occupation still happens by road and on foot; only air lift is missing.
Performance
- No per-frame work. Every tick here is ≥ 1 s, staggered by id (
id % N). - No allocation inside loops. Reuse member arrays.
- Spatial bucketing (500 m cells) for proximity — copy
ERR_HQNetwork.Add/Build. - Hard caps everywhere, with a logged refusal when hit.
- Issue waypoints and let the engine drive. No custom per-frame steering.
- Hysteresis and dwell before any spawn/dormant flip.
Style and validation
- Match surrounding indentation (older files use tabs). New lines under 120 chars.
- Decision logic goes in pure static rule classes (see
ERR_MissionRules,ERR_DiscoveryMapRules) so it is testable without the engine. Entity work stays in the service. python3 tools/check.pymust pass before you report done.- Add tests with
tests/policy_harness.py:body(path, signature)extracts a real function body from the.cfile andrun(code)compiles and runs it as C++. Followtests/test_admin_session.py. - Report honestly. Separate "compiles and passes scripted fixtures" from "verified in the real game". Do not write evidence claiming runtime verification you did not perform.
3. The occupation timeline
T+0 — Dormant island
| Location | State at start |
|---|---|
| Prison | Full garrison, physical (players are there) |
| Within 2 km of prison | 2 patrol records, dormant |
| Staging areas (1–2 authored) | The entire remaining force pool, as records |
| Everything else | Buildings and props only. No troops. |
Authored staging areas are the military sites the occupation flows out of — pick the existing barracks/airfield areas on Everon. They hold the pool and are where transport originates. A staging area is itself a site that can be attacked.
T+alarm — Detection
The occupation plan starts when HQ learns something is wrong, through the
existing systems, not a timer: an escape is witnessed, a body or breach is found,
or a guard check-in is missed. If nothing is detected, occupation still begins
on a long fallback (default 15 min, setting occupationFallbackSeconds) — a
missing prisoner count is noticed eventually.
T+alarm onward — Deployment
HQ walks a priority list and issues deployment orders while the pool lasts:
| Priority | Objective | Why |
|---|---|---|
| 1 | Radio station(s) | Denies the player's first objective |
| 2 | Helipad / extraction site | Denies the escape |
| 3 | Road junctions and bridges on the ring road | Roadblocks, cordon |
| 4 | Medical and depot sites | Denies resupply |
| 5 | Overwatch positions near likely routes | Observation, HQ eyes |
Cadence: one order every 90–180 s early, slowing as the pool depletes
(occupationOrderSeconds). At most 5 deployments in transit at once.
T+20 to T+40 — Entrenchment
Arrived sections build. Roadblocks go up over 6–10 minutes each. Checkpoints become manned (T3). Patrol routes establish between occupied sites. The island that was empty is now a network.
Continuous — Adaptation
As HQ gains contacts (T5), it re-prioritises: the sector where players were seen gets the next deployments and a cordon on its exits; quiet sectors are thinned, not reinforced. With no contacts, occupation proceeds methodically down the priority list — so hiding tightens the noose.
4. Data model
Add to core, next to the ledger:
class ERR_ForcePool // finite, permanent losses
{
int Sections; // remaining uncommitted sections
int SectionStrength; // soldiers per section, default 4
int TransportAircraft; // remaining flyable transports
int TransportVehicles; // remaining trucks
}
enum ERR_OccupationPriority { RADIO, EXTRACTION, JUNCTION, SUPPLY, OVERWATCH }
class ERR_OccupationObjective
{
string SiteId; ERR_OccupationPriority Priority;
int RequiredSections; int AssignedSections;
bool Held; bool Contested; bool Abandoned;
}
enum ERR_DeployMethod { FOOT, CONVOY, AIRLIFT }
enum ERR_DeployState { QUEUED, LOADING, IN_TRANSIT, UNLOADING, ARRIVED, LOST }
class ERR_DeploymentOrder
{
int Id; string ObjectiveId; ERR_DeployMethod Method; ERR_DeployState State;
int LedgerRecordId; // the travelling force (T1 record)
int TransportRecordId; // the aircraft/vehicle, -1 for foot
vector Origin; vector LandingZone; float StartedAt; float EtaSeconds;
}
ERR_OccupationPlan owns the pool, the objective list and the live orders, and
ticks every 5 s (staggered). It is created and ticked by ERR_GameMode on the
existing tick path — do not add a timer.
5. Deployment methods
Method selection is a pure rule (ERR_DeployRules.Select): urgency, distance,
and what transport remains. Roughly — under 1.5 km on foot, 1.5–6 km by convoy,
over 6 km or urgent by air, falling back down the list when transport is gone or
the AI addon is absent.
Foot
The record's duty becomes MOVE_TO; it walks its route virtually and
materialises near players like anything else. No transport consumed.
Convoy
Consumes a truck. A CONVOY record carries the section along an authored road
route. Slow (8–15 min) and ambushable — when physical, it is a real truck with
real soldiers in it. If destroyed, the section and the truck are permanently
lost from the pool.
Air lift
Consumes an aircraft. Three rules make this affordable — read them before implementing, because the naive version blows the whole agent budget at T+0.
Cargo is virtual in transit. Passengers inside a fuselage are invisible. Never spawn them for the flight. A physical lift is 1 vehicle + 1 pilot; the section spawns at the moment of unload. This takes a lift from ~10 agents to ~2 with no visual difference, and it is the single most important optimisation in this document.
Two aircraft in the air at once, not ten. Queue as many lifts as the plan
needs — the island-wide occupation is real — but execute at most
occupationConcurrentLifts (default 2) physically. The rest fly as records with
real routes and real timings, and materialise if a player comes near. The player
is only ever in one place; ten simultaneous flights pay full price for drama
nobody witnesses. The existing ERR_AircraftService.MAX_AIRCRAFT is 4, and the
controller's demonstrated envelope is vertical flight plus a bounded 20 m route —
ten concurrent cross-map flights with landings is far outside what is proven.
Treat the concurrency as a dial: start at 2, measure with the T7 metrics, raise
it only if the numbers allow.
Aircraft get a far larger materialisation band than infantry. A helicopter is
visible and audible at 2–3 km, so lifts materialise at presenceAirMetres
(default 2500 m) instead of the 600 m used for foot patrols. This is what
actually delivers "helicopters moving around the island" — they are seen
crossing the valley, not only overhead.
A fleet of ten airframes in the pool is fine; ten in the air simultaneously is not. Note also that ten AI helicopters converging on one staging area is where collisions, failed landings and wrecks blocking your own LZs come from.
Core cannot call the aircraft service directly, so:
// core
class ERR_TransportProvider // interface
{
bool CanLift(vector from, vector to);
int Lift(int recordId, vector from, vector landingZone, float deadline);
int LiftState(int orderId); // queued / flying / landed / unloaded / lost
void CancelLift(int orderId);
}
core calls the registered provider; ai registers an implementation backed by
the existing ERR_AircraftService (addons/ai/Scripts/Game/ERR/ERR_AircraftService.c),
which already supports route flight, field flight, vertical flight, cancellation
and a MAX_AIRCRAFT cap of 4. Reuse it — do not write a second flight
controller. {859B2A6A7DFA9EFB} style GUIDs must come from
docs/vanilla-resources.json; the transport helicopter
Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et is already
registered there.
Lift sequence — each stage is a state, none of it is per-frame:
LOADING— section boards at the staging area (virtual unless observed).IN_TRANSIT— aircraft flies the route. Physical when near observers; this is the moment the design exists for, so let it be seen and heard.UNLOADING— lands at the LZ, section disembarks, order is markedARRIVED.- Aircraft returns to staging and is returned to the pool.
LZ selection: flat ground within 150 m of the objective, not inside a building footprint, not on the road. Choose from authored LZ points per site rather than searching terrain at runtime — authoring is cheaper and more reliable than a runtime flatness search.
Interruption: if the aircraft is destroyed in any state before ARRIVED,
the section and the aircraft are permanently removed from the pool, the
objective returns to unassigned with a logged reason, and HQ must re-plan.
Same for a convoy. This must be tested, not assumed.
6. Roadblock construction
A roadblock is built, not spawned. State machine, advancing on the 5 s plan tick:
| State | What exists | Duration |
|---|---|---|
SURVEYED |
Nothing. Site reserved by an incoming order | — |
MATERIEL |
Flat-packed props: stacked barriers, crates, a parked truck | on arrival |
BUILDING |
Props appear in 3 stages as progress crosses 33/66/100% | 6–10 min |
MANNED |
Complete; becomes a T3 checkpoint with its garrison | — |
DAMAGED |
Partial props, reduced garrison | on attack |
ABANDONED |
Props remain, no garrison, never repopulated | garrison wiped |
Rules:
- Progress advances whether or not anyone is watching, and props spawn on stage transitions regardless of observers. A player who leaves and returns finds a finished roadblock. Props are static and cost nothing.
- Soldiers under construction use T4 smart actions so they look like they are working. Never all of them — keep a sentry.
- Destroying props during
BUILDINGdrops progress and can return the site toMATERIEL. Destroying it repeatedly must not loop forever: after two resets HQ abandons that junction and spends the section elsewhere. - Leave a drivable gap. A roadblock that fully seals a road can soft-lock a route; it should slow and expose the player, not wall them in.
- At most 4 construction sites at once (
occupationBuildSites).
7. Interaction with HQ and fairness
- Dormant records never observe and never report. Only physical units generate contacts. Enforce in code.
- Deployment orders are HQ traffic: they travel the existing radio model
(
ERR_HQNetwork) with its latency and failure rules. Severing HQ's radio chain delays or loses deployment orders — killing radio operators slows the occupation, which is exactly the intended counterplay. - HQ's contact-driven re-prioritisation reuses the scored tasking from T5. The occupation plan proposes; HQ's priorities re-order the queue.
8. Caps (all settings, all enforced, all logged on refusal)
| Setting | Default | Meaning |
|---|---|---|
activeAgentBudget |
60 | Global active enemy agents (T2 owns enforcement) |
occupationSections |
16 | Total sections in the pool |
occupationTransportAir |
2 | Transport aircraft |
occupationTransportRoad |
3 | Trucks |
occupationConcurrentOrders |
5 | Deployments in transit at once |
occupationConcurrentLifts |
2 | Air lifts flown physically at once; others fly as records |
presenceAirMetres |
2500 | Materialisation band for aircraft (infantry use 600) |
occupationBuildSites |
4 | Simultaneous construction sites |
occupationOrderSeconds |
120 | Cadence between new orders |
occupationFallbackSeconds |
900 | Alarm fallback if nothing is detected |
9. Work breakdown
Implement in order. One agent per step is fine; steps O3 and O4 can run in parallel after O2.
- O1 — Pool, objectives and plan skeleton.
ERR_ForcePool,ERR_OccupationObjective,ERR_OccupationPlanwith the priority queue and the 5 s tick. Authored staging areas and objective list inEveronSites.conf. Start state: prison garrison only, everything else empty. - O2 — Deployment orders and transit.
ERR_DeploymentOrder,ERR_DeployRules.Select, foot and virtual transit, arrival handover to the objective. Permanent pool loss on destruction. - O3 — Air lift. The
ERR_TransportProviderinterface in core, theaiimplementation overERR_AircraftService, authored LZ points, load/fly/unload states, loss handling. - O4 — Convoy. Road routes, truck + section as a
CONVOYrecord, ambush and loss handling, blocked-route fallback to foot. - O5 — Roadblock construction. The state machine, staged props, progress, damage and abandonment, handover to T3 checkpoints.
- O6 — Adaptation and proof. Contact-driven re-prioritisation, cordon formation, plus the metrics and the measured comparison run from T7.
10. Acceptance criteria
- At T+0 the island holds troops only at the prison and its two nearby patrols; measured active agents at mission start are under 20.
- Within 10 minutes of the alarm, the radio station and the helipad have garrisons that arrived, and the journal shows how each got there.
- A player positioned along a lift route sees a real helicopter fly, land, unload soldiers and depart.
- Destroying that helicopter before it unloads permanently removes those soldiers and that aircraft from the pool; the objective is logged as unassigned and is still empty ten minutes later.
- A player watching a junction sees a roadblock built in stages over minutes, not appear at once. Leaving and returning shows progress continued.
- Active agents never exceed
activeAgentBudgetat any sample during a 20-minute occupation, and never exceed 20 in the first minute after the alarm despite the full deployment queue being issued. - A lift in transit costs 2 agents, not 10 — verified in the metrics output.
- With the AI addon absent, occupation still completes by road and foot.
- Restarting the mission leaves no deployment, aircraft, convoy or prop behind.
python3 tools/check.pypasses.
11. Tests
tests/test_occupation_plan.py
- Priority order is respected while the pool lasts, and objectives are refused once the pool is empty rather than spawning free sections.
- Pool accounting: a lost deployment permanently decrements sections and transport; totals never go negative and never regenerate.
- Cadence and concurrency caps hold under a burst of eligible objectives.
tests/test_deployment_rules.py
Selectpicks foot under 1.5 km, convoy in the middle band, air over 6 km, and falls back correctly when transport is exhausted or no provider is registered.- ETA scales with method speed and distance.
- A destroyed transport moves the order to
LOSTand releases the objective. - A lift in transit reports zero passenger agents; the section's agents appear only on unload, and the count matches the section strength.
- The physical-lift cap holds: with 6 queued lifts and a cap of 2, exactly 2 are flown physically and the other 4 remain records with valid ETAs.
tests/test_roadblock_build.py
- Progress crosses the three prop stages exactly once each, in order.
- Progress advances with no observers present.
- Two resets lead to abandonment, not an infinite rebuild loop.
- The drivable gap is preserved in every completed variant.
12. Do not
- Do not spawn the island's garrisons at mission start "for now" — the staged arrival is the feature, not an optimisation to add later.
- Do not write a second flight controller; use
ERR_AircraftServicethrough the provider interface. - Do not launch a fleet of helicopters at once. Ten physical lifts is ~90 agents before a single garrison exists, front-loads the whole occupation into five minutes, and stages nine flights where no player can see or stop them.
- Do not spawn passengers for a flight. Cargo is virtual until unload.
- Do not let core reference
aiclasses. - Do not make deployments invulnerable or teleport them on failure. If the player breaks a deployment, it stays broken.
- Do not exceed the caps to make a demo look better.