Files
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

165 lines
9.3 KiB
Markdown
Raw Permalink 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.
# 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<AIAgent>` 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<vector>)` | `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]: ...")`