Skip to content

AI Commander B69 Implementation Sketches

rayswaynl edited this page Jun 23, 2026 · 10 revisions

AI Commander B69 — Implementation Sketches

A2-OA-1.64-safe implementation sketch for each of the 78 verified items in the AI Commander B69 Improvement Roadmap. Drafts for an engineer, not committed code. SQF bodies trimmed for page size.

Branch landing note, 2026-06-22: origin/claude/b69@8d465fce supersedes several sketches with branch code in both maintained roots, but not stable/master proof. The full B69 diff from B68 head b8a1505f is 44 files / +3203 / -239 across B69-IMPLEMENTATION-PLAN.md, current Chernarus and maintained Vanilla/Takistan. Source-present pieces include HQ-strike order/gate/picker (Init_CommonConstants.sqf:229-232; Takistan Common/Init/Init_CommonConstants.sqf:649-652; AI_Commander_Strategy.sqf:530-539,547,561-565,576), capture-loop interrupt (Common_RunCommanderTeam.sqf:709-712,810,867,895,916), supervisor heartbeat/jitter/watchdog (AI_Commander.sqf:28,36,119; Takistan Server/Init/Init_Server.sqf:58-67,1015-1057), territory-credit posture plus garrison telemetry (AI_Commander_Strategy.sqf:603-640), relief min-alive gate (:424-436), MHQ nudge (AI_Commander_MHQReloc.sqf:228-257), pending-slot reaper (AI_Commander_Teams.sqf:124-128), bootstrap stipend hoist (AI_Commander.sqf:184-216), PR #48 B71 harvest changes, PR #49 B72 tint default flip, PR #50 Takistan AICOM port and PR #51 Chernarus-only B73 troop-truck guard (AI_Commander_Teams.sqf:414-418). The HC-team metadata append at AI_Commander_Teams.sqf:498-507 is scaffolding until an owning-HC top-up/merge consumer and smoke are recorded.

PR #47 merged-head addendum, 2026-06-22: origin/claude/b69-finalpieces@80d3267c1b2b is now an ancestor of origin/claude/b69@0094647d7b64; PR #47 merged as claude/b69-finalpieces -> claude/b69 at merge commit 0094647d7b641bb79202e77e7f480d6d39aadcdb. The former finalpieces delta source-implements some sketch directions for town-punch multipliers (Init_CommonConstants.sqf:389-390; AI_Commander_AssignTypes.sqf:166-167; AI_Commander_Teams.sqf:330,335-336), armed troop transport filtering (Init_CommonConstants.sqf:404; Common_RunCommanderTeam.sqf:416) and stranded survivor merge-before-cull (AI_Commander_Produce.sqf:122,127-128,156). It also adds default-off HC merge/top-up scaffolding (Init_CommonConstants.sqf:394-400; HandleSpecial.sqf:57,59; AI_Commander.sqf:232-238; AI_Commander_HCTopUp.DRAFT.sqf:50,84-85,153,158,167-168), but Init_Server.sqf:58-66 does not compile WFBE_SE_FNC_AI_Com_HCTopUp, so HC depleted-team merge/top-up is not proven live. Remaining sketches stay implementation guidance rather than committed gameplay code.

PR #48 B71-harvest addendum, 2026-06-22: origin/claude/b69@4dcc10b143a0 now also carries PR #48, merged as claude/b71-pr-harvest -> claude/b69 from 9cad74c4b6d3. The merge delta 0094647d7b64..4dcc10b143a0 is 6 Chernarus files / +40 / -13 and adds map-gated town marker updates (updatetownmarkers.sqf:11,14-16), terrain-grid profile fallback comments plus existing fallback path (Init_Client.sqf:416-440), faction tint side-resolution and opt-in default (Common_CreateVehicle.sqf:37-41; Common_AddVehicleTexture.sqf:1,7,262,271; Init_CommonConstants.sqf:795) and the bounded water-reject loop (Common_GetRandomPosition.sqf:30,33-34). This remains branch-only source evidence without maintained Vanilla propagation or Arma smoke.

PR #49 B72-tints-on addendum, 2026-06-22: origin/claude/b69@39eed5c0 now also carries PR #49, merged as claude/b72-tints-on -> claude/b69 from 7e1026ea. The merge delta 4dcc10b143a0..39eed5c0 is one current Chernarus constants file / +1 / -1 and flips the nil-guard default at Init_CommonConstants.sqf:795 from WFBE_C_VEHICLE_TINTS = 0 to 1. This remains branch-only source evidence without maintained Vanilla propagation or in-engine tint smoke; the adjacent comment still preserves the older default-off / possible-revert caveat.

PR #50 Takistan AICOM port addendum, 2026-06-22: origin/claude/b69@b8530477 now carries PR #50, merged as claude/tk-aicom-port -> claude/b69 from 3b1106a1 on 2026-06-22T13:33:48Z. The merge delta 39eed5c0..b8530477 is 22 maintained Vanilla/Takistan files / +2499 / -147. Takistan source now has the B69/B59-B72 AICOM constants at Common/Init/Init_CommonConstants.sqf:630-652,691-700, supervisor compile/spawn/watchdog at Server/Init/Init_Server.sqf:58-67,1015-1057, GUER air-defense launch/worker at Init_Server.sqf:789-795 plus Server/Server_GuerAirDef.sqf:37-58,72-76,243-252, client marker/PV support at Client/Init/Init_Client.sqf:494-496 and Client/PVFunctions/HandleSpecial.sqf:52-67, and the nil-guarded HC top-up caveat at AI_Commander.sqf:228-238. Treat this as current B69 branch propagation, not as master/stable or Arma-smoked implementation proof.

PR #51 B73 troop-truck addendum, 2026-06-22: origin/claude/b69@8d465fce now carries PR #51, merged as claude/ch-truck-fix -> claude/b69 from 70a1c808 on 2026-06-22T14:03:51Z. The merge delta b8530477..8d465fce is one Chernarus AI commander file / +5 / -1. Chernarus AI_Commander_Teams.sqf:414-418 gates the B66 pure-infantry troop-truck prepend on WFBE_C_AICOM_ARMED_TRANSPORT_ONLY <= 0, so armed-transport-only mode does not create unarmed troop trucks that Common_RunCommanderTeam rejects. No maintained Vanilla/Takistan file changed in PR #51, so this remains current B69 branch evidence, not both-root propagation or Arma-smoked proof.

aicom-supervisor-heartbeat-stamp

Per-tick heartbeat stamp at the top of the supervisor loop (the signal a watchdog needs)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf

GOAL: stamp an always-fresh per-side liveness beat `wfbe_aicom_hb_<sideID>` = time at the very TOP of the supervisor loop body, BEFORE the `_active` gate and BEFORE any worker Call, so a stale beat unambiguously means the loop died/wedged (never a healthy idle). Plus one pre-loop stamp so the variable exists the instant the loop starts. Pure server-side `missionNamespace setVariable` — no publicVariable, no JIP, no wait/timeout concern.  All commands used are A2-OA-1.64-safe: `missionNamespace`, `setVariable`, `format`, `time`, `+` string concat, `str`, `diag_log`. No isEqualType/isEqualTo/typed-params/isKindOf needed. `_myID` is an integer already resolved at line 20 (after the `isNil "_logik"` exit at line 19), so the format key cannot throw.  ================ EDIT 1 — pre-loop seed stamp ================ File: AI_Commander.sqf ANCHOR: the `waitUntil` at line 23 (server-init gate). Insert immediately AFTER it, BEFORE the doctrine block at line 25.  --- BEFORE (lines 22-25) --- 	//--- Wait for full server init before commanding. 	waitUntil {sleep 1; !(isNil "serverInitFull")};  	//--- V0.2: pick a doctrine once - the primary factory path this AI builds around.  --- AFTER --- 	//--- Wait for full server init before commanding. 	waitUntil {sleep 1; !(isNil "serverInitFull")};  	//--- SUPERVISOR HEARTBEAT (B69): seed the per-side liveness beat BEFORE the loop so the 	//--- variable exists the moment the loop starts (closes the 'never stamped yet' ambiguity). 	//--- Server-only; integer key by side ID; a watchdog reads time-(this) > N*TICK => loop dead. 	missionNamespace setVariable [format ["wfbe_aicom_hb_%1", _myID], time]; 	diag_log ("AICOMHB|v1|" + (str _side) + "|" + (str _myID) + "|SEED|" + (str (round time)));  	//--- V0.2: pick a doctrine once - the primary factory path this AI builds around.  ================ EDIT 2 — per-tick heartbeat (the load-bearing one) ================ File: AI_Commander.sqf ANCHOR: loop top at line 93 `while {!gameOver} do {` and its first body statement line 94 `_active = false;`. Insert the stamp as the FIRST statement inside the loop body, ABOVE `_active = false;` — so it runs unconditionally every tick BEFORE the `_active`/`_humanCmd`/`_canBuild` gates and BEFORE any worker Call (Execute@140, AssignTowns@144, Teams/Produce@156-198, Strategy@173, MHQReloc@179, Base@183). Any worker that throws still leaves a fresh beat from THIS tick; the NEXT tick never runs, so the beat goes stale and the watchdog can detect it.  --- BEFORE (lines 93-97) --- 	while {!gameOver} do { 		_active = false; 		if ((missionNamespace getVariable… [truncated — full sketch regenerable]

Soak test: SOAK GREP: the boot seed emits AICOMHB|v1|<side>|<id>|SEED|<t> once per side at supervisor start — grep AICOMHB *.rpt (or Select-String AICOMHB) should show exactly one SEED line per present side (e.g. WEST and EAST) within the first ~30s after MISSINIT, confirming the variable exists before the loop. RUNTIME OBSERVABLE (no extra log needed): in a live console / debug read, missionNamespace getVariable ['wfbe_aicom_hb_1', -1] should track within ~15s of time on a healthy side; to validate the dead-loop detection, force a worker exception (e.g. temporarily feed AI_Com_Produce a malfor… [truncated — full sketch regenerable]

aicom-supervisor-watchdog-restart-loop

Standalone server watchdog that re-Spawns a dead/wedged per-side supervisor

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Init/Init_Server.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_WATCHDOG = 1 (Common/Init/Init_CommonConstants.sqf (AI Commander revival block, after WFBE_C_AI_COMMANDER_TICK at line 142). Default 1=on; set 0 for instant rollback (watchdog becomes inert).)
  • new const WFBE_C_AICOM_WATCHDOG_SCAN = 30 (Common/Init/Init_CommonConstants.sqf (same block). Seconds between watchdog scans.)
  • new const WFBE_C_AICOM_WATCHDOG_COOLDOWN = 120 (Common/Init/Init_CommonConstants.sqf (same block). Per-side minimum seconds between two restarts (restart-storm guard).)
PROBLEM (verified against source): The per-side supervisor is Spawned exactly ONCE at Init_Server.sqf:1015 ({_x Spawn WFBE_SE_FNC_AI_Commander} forEach (WFBE_PRESENTSIDES - [resistance])). Its body is one `while {!gameOver} do { ... sleep WFBE_C_AI_COMMANDER_TICK }` loop (AI_Commander.sqf:93-517). If any tick throws an uncaught error the loop dies and that side has NO economy / town-assign / strategy / team-founding for the rest of the round (documented HIGH-severity single point of failure). There is currently NO heartbeat and NO restart anywhere (grep confirms wfbe_aicom_hb / WATCHDOG-restart do not exist; the one "WATCHDOG" hit in AI_Commander_Strategy.sqf:454 is an unrelated team-level wedge release).  => This item needs TWO pieces in one sitting: (A) a cheap heartbeat STAMP inside the supervisor, and (B) the standalone watchdog loop that reads it and re-Spawns. The findings `change` text assumes the heartbeat candidate ships alongside; I fold it in so the patch is self-contained.  -------------------------------------------------------------------- PART A - heartbeat stamp (AI_Commander.sqf, top of the while body) -------------------------------------------------------------------- The stamp must be the FIRST thing every tick so a fresh respawn beats immediately and a wedged inner worker that never returns leaves the beat stale. Key on the numeric side id (_myID, already computed at line 20) so the watchdog can rebuild the key without GetSideID.  BEFORE (AI_Commander.sqf:93-94):   while {!gameOver} do {       _active = false;  AFTER:   while {!gameOver} do {       //--- B69 SUPERVISOR HEARTBEAT: stamp time at the TOP of every tick. The watchdog       //--- (Init_Server) reads this per-side; a dead/wedged loop stops stamping and is       //--- restarted. _myID is the numeric side id (computed once at supervisor entry).       missionNamespace setVariable [Format ["wfbe_aicom_hb_%1", _myID], time];       _active = false;  (No new private needed - _myID is already in the private[] list and assigned at line 20. Uses only setVariable/Format/time/str = A2-OA-safe.)  -------------------------------------------------------------------- PART B - watchdog loop (Init_Server.sqf, immediately after line 1015) -------------------------------------------------------------------- Spawn ONE server-side loop right where the supervisors are spawned, so WFBE_PRESENTSIDES / WFBE_SE_FNC_AI_Commander are already in scope and serverInitFull is being awaited the same way the supervisor does.  BEFORE (Init_Server.sqf:1014-1016):   //--- feat/ai-commander: one always-running s… [truncated — full sketch regenerable]

Soak test: Greppable diag_log: on a real or forced wedge, exactly one line per stale side appears - AICOMSTAT|v1|EVENT|WEST|<min>|WATCHDOG|restart-stale-hb age=<sec> (and the WARNING via AICOMLog). SOAK PROOF: tail the server log over a long unattended run and assert grep -c 'WATCHDOG|restart-stale-hb' stays at 0 on a healthy run (no false positives), and that any AI_Commander uncaught-error in the RPT is followed within ~one WFBE_C_AICOM_WATCHDOG_SCAN+threshold window (<= ~105s at defaults) by a WATCHDOG restart line for that side AND a resumed CMDRSTAT|v1|<SIDE>|... / AICOMSTAT|...COMBATSTAT ca… [truncated — full sketch regenerable]

aicom-worker-call-guard-noheartbeat-on-error

Isolate the inline worker Calls so one worker error cannot kill the supervisor loop (defence-in-depth)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AI_COMMANDER_ISOLATE_HEAVY_WORKERS = 1 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AI_COMMANDER_BASE_INTERVAL at line ~143). Add: if (isNil "WFBE_C_AI_COMMANDER_ISOLATE_HEAVY_WORKERS") then {WFBE_C_AI_COMMANDER_ISOLATE_HEAVY_WORKERS = 1}; 1 = Spawn (isolate) the two createVehicle-touching workers (Base, MHQReloc) so a throw kills only the child run, not the supervisor; 0 = legacy inline Call. Rollback lever for soak.)
GOAL: A throw inside the two construction-touching workers (AI_Com_Base -> ConstructDefense/createVehicle at Base.sqf:524; AI_Com_MHQReloc gate body before its own Spawn at MHQReloc.sqf:151) currently unwinds straight through the inline `Call` and ENDS `while {!gameOver}` (AI_Commander.sqf:93). A Spawn'd child that throws dies alone; the supervisor loop keeps iterating and the AICOMSTAT TICK heartbeat (line 326) keeps beating. Leave the cheap, order-coupled economy workers (Execute/AssignTowns/Strategy/Teams/AssignTypes/Upgrade/Produce) as `Call` — Spawning them would race the same-tick economy sequencing the supervisor relies on (explicitly out of scope per the finding).  WHY ONLY THESE TWO ARE SAFE TO DETACH: - Both take `_this = side` (a Side scalar) — verified Base.sqf:14 `_side=_this`, MHQReloc.sqf header. `Spawn` passes the LEFT arg as `_this` exactly like `Call`, so NO call-site arg change is needed. - MHQReloc already self-forks its drive lifecycle in a Spawn at MHQReloc.sqf:151 and single-flights on wfbe_mhqreloc_active (set at :147) — it is NOT order-coupled to the same-frame economy run. Detaching its gate-evaluation body too is consistent. - Base self-gates on structure-spacing + wfbe_mhqreloc_active and runs one construction per call; a skipped/failed run just means one base tick is missed (next cadence 60s away; watchdog/next tick retries). - The throttle timestamps (_ltBase, _ltMHQReloc) are advanced optimistically (dispatched, not awaited) — acceptable.  A2-OA SAFETY: uses only `Spawn`/`Call`, `>` on a number, `getVariable [name,default]`. NO A3 commands (no isEqualType/isEqualTo/typed params). The `>0` switch read is the established WASP idiom. publicVariable is not touched (server-local control flag), so no JIP/wait concern.  --- AI_Commander.sqf:178-180 (MHQReloc dispatch) --- BEFORE:             if (time - _ltMHQReloc > (missionNamespace getVariable ["WFBE_C_AICOM_MHQ_RELOCATE_INTERVAL", 180])) then {                 (_side) Call WFBE_SE_FNC_AI_Com_MHQReloc; _ltMHQReloc = time;             }; AFTER:             if (time - _ltMHQReloc > (missionNamespace getVariable ["WFBE_C_AICOM_MHQ_RELOCATE_INTERVAL", 180])) then {                 //--- B68: isolate the createVehicle/drive-launch worker so a throw kills only the child, not the supervisor loop.                 if ((missionNamespace getVariable ["WFBE_C_AI_COMMANDER_ISOLATE_HEAVY_WORKERS", 1]) > 0) then {                     (_side) Spawn WFBE_SE_FNC_AI_Com_MHQReloc;                 } else {                     (_side) Call WFBE_SE_FNC_AI_Com_MHQReloc;                 };              … [truncated — full sketch regenerable]

Soak test: SOAK / OBSERVABILITY: 1) HEARTBEAT-SURVIVES-THROW (primary): the supervisor's existing 5-min AICOMSTAT TICK at AI_Commander.sqf:326 is the heartbeat. Grep the live RPT for continuity: grep -E "AICOMSTAT\\|v1\\|TICK\\|" rpt should show an UNBROKEN cadence (one line per side every ~5 min) with NO >5-min gap, even across a base-build error. Pre-change, a Base createVehicle throw produced a permanent gap (the loop died for that side until restart). A surviving cadence after a forced/observed Base error is the pass signal. 2) WORKER-DIED-ALONE: when a child Spawn throws, the engine logs the error… [truncated — full sketch regenerable]

armor-screen-while-infantry-captures

Give the mounted crew an outward screening overwatch instead of parking idle while infantry captures

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_SCREEN_STANDOFF = 70 (Common/Init/Init_CommonConstants.sqf — AICOM ASSAULT tunables block, immediately after WFBE_C_AICOM_ASSAULT_SAD (line 322), alongside WFBE_C_AICOM_ASSAULT_HOLD (321))
  • new const WFBE_C_AICOM_SCREEN_DRIFT = 20 (Common/Init/Init_CommonConstants.sqf — same AICOM ASSAULT block, directly below WFBE_C_AICOM_SCREEN_STANDOFF (re-issue threshold: re-order a hull only after it has drifted this many metres from its screen point, keeps the depot-hold re-issue cheap))
GOAL: during the depot-center capture phase, the still-mounted, alive, drivable, non-Air weaponized hull(s) stop parking idle and instead screen OUTWARD from the depot, ~70m out (safely beyond the 40m capture ring), along the threat axis the team approached from. Re-issued cheaply inside the existing depot-hold while-loop only when a hull drifts.  ALL primitives below are already used in THIS file (A2-OA-1.64-safe): the atan2-on-position-delta bearing idiom (L100), the weaponized-ground-hull gate `!isNull && alive && !(_x isKindOf "Air") && canMove _x` (L414, L582), setBehaviour/setCombatMode (L65, L589-590), 2-operand reveal (L778/831/888), doMove, distance, getPos. No isEqualType/isEqualTo/typed-params/binary-getDir/array-reveal.  ================================================================ STEP 1 — Constants (Init_CommonConstants.sqf, after L322) ----------------------------------------------------------------   if (isNil "WFBE_C_AICOM_ASSAULT_SAD")  then {WFBE_C_AICOM_ASSAULT_SAD  = 80}; + //--- combined-arms-choreography: mounted crew screen OUTWARD ~70m (beyond the 40m capture ring) + //--- along the approach axis while infantry caps. Standoff MUST stay > WFBE_C_TOWNS_CAPTURE_RANGE(40) + //--- so a screening hull never registers in the depot presence/resistance scan. Rollback: delete both. + if (isNil "WFBE_C_AICOM_SCREEN_STANDOFF") then {WFBE_C_AICOM_SCREEN_STANDOFF = 70}; //--- m: screen ring radius (>40m ring). + if (isNil "WFBE_C_AICOM_SCREEN_DRIFT")    then {WFBE_C_AICOM_SCREEN_DRIFT    = 20}; //--- m: re-issue only after the hull drifts this far from its screen point.  ================================================================ STEP 2 — Build the screen list + compute the outward point + issue ONE order (RunCommanderTeam.sqf) INSERT immediately AFTER the _footInf forEach loop closes (after L756) and BEFORE `if (count _footInf > 0)` at L758. _liveUnits, _townCenter, _dest, _side, _team are ALL already in scope here. ---------------------------------------------------------------- 756  		} forEach _liveUnits;    + 		//--- ===== ARMOR SCREEN (combined-arms-choreography) =====    + 		//--- The capture loop dismounts every non-crew soldier (above) but leaves vehicle crew    + 		//--- mounted with NO task -> the team's biggest gun parks idle. Collect the still-crewed,    + 		//--- alive, drivable, weaponized GROUND hulls and screen them OUTWARD beyond the 40m ring    + 		//--- toward the approach axis, COMBAT/RED so they engage. Re-issued in the hold loop below.    + 		//--- Derive hulls from LIVE units' vehicles (not the stale spawn-roste… [truncated — full sketch regenerable]

Soak test: Greppable: the new STEP-2 AICOMLog line "ARMOR-SCREEN %3 hull(s) outward at %4m (brg %5)". In the dedi RPT / AICOM log, grep ARMOR-SCREEN — expect one line per armored AICOM team entering a town capture (e.g. an MBT/APC team), with hull count >= 1 and a sane 0-360 bearing. Soak assertion: it must NEVER fire for a pure-infantry team (count would be 0 and the guarded log is skipped) and must NEVER report a standoff < 40. Observable in-engine: during a town assault by an armored team, the tank/APC should reposition to ~70m off the depot facing outward and engage, while the dismounted infantry g… [truncated — full sketch regenerable]

arty-target-enemy-relief-and-counterbattery

Base artillery: also fire on an enemy column massing against the AI's own contested town (anti-relief / soften the counter-attack), not only the spearhead town or enemy HQ

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_ARTY_DEFEND_OWN = 1 (Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after the WFBE_C_AI_COMMANDER_ARTILLERY = 0 line ~194; guard with isNil so a param/override can disable it: if (isNil "WFBE_C_AICOM_ARTY_DEFEND_OWN") then {WFBE_C_AICOM_ARTY_DEFEND_OWN = 1};))
  • new const WFBE_C_AICOM_ARTY_DEFEND_RADIUS = 250 (Common/Init/Init_CommonConstants.sqf (same AICOM block; metres around the contested-own-town centre to scan for the attacking enemy mass): if (isNil "WFBE_C_AICOM_ARTY_DEFEND_RADIUS") then {WFBE_C_AICOM_ARTY_DEFEND_RADIUS = 250};)
GOAL: insert a THIRD arty-target priority BETWEEN the HQ-strike target and the spearhead target, in AI_Commander_Strategy.sqf. Aim the (already-gated, already-friendly-fire-guarded) base guns at the CENTROID of the enemy column attacking the AI's most-threatened OWN contested town. _attacked (own contested town OBJECTS) is already computed this tick at line 405-406 and is in scope at the arty block; town objects respond to getPos (proven at lines 432/438/602/603). _side, _sideText, _enemySide are all in scope (defined lines 20/22/30).  PRIORITY ORDER AFTER CHANGE: HQ-strike target (602, unchanged) > most-threatened own town with enemy mass (NEW) > spearhead town (603, unchanged). The new block only fires when _artyTgt is still empty after the HQ check, so an active HQ strike still wins.  --- BEFORE (AI_Commander_Strategy.sqf:601-603) ---             _artyTgt = [];             if (_strikeOn && {!isNull _enemyHQ} && {alive _enemyHQ}) then {_artyTgt = getPos _enemyHQ};             if (count _artyTgt == 0 && {count _targets > 0}) then {_artyTgt = getPos (_targets select 0)};  --- AFTER ---             _artyTgt = [];             if (_strikeOn && {!isNull _enemyHQ} && {alive _enemyHQ}) then {_artyTgt = getPos _enemyHQ};             //--- NEW: anti-relief / counter-attack-softening. If no HQ strike, and the side has its OWN             //--- contested town(s) (_attacked, computed at 405-406), pick the most-threatened one (most live             //--- enemy Man/LandVehicle bodies within DEFEND_RADIUS of its centre) and aim at the CENTROID of             //--- those enemy bodies, so the rounds land on the attacking COLUMN, not the friendly-held depot.             if (count _artyTgt == 0                 && {(missionNamespace getVariable ["WFBE_C_AICOM_ARTY_DEFEND_OWN", 1]) > 0}                 && {count _attacked > 0}) then {                 _bestTownN  = 0;      //--- highest enemy-body count found so far                 _bestCentre = [];     //--- centroid [x,y,z] of the enemy mass on the winning town                 _defR = missionNamespace getVariable ["WFBE_C_AICOM_ARTY_DEFEND_RADIUS", 250];                 {                     _ot   = _x;                       //--- an own contested town object                     _foes = (getPos _ot) nearEntities [["Man","Car","Tank"], _defR];                     _sumX = 0; _sumY = 0; _sumZ = 0; _n = 0;                     {                         if ((side _x == _enemySide) && {alive _x} && {!isPlayer _x}) then {                             _ep = getPos _x;                             _sumX = _sumX + (_ep select 0);      … [truncated — full sketch regenerable]

Soak test: Greppable: the new line diag_log ("AICOMSTAT|v1|EVENT|" + _sideText + ... + "|ARTY_DEFEND_OWN|foes=" + str _bestTownN) emits a ARTY_DEFEND_OWN event whenever the new branch wins target selection. Soak verification (requires WFBE_C_AI_COMMANDER_ARTILLERY temporarily >0 in a test run, since it is force-0 in prod): grep the server RPT for AICOMSTAT|v1|EVENT|.*|ARTY_DEFEND_OWN|foes= — a non-zero foes count proves the AI re-aimed onto an attacking column at its own contested town. Cross-check that the SAME tick's FIRE_MISSION line (existing, 623) appears only when the 400m _ownNear guard pa… [truncated — full sketch regenerable]

arty-threat-10km-scan-throttle

Throttle the 10km arty-threat nearestObjects scan to a slow cached cadence instead of every Base tick

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Base.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_ARTYSCAN_INTERVAL = 300 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AICOM_ARTRAD_REQUIRE_ENEMY_ARTY at line 170; nil-guarded))
PROBLEM ANCHOR — AI_Commander_Base.sqf:251-266 (verified in source): The cond-c block runs an unconditional 10km, 3-broad-class sphere scan on EVERY Base worker call (~60s) once `time > 3600`, for the rest of the match, until the threat flag arms. On Chernarus the 10km sphere covers most of the map. This is the single most expensive recurring scan in the Base worker, paid on the FPS-bound server, and only the rare "enemy builds arty but never fires" case ever needs it (the event-driven cond-a/ cond-b paths usually arm wfbe_aicom_arty_threat first).  FIX — gate cond-c behind a per-side cached timestamp stored on `_logik` (the same side logic object that already holds wfbe_aicom_arty_threat at line 246/260). All A2-OA-safe: time / getVariable / setVariable / nearestObjects / forEach only. No PV, no new units, no behaviour change on the common (arty-fires) path. The flag, once armed, is sticky (line 251 `!_artyThreat` short-circuits), so once detection happens the scan never runs again regardless of cadence.  --- CONSTANT (Init_CommonConstants.sqf, add directly AFTER line 170) --------------- BEFORE (line 170):   if (isNil "WFBE_C_AICOM_ARTRAD_REQUIRE_ENEMY_ARTY") then {WFBE_C_AICOM_ARTRAD_REQUIRE_ENEMY_ARTY = 1}; \--- CB-GATE ...  AFTER (insert new line 171):   if (isNil "WFBE_C_AICOM_ARTRAD_REQUIRE_ENEMY_ARTY") then {WFBE_C_AICOM_ARTRAD_REQUIRE_ENEMY_ARTY = 1}; \--- CB-GATE ...   if (isNil "WFBE_C_AICOM_ARTYSCAN_INTERVAL") then {WFBE_C_AICOM_ARTYSCAN_INTERVAL = 300}; \--- B68: min seconds between the (expensive) 10km cond-c enemy-arty nearestObjects scans per side (AI_Commander_Base.sqf cond-c). Was every ~60s Base tick; throttle to 1/300s. Only affects detection latency of a SILENT (never-fired) enemy arty piece (worst case +interval). Rollback: set very low (e.g. 1) for old every-tick behaviour.  --- AI_Commander_Base.sqf cond-c gate (line 251) ----------------------------------- BEFORE (line 251):   if (!_artyThreat && {time > 3600}) then {  AFTER:   //--- B68 perf: throttle the expensive 10km cond-c scan to a slow cached cadence (per side, on _logik).   if (!_artyThreat        && {time > 3600}        && {(time - (_logik getVariable ["wfbe_aicom_artyscan_t", -1e9])) >= (missionNamespace getVariable ["WFBE_C_AICOM_ARTYSCAN_INTERVAL", 300])}      ) then {       _logik setVariable ["wfbe_aicom_artyscan_t", time];   //--- stamp BEFORE the scan so a slow forEach can't double-fire  --- (lines 252-266 inside the block are UNCHANGED: _enemySide compute, the forEach over     nearestObjects [getPos GetSideHQ, ["StaticWeapon","Tank","Car"], 10000], the flag-arm  … [truncated — full sketch regenerable]

Soak test: SOAK / OBSERVABILITY: The existing arm-event diag_log line is the natural hook — grep the server RPT for: AICOMSTAT|v1|EVENT|||ARTY_THREAT_ARMED|cond-c Behaviour to confirm on a soak where one side parks but never fires artillery past 60 min: (1) The flag still eventually ARMS via cond-c (the line appears), proving the throttled scan still works — just on the slow cadence. (2) OPTIONAL extra instrumentation for the soak run (can be left in or removed): add one diag_log immediately after the new stamp line, e.g. diag_log ("AICOMSTAT|v1|PERF|" + _sideText + "|" + str … [truncated — full sketch regenerable]

base-defense-replace-destroyed

Replace destroyed base defenses: count LIVE statics, not a monotonic placed-counter

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Base.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AI_COMMANDER_DEFENSE_SCAN_RANGE = 150 (Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after line 149 WFBE_C_AI_COMMANDER_DEFENSES_MAX))
PROBLEM ANCHOR (AI_Commander_Base.sqf, current):   458  _defMax   = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSES_MAX", 4];   459  _defCount = _logik getVariable ["wfbe_aicom_defenses", 0];   // monotonic PLACED counter   460  if (_defCount < _defMax) then {   ...   482      [_defClass, _side, _pos, _defDir, true, true] Call ConstructDefense;   // return value DISCARDED   483      _logik setVariable ["wfbe_aicom_defenses", _defCount + 1];             // only ever incremented   ... wfbe_aicom_defenses is read at 459/496, written (++) only at 483, NEVER decremented -> once 4 placed the gate at 460 is permanently satisfied; destroyed base guns are never rebuilt.  ConstructDefense (Construction_StationaryDefense.sqf:172) RETURNS the created _defense object, so the caller can tag it. Every defense already gets the GENERIC tag `wfbe_defense=true` (line 83) -- town garrison guns carry it too, so it is NOT a safe scope. We add a distinct AICOM-base tag.  --- CHANGE A: tag each AICOM base defense at construction (capture the return) --- BEFORE (line 482-483):   [_defClass, _side, _pos, _defDir, true, true] Call ConstructDefense;   _logik setVariable ["wfbe_aicom_defenses", _defCount + 1]; AFTER:   private "_defObj";   _defObj = [_defClass, _side, _pos, _defDir, true, true] Call ConstructDefense;   if (!isNull _defObj) then { _defObj setVariable ["wfbe_aicom_basedef", true, false]; };   _logik setVariable ["wfbe_aicom_defenses", _defCount + 1];   // KEEP monotonic write: arty gate at line 496 reads it as "ever built defMax"  --- CHANGE B: gate on a LIVE count, not the monotonic counter --- BEFORE (line 458-460):   _defMax   = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSES_MAX", 4];   _defCount = _logik getVariable ["wfbe_aicom_defenses", 0];   if (_defCount < _defMax) then { AFTER:   _defMax   = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSES_MAX", 4];   _defCount = _logik getVariable ["wfbe_aicom_defenses", 0];   // KEEP: still drives MG/AA alternation parity (line 465 _defCount % 2) + arty gate   // base-defense-replace-destroyed: gate on LIVE alive+tagged AICOM base statics near HQ, so   // guns lost in an assault are topped back up to DEFENSES_MAX on the 60s BASE cadence.   private ["_defScan","_liveDef"];   _defScan = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSE_SCAN_RANGE", 150];   _liveDef = 0;   {       if ((alive _x) && {_x getVariable ["wfbe_aicom_basedef", false]}) then { _liveDef = _liveDef + 1; };   } forEach (nearestObjects [_hqPos, ["StaticWeapon"], _defScan]);   if (_liveDef < _defMax) then { ...… [truncated — full sketch regenerable]

Soak test: Add a one-line diag_log at the rebuild path so a soak can observe top-ups (greppable): inside if (_liveDef < _defMax) after the ConstructDefense in CHANGE A, emit diag_log ("AICOMSTAT|v1|EVENT|" + _sideText + "|" + str (round (time/60)) + "|BASEDEF_BUILD|live=" + str _liveDef + "|max=" + str _defMax + "|class=" + _defClass); SOAK CHECK: on the live B68 GUER soak, after an assault that destroys base guns, grep the server RPT for BASEDEF_BUILD -- you should see new lines with live<max appearing within ~60s of a loss (proving rebuild), and them STOPPING once live==max (proving the cap holds… [truncated — full sketch regenerable]

bootstrap-stipend-out-of-canbuild

Move the 0-town bootstrap stipend off the build-grace gate so a brief human blip can't starve a broke AI for 300s

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf

GOAL: Hoist the BOOTSTRAP STIPEND block out of `if (_canBuild){...}` so it runs whenever the side is _active (HQ alive + AICOM enabled), regardless of _humanCmd / _canBuild / build-grace. Keep its internal guards (0 towns AND time < MAXTIME AND 60s rate guard) unchanged.  CONTEXT (current line numbers in AI_Commander.sqf):   :15   private[...] already declares EVERY local the block uses (_stipendMaxTime,_stipendTowns,_stipendActive,_prevStipendActive,_ltStipend,_tickS,_stipendFunds,_stipendSupply,_stipendFundsGrant,_stipendSupplyGrant,_dual) PLUS _myID,_side. No new private needed.   :76-77 _prevStipendActive=false; _ltStipend=-1e9;  (init before loop -- unchanged, stays correct)   :99   if (_active) then {            <-- HQ alive + ENABLED. The stipend belongs HERE.   :140  (_side) Call WFBE_SE_FNC_AI_Com_Execute;   <-- executor (every tick)   :143-145  town auto-assign   :156-165  HYBRID-REFILL block (runs only when _humanCmd)   :170  if (_canBuild) then {          <-- stipend is CURRENTLY trapped inside here   :227-267  <<< the stipend block to MOVE >>>   :282  };                             <-- close of if(_canBuild)  === STEP 1: DELETE the stipend block from inside if(_canBuild) === Cut lines :227-267 verbatim. That is the comment header   //--- V0.7 BOOTSTRAP STIPEND: ... through the closing `};` of `if (_stipendActive) then { ... }` at :267. (Leave the WEALTH-CONVERSION block :200-225 and the CBR-research block :269-281 exactly where they are. The cut is a clean contiguous span; nothing else in if(_canBuild) references _stipend* or _prevStipendActive.)  === STEP 2: PASTE it into the _active scope, OUTSIDE _canBuild === Insert the cut block immediately AFTER the executor + town-assign and BEFORE the HYBRID-REFILL block -- i.e. between current line :145 and :147. Pseudo-diff:    140    (_side) Call WFBE_SE_FNC_AI_Com_Execute;   ...   145    (_side) Call WFBE_SE_FNC_AI_Com_AssignTowns; _ltTown = time;   146    }; + 146a  //--- B69 (bootstrap-stipend-out-of-canbuild): the 0-town survival drip is a LIFELINE, not a + 146b  //--- 'spend'. It must run on the _active gate (HQ alive + AICOM enabled) so a one-tick human + 146c  //--- blip that re-arms _noHumanSince (zeroing _canBuild for 300s) cannot suspend the only income + 146d  //--- a broke 0-town side has. Internal guards unchanged: 0 towns AND time<MAXTIME AND 60s rate. + 146e  //--- Funds route to the SEPARATE AICOM treasury (ChangeAICommanderFunds) -> no contention with a + 146f  //--- human commander's spend in assist state (same treasury separation HYBRID-REFILL relies on). + 146g  //--- <<< paste… [truncated — full sketch regenerable]

Soak test: Greppable existing diag_log (already emitted by the moved block, unchanged): AICOMSTAT|v1|EVENT|<side>|<min>|BOOTSTRAP_STIPEND|start. SOAK PROOF: induce a human-commander blip on a 0-town side (vote in then immediately out, or just observe a votes-enabled LOCK=0 match where a side is wiped to 0 towns while a human briefly holds the slot). BEFORE the fix: after the blip, no further BOOTSTRAP_STIPEND grants and AICOM funds stay flat for ~300s (starvation) -- visible as a flat funds field in the 5-min AICOMSTAT|v1|TICK|... line with towns=0. AFTER the fix: BOOTSTRAP_STIPEND|start fires … [truncated — full sketch regenerable]

buyunit-aicom-refund-on-cancel

Refund the AI-commander treasury when Server_BuyUnit cancels a queued AICOM refill

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Functions/Server_BuyUnit.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_REFUND_ON_CANCEL = 1 (Common/Init/Init_CommonConstants.sqf (AICOM/economy block; same file that already defines QUERYUNITPRICE=2 / QUERYUNITTIME=3 at lines 8-9). Default 1 = refund enabled. Set 0 to revert to legacy burn-on-cancel without editing logic.)
GOAL: when Server_BuyUnit cancels a *queued AI-commander* unit because its FACTORY WAS DESTROYED mid-queue, return the per-unit config price to that side's AI treasury. Do NOT refund the human-takeover branch (the queue transfers to the human, who is now the team owner) and do NOT touch the human player buy path at all.  WHY A HELPER MACRO: the same refund snippet is needed in 3 early-exit blocks (lines 12-17, 76-84, 107-112). To keep it A2-OA-safe and DRY, compute the refund once near the top (price is constant for the life of this _unitType) and reuse a single `_aiRefund` closure-free expression.  --- STEP A. Top-of-file: capture refund eligibility + amount ONCE (insert after line 10, `_sideText = str _side;`). All scalar/string/bool ops, no A3 commands. ---    // BEFORE (line 8-10):   _isVehicle = _this select 5;   _sideText = str _side;    // AFTER (add below line 10):   _isVehicle = _this select 5;   _sideText = str _side;   //--- AICOM refund-on-cancel (finding buyunit-aicom-refund-on-cancel): only AI-owned teams   //--- (no human leader) on a real SIDE refund. Player buys/takeovers never refund here.   _aiRefundOK = ((missionNamespace getVariable ["WFBE_C_AICOM_REFUND_ON_CANCEL", 1]) > 0)                 && {!(isPlayer (leader _team))}                 && {(typeName _side) == "SIDE"};   _aiRefundAmt = 0;   if (_aiRefundOK) then {       private "_ud";       _ud = missionNamespace getVariable _unitType;          //--- unit config tuple (same lookup as _waitTime @ line 31)       if (!isNil "_ud" && {(count _ud) > QUERYUNITPRICE}) then {_aiRefundAmt = _ud select QUERYUNITPRICE};   };    (Add `_aiRefundOK` and `_aiRefundAmt` to the Private[] list on line 1.)  --- STEP B. Each of the 3 cancel branches: refund ONLY on the factory-destroyed sub-condition (`!alive _building`), NEVER on the human-takeover sub-condition (`isPlayer (leader _team)`). ---  The existing branches already split the two reasons with `if !(alive _building) then {...log...}` and `if (isPlayer (leader _team)) then {...log...}`. Hook the refund onto the EXISTING `if !(alive _building) then {...}` log line so scoping is automatic.    // BRANCH 1 -- BEFORE (line 15):   if !(alive _building) then {["INFORMATION", Format ["Server_BuyUnit.sqf: Unit [%1] construction has been stopped due to factory destruction.", _unitType]] Call WFBE_CO_FNC_LogContent};   // BRANCH 1 -- AFTER:   if !(alive _building) then {       ["INFORMATION", Format ["Server_BuyUnit.sqf: Unit [%1] construction has been stopped due to factory destruction.", _unitType]] Call WFBE_CO_FNC_LogContent;       if (_aiRefundOK && … [truncated — full sketch regenerable]

Soak test: Greppable log: each refund emits Server_BuyUnit.sqf: AICOM-REFUND via WFBE_CO_FNC_LogContent (with the pre-loop / in-loop / post-wait tag, the _unitType, the amount, and the side). Soak procedure on Chernarus B68: let the AI commander queue refills, then destroy an AI factory while units are mid-queue (or watch a long match where factories are naturally lost). Expect to see AICOM-REFUND ... refunded <price> to <side> (factory destroyed ...) lines paired with the existing construction has been stopped due to factory destruction lines, and NO refund line accompanying any `player [...] has … [truncated — full sketch regenerable]

camp-plant-dostop-proximity-rewake

Replace the doStop camp-plant with a tight doMove (or add a player-proximity re-wake) to honor the never-frozen-AI guardrail

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf

  • new const WFBE_C_AICOM_CAMP_REWAKE_RANGE = 200 (Init_CommonConstants.sqf (AICOM/WFBE_C_AICOM_* constants block; read in Common_RunCommanderTeam.sqf via missionNamespace getVariable ["WFBE_C_AICOM_CAMP_REWAKE_RANGE", 200]))
CONTEXT (Common_RunCommanderTeam.sqf, camp-first anti-orbit hold, ~L840-850). The depot-center hold at L873-894 is the proven correct pattern: doMove-only, no doStop, presence capture still flips the town. The camp loop is the lone inconsistency: it doStops planted foot units, then `sleep 8` dwells while they stand frozen with NO proximity re-wake.  APPROACH (recommended = doMove-only + belt-and-braces re-wake guard, mirrors depot idiom): Drop the doStop branch entirely and keep planted units pressing the camp CENTRE with a tight doMove. _campHoldR is already ~8m (L817), comfortably inside the 10m "Man" scan, so doMove-to-centre keeps them in the capture ring without orbiting -- exactly like L873/L894. Add a player-proximity re-issue immediately after the dwell so that even if the engine parks a unit, any planted unit is re-pressed (never standing still) whenever a player is within WFBE_C_AICOM_CAMP_REWAKE_RANGE (200m). Keep setUnitPos "UP" for the prone stance; the L857 "AUTO" release is unchanged.  ---------------- BEFORE (L840-850) ----------------   //--- ANTI-ORBIT HOLD: any foot unit now inside the zone STOPS and stands ...   {       if (alive _x) then {           if ((_x distance _nearCamp) < _campRange) then {_x setUnitPos "UP"; doStop _x}           else {_x doMove _campTgtPos};       };   } forEach _footInf;   //--- Dwell so the 10m camp scan ticks the supplyValue down to a flip.   sleep 8;  ---------------- AFTER (L840-850) -----------------   //--- ANTI-ORBIT HOLD (never-frozen): units inside the zone hold a PRONE stance but keep   //--- a TIGHT live move order on the camp centre (mirrors the depot-centre hold L873/L894)   //--- instead of doStop. Units outside keep the same move order. No unit is ever doStop-   //--- frozen, so the MEMORY guardrail (a player must never see a standing-still AI) holds.   {       if (alive _x) then {           if ((_x distance _nearCamp) < _campRange) then {_x setUnitPos "UP"; _x doMove _campTgtPos}  //--- was: doStop _x           else {_x setUnitPos "AUTO"; _x doMove _campTgtPos};       };   } forEach _footInf;   //--- Dwell so the 10m camp scan ticks the supplyValue down to a flip.   sleep 8;   //--- RE-WAKE GUARD: if any player is near the camp, re-press every planted foot unit so   //--- the engine cannot leave one parked/standing in view during the dwell. Reuses the   //--- proven playableUnits/isPlayer/distance idiom already at L530 & L555 (A2-OA-safe).   _campPlayerNear = false;   {       if (isPlayer _x && {(_x distance _nearCamp) < (missionNamespace getVariable ["WFBE_C_AICOM_CAMP_REWAKE_RANGE", 200])})… [truncated — full sketch regenerable]

Soak test: SOAK: grep the server RPT for the new diag line camp-rewake re-press near player -- it should appear whenever a player drives within 200m of a contested camp during an AICOM assault, confirming planted units are re-pressed rather than doStop-frozen. NEGATIVE CHECK: confirm doStop no longer appears in the camp-first loop (the only remaining doStop references in the file should be unrelated). BEHAVIOURAL: stand a player ~50m from an enemy camp the AI is capturing and watch the foot squad -- they should hold prone IN the 10m ring and micro-adjust toward centre, never freeze standing/idle; the… [truncated — full sketch regenerable]

capture-hold-mass-aware-shorter-budget

Capture-hold uses a flat 360s budget for BOTH camp-first and depot phases, so a 6-team concentration parks ~12 min on one town while the rest of the front idles — make the hold mass-aware

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_AssignTowns.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HOLD_MASS_THRESHOLD = 2 (Init_CommonConstants.sqf — ASSAULT FINISH tunables block, insert right after WFBE_C_AICOM_ASSAULT_SAD (line 322). Comment: teams-converging count (incl. self) ABOVE which the depot/camp hold budget is shortened; <= keeps the full lone-team budget.)
  • new const WFBE_C_AICOM_HOLD_MASS_FACTOR = 0.5 (Init_CommonConstants.sqf — same block, line after the threshold. Comment: budget multiplier applied to WFBE_C_AICOM_ASSAULT_HOLD when a town is massed (0.5 -> ~180s); a lone team keeps 1.0 (full 360s).)
PROBLEM ANCHOR (verified against the live tree, not the findings blurb): - Common_RunCommanderTeam.sqf:800 (camp-first) and :880 (depot-center) both budget `time + WFBE_C_AICOM_ASSAULT_HOLD` (=360). With WFBE_C_AICOM_CONCENTRATION=6 (Init:226), up to 6 teams converge on one depot; the surplus orbits ~12 min (camp 360 + depot 360) while the rest of the front idles.  CRITICAL CORRECTION to the findings item: it says "extend wfbe_aicom_order with a 4th element = _massCount". That slot is ALREADY TAKEN — AssignTowns:407 publishes `[seq,"towns-target",_hcDest,_hcStrk]` and RunCommanderTeam:501 reads `_order select 3` as the unstuck strike tier, on the SAME HC path the hold loops run on. So do NOT reuse slot 3. Carry mass in a PARALLEL group var (mirrors wfbe_aicom_route / wfbe_aicom_unstuck at AssignTowns:401-402) — decoupled, isNil-guardable, no contract churn for the 3-element/defense publishers (AssignTowns:151, Strategy:397/438/489/544, Produce:136, groupsGC:155).  --- EDIT 1: Init_CommonConstants.sqf (after line 322) -----------------------   if (isNil "WFBE_C_AICOM_HOLD_MASS_THRESHOLD") then {WFBE_C_AICOM_HOLD_MASS_THRESHOLD = 2}; //--- teams-converging (incl self) ABOVE this shortens the hold. <= keeps lone-team full budget.   if (isNil "WFBE_C_AICOM_HOLD_MASS_FACTOR")    then {WFBE_C_AICOM_HOLD_MASS_FACTOR    = 0.5}; //--- budget x this when massed (~180s); lone team = 1.0 (full 360s).  --- EDIT 2: AI_Commander_AssignTowns.sqf — broadcast mass at the HC publish - At line 407, _target is set (346) and _assigned is the live en-route list (mirrors the quota gate at :320 `{_x == _spearT} count _assigned`). Insert BEFORE line 407:   //--- mass-aware hold: how many teams are converging on THIS town (en-route already in _assigned, +1 for this dispatch). Mirrors the :320 quota count. Carried as a parallel group var (slot 3 of the order is the unstuck strike tier).   private "_massConv";   _massConv = ({_x == _target} count _assigned) + 1;   _team setVariable ["wfbe_aicom_holdmass", _massConv, true]; (The `+1` is because _assigned set [count _assigned,_target] runs AFTER, at :415, so this team isn't counted yet.)  --- EDIT 3: Common_RunCommanderTeam.sqf — scale both budgets ----------------- Add `"_holdMass","_holdBudget"` to the function-level Private at line 16-20. After _order parse (~482) but before the capture block, OR inline just above each budget line, read once:   _holdMass = _team getVariable "wfbe_aicom_holdmass"; if (isNil "_holdMass") then {_holdMass = 1};   _holdBudget = (missionNamespace getVariable ["WFBE_C_AICOM_ASSAULT_HOLD", 360]) * (if (_ho… [truncated — full sketch regenerable]

Soak test: Add one diag_log at the budget computation in Common_RunCommanderTeam.sqf so soak greps can A/B it. Insert right after _holdBudget is computed: diag_log ("AICOMSTAT|v2|EVENT|" + (str _side) + "|" + str (round (time/60)) + "|HOLD_MASS|team=" + (str _team) + "|mass=" + str _holdMass + "|budget=" + str _holdBudget); SOAK CHECKS on the RPT: 1) grep "HOLD_MASS" — expect a stream of lines; mass=1/mass=2 rows show budget=360, mass=3+ rows show budget=180 (with defaults). If a mass>=3 row ever shows budget=360 the guard/threshold is mis-wired. 2) OBSERVABLE: spectate a 6-team spearhead — t… [truncated — full sketch regenerable]

capture-phase-seq-interrupt

Make the blocking capture phase honor a fresh order seq + disband flag (break out of the camp/depot while-loops on re-task)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_CAPTURE_INTERRUPT = 1 (Init_CommonConstants.sqf AICOM block, add directly under WFBE_C_AICOM_ASSAULT_HOLD at line 321 (the ASSAULT FINISH tunables group, ~322): if (isNil "WFBE_C_AICOM_CAPTURE_INTERRUPT") then {WFBE_C_AICOM_CAPTURE_INTERRUPT = 1}; // 0=legacy blocking behaviour, 1=abort capture loops on fresh order seq / disband. Rollback to 0 restores B68 exactly.)
GOAL: make the towns-target capture phase (Common_RunCommanderTeam.sqf 702-975) re-taskable within ~8s instead of going deaf for up to ~12 min. Latch the seq at phase entry, then add a cheap A2-OA-safe abort guard to (a) the camp-first while (801), (b) the depot-center hold while (882), and (c) the per-camp sweep settle (775) + per-camp dwell (783). On abort, exitWith out of the capture block; the outer 8s loop (1065) then re-reads the fresh order (484) and re-routes. ZERO behavior change when no re-task arrives.  === CRITICAL A2-OA CORRECTION TO THE FILED ITEM === The item's proposed guard `(_team getVariable [\"wfbe_aicom_order\",[_capSeq]]) select 0` uses the 2-arg [name,default] getVariable form ON A GROUP. That form is NOT supported on groups in A2-OA — this very file warns it twice: line 476 \"groups do not support the [name, default] getVariable form; plain get + isNil\" (so _order is read plain at 477-478) and line 716 for _townorder. Using the 2-arg form here would read nil/garbage and the guard could spuriously fire (or error). The sketch below uses the PLAIN-GET + isNil idiom instead (mirrors 458-459 / 477-478 / 716-718). Everything else stays as filed.  --- 0) Helper macro idea (NOT a real macro — A2 has no inline lambdas). Define a reusable abort predicate as a small inline snippet we paste into each while header. We compute it into a bool just BEFORE/INSIDE each loop using plain get + isNil.  --- 1) PHASE ENTRY: latch the seq + read the disband flag once. Insert right after line 706 early-exit, before _townObj resolve (707):   // CAPTURE-PHASE INTERRUPT (capture-phase-seq-interrupt): snapshot the seq we entered on.   // _seq is already in scope (set at 480 from _order select 0). Plain scalar copy — A2-safe.   private \"_capSeq\"; _capSeq = _seq;   private \"_capInt\"; _capInt = (missionNamespace getVariable [\"WFBE_C_AICOM_CAPTURE_INTERRUPT\", 1]) > 0;  --- 2) Reusable read of \"should I abort?\" — paste this exact pattern wherever needed. It returns true when (interrupt enabled) AND (a fresh order seq differs from _capSeq OR disband flag is set). A2-OA-safe: plain getVariable + isNil + count + select + scalar/bool compare, NO 2-arg group getVariable, NO A3 commands.   // ---- BEGIN abort-predicate snippet ----   private [\"_curOrd\",\"_curSeq\",\"_disF\",\"_abort\"];   _curOrd = _team getVariable \"wfbe_aicom_order\";        // plain get (group-safe)   if (isNil \"_curOrd\") then {_curOrd = []};   _curSeq = if (count _curOrd >= 1) then {_curOrd select 0} else {_capSeq}; // default == _capSeq => missing var never aborts   _disF   = _team ge… [truncated — full sketch regenerable]

Soak test: SOAK GREP: new diag_log line AICOMSTAT|v1|EVENT|<sideID>|<min>|CAPTURE_INTERRUPT|<campfirst|depothold|sweep>|seq<old>-><new> fires on every successful interrupt. Soak validation: (1) Positive — during a live round, when AssignTowns re-bumps an order seq (front roll / town taken by another team / blacklist promotion) while a team is grinding a depot, expect a CAPTURE_INTERRUPT line within ~8-10s, immediately followed (next outer pass) by the existing fresh-order log at the same team. grep count of CAPTURE_INTERRUPT should be > 0 over a 2h soak (was structurally impossible before). (2) Negativ… [truncated — full sketch regenerable]

centralize-econ-gate-and-funds-per-extra-consts

Define the orphaned inline-default economy constants (RETREAT_HOME_RANGE, RETREAT_MAX_TRIES/MIN_CLOSE, FUNDS_PER_EXTRA_TEAM, ECON_GATE_TOWNS) centrally so they are tunable and rollback-consistent

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RETREAT_HOME_RANGE = 800 (Common/Init/Init_CommonConstants.sqf (AICOM block, after the B68 RETREAT_MAX_ISSUES/MAX_DIST lines ~284))
  • new const WFBE_C_AICOM_RETREAT_MAX_TRIES = 4 (Common/Init/Init_CommonConstants.sqf (same block))
  • new const WFBE_C_AICOM_RETREAT_MIN_CLOSE = 50 (Common/Init/Init_CommonConstants.sqf (same block))
  • new const WFBE_C_AI_COMMANDER_FUNDS_PER_EXTRA_TEAM = 15000 (Common/Init/Init_CommonConstants.sqf (same block) — load-bearing: sets BOTH founding funds-extra (Teams.sqf:60) AND rich-flag threshold *2 (AI_Commander.sqf:203))
  • new const WFBE_C_AICOM_ECON_GATE_TOWNS = 6 (Common/Init/Init_CommonConstants.sqf (same block))
PURE-ADDITIVE CENTRALIZATION. Define 5 currently-orphaned consts at their exact inline defaults so they become tunable/rollback-consistent. ZERO behaviour change at the listed values. Call sites are LEFT UNTOUCHED — they keep their getVariable[name,default] safety net, so they still degrade gracefully if a const is ever absent (JIP/load-order safe).  === VERIFIED FACTS (anchored to current B68 tree) === - Grep of Init_CommonConstants.sqf for all 5 names -> "No matches found": confirmed genuinely orphaned (none defined anywhere in constants). - Sibling B68 retreat consts ALREADY live at lines 283-284 using the exact `if (isNil "NAME") then {NAME = N};` pattern -> insert the new block immediately AFTER line 284 to keep the retreat family together. - Inline defaults confirmed at call sites:   * Produce.sqf:88   _homeR     = missionNamespace getVariable ["WFBE_C_AICOM_RETREAT_HOME_RANGE", 800];   * Produce.sqf:103  _rBudget   = missionNamespace getVariable ["WFBE_C_AICOM_RETREAT_MAX_TRIES", 4];   * Produce.sqf:104  _rMinClose = missionNamespace getVariable ["WFBE_C_AICOM_RETREAT_MIN_CLOSE", 50];   * AI_Commander.sqf:203 _richThreshold = (... getVariable ["WFBE_C_AI_COMMANDER_FUNDS_PER_EXTRA_TEAM", 15000]) * 2;   * Teams.sqf:60      _fundsPerExtraTeam = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_FUNDS_PER_EXTRA_TEAM", 15000];   * AI_Commander_Base.sqf:282 _econGateTowns = missionNamespace getVariable ["WFBE_C_AICOM_ECON_GATE_TOWNS", 6];  === EDIT: Init_CommonConstants.sqf, insert AFTER line 284 ===  BEFORE (lines 281-285):   //--- B68 (Ray 2026-06-21) RETREAT-CULL hardening: ...   if (isNil "WFBE_C_AICOM_RETREAT_MAX_ISSUES") then {WFBE_C_AICOM_RETREAT_MAX_ISSUES = 8};   if (isNil "WFBE_C_AICOM_RETREAT_MAX_DIST") then {WFBE_C_AICOM_RETREAT_MAX_DIST = 6000};   //--- B67 (Ray 2026-06-21) BUILD PLACEMENT (item #10): ...  AFTER (new block inserted between the two comment blocks):   if (isNil "WFBE_C_AICOM_RETREAT_MAX_ISSUES") then {WFBE_C_AICOM_RETREAT_MAX_ISSUES = 8};   if (isNil "WFBE_C_AICOM_RETREAT_MAX_DIST") then {WFBE_C_AICOM_RETREAT_MAX_DIST = 6000};   //--- B68 ECON-CENTRALIZE: surface 5 economy/retreat knobs that were read via getVariable[name,default]   //--- but never assigned here (masqueraded as tunables; actually hardcoded inline). Values EQUAL the   //--- current inline defaults => zero behaviour change; now A/B-tunable + rollback-consistent. Call sites   //--- keep their 2-arg getVariable fallback as the safety net (degrade-safe if a const ever goes missing).   if (isNil "WFBE_C_AICOM_RETREAT_HOME_RANGE") then {WFBE_C_AICOM_RETREAT_HOME_RAN… [truncated — full sketch regenerable]

Soak test: SOAK / VERIFY HOOKS: 1) Parse gate (pre-deploy): boot-smoke the mission and assert ErrInExpr=0 / MISSINIT present in the RPT — a typo'd scalar or missing brace fails here before live. 2) Identity check (no-regression): on the server, after MISSINIT, run in debug or add a one-shot diag_log: diag_log format ["ECON-CENTRALIZE check homeR=%1 tries=%2 close=%3 fpe=%4 gate=%5", WFBE_C_AICOM_RETREAT_HOME_RANGE, WFBE_C_AICOM_RETREAT_MAX_TRIES, WFBE_C_AICOM_RETREAT_MIN_CLOSE, WFBE_C_AI_COMMANDER_FUNDS_PER_EXTRA_TEAM, WFBE_C_AICOM_ECON_GATE_TOWNS]; Greppable expected line in the RPT: `ECON-CENTRAL… [truncated — full sketch regenerable]

cmdrstat-telemetry-firstfire-offset

Stop the heavy per-side CMDRSTAT/COMBATSTAT telemetry block from firing on the very first tick for both sides

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AI_COMMANDER_STAT_JITTER = 120 (Common/Init/Init_CommonConstants.sqf (AICOM block, the //--- AI. section ~line 134, next to WFBE_C_AI_COMMANDER_SUPPLY_TRUCKS_MAX))
GOAL: Remove the boot co-fire of the heavy per-side AICOMSTAT/CMDRSTAT/COMBATSTAT scan (AI_Commander.sqf:297-447), which iterates wfbe_teams and classifies every group, on tick 1 for BOTH sides simultaneously. Today _ltStat = -301 makes the gate `time - _ltStat >= 300` (line 292) true on the first tick. Each side runs its own copy of this loop and both enter at ~time 0, so they co-fire.  FIX = option (b) [seed at time -> first scan a full 300s in, removes the tick-1 scan entirely] PLUS a small per-side random jitter so the two sides also DE-CORRELATE (never co-fire 300s later either). `random` and `time` are A2-OA-1.64 core. No A3 commands.  --- STEP 1: new constant (Init_CommonConstants.sqf, AICOM `//--- AI.` block) --- Place beside the existing AI-commander constants (e.g. just after WFBE_C_AI_COMMANDER_SUPPLY_TRUCKS_MAX = 5; ~line 134), matching the file's isNil idiom:    //--- B68 (cmdrstat-telemetry-firstfire-offset): de-correlate + defer the heavy per-side AICOMSTAT/CMDRSTAT/COMBATSTAT   //--- scan so it no longer co-fires on tick 1 for both sides (was _ltStat=-301 -> true on first tick). 0 disables the   //--- jitter (pure 300s defer). Telemetry is non-critical at boot; the [AICOM BOOT] one-shot (AI_Commander.sqf:86) already   //--- covers the start snapshot. Rollback: set to 0 (or revert line 72 to -301) to restore old first-tick behaviour.   if (isNil "WFBE_C_AI_COMMANDER_STAT_JITTER") then {WFBE_C_AI_COMMANDER_STAT_JITTER = 120};  --- STEP 2: AI_Commander.sqf line 72 (the per-side throttle seed) ---  BEFORE (line 72, one statement among several on the line):   _ltTypes = 0; _ltUp = 0; _ltTown = 0; _ltProd = 0; _ltBase = 0; _ltTeams = 0; _ltStrat = 0; _ltStat = -301; _ltBrief = 0; _ltMHQReloc = 0;  AFTER (change ONLY the _ltStat = -301 sub-statement; leave the rest of the line intact):   _ltTypes = 0; _ltUp = 0; _ltTown = 0; _ltProd = 0; _ltBase = 0; _ltTeams = 0; _ltStrat = 0; _ltStat = (time + (random (missionNamespace getVariable ["WFBE_C_AI_COMMANDER_STAT_JITTER", 120]))); _ltBrief = 0; _ltMHQReloc = 0;  Add a one-line comment directly above line 72 (above the `_ltTypes = ...` line):   //--- B68 (cmdrstat-telemetry-firstfire-offset): seed _ltStat at `time + random jitter` instead of -301. This defers the   //--- first heavy AICOMSTAT/CMDRSTAT/COMBATSTAT scan a full ~300s in AND staggers the two sides 0..JITTER s apart so they   //--- never co-fire on the match-start spike. The [AICOM BOOT] snapshot (line 86) still covers t=0. See WFBE_C_AI_COMMANDER_STAT_JITTER.  WHY this is correct / A2-safe:   - Gate at line 292 stays `if (time - _ltStat >… [truncated — full sketch regenerable]

Soak test: SOAK / OBSERVABLE: (1) Grep the server RPT for the first per-side telemetry line and confirm it does NOT appear in the first ~280s of the round and that the two sides are staggered, e.g. Select-String 'AICOMSTAT\|v1\|.*CMDRSTAT' server.rpt | Select-Object -First 4 — the first WEST and first EAST CMDRSTAT timestamps should differ by 0..120s and both be >= 300s after MISSINIT, NOT clustered at t0. (2) Confirm the start snapshot still lands at boot: grep [AICOM BOOT] appears once per side at t=0 (unchanged). (3) Confirm steady-state cadence intact: consecutive same-side AICOMSTAT|TICK lines… [truncated — full sketch regenerable]

concentration-arrival-arc-by-lanejit

Disperse the depot-hold by a per-team approach arc so 6 massed teams don't bunch on one 40m point

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_ASSAULT_ARC_RADIUS = 28 (Common/Init/Init_CommonConstants.sqf (AICOM ASSAULT FINISH tunables block, insert after line 322 / WFBE_C_AICOM_ASSAULT_SAD))
GOAL: each of up to 6 massed teams holds its OWN arc segment of the 40m capture ring instead of all doMove-ing the identical town center. Reuse the persistent per-team lane seed wfbe_aicom_lanejit ({-1,0,1}) already set at AI_Commander_AssignTowns.sqf:385 -> 3 bearings 120deg apart. All math is plain 2D array arithmetic on the position; NO A3 commands.  === EDIT 1: new constant (Init_CommonConstants.sqf, after line 322) === BEFORE (lines 321-322):   if (isNil "WFBE_C_AICOM_ASSAULT_HOLD") then {WFBE_C_AICOM_ASSAULT_HOLD = 360};   if (isNil "WFBE_C_AICOM_ASSAULT_SAD")  then {WFBE_C_AICOM_ASSAULT_SAD  = 80}; AFTER (add one line):   if (isNil "WFBE_C_AICOM_ASSAULT_SAD")  then {WFBE_C_AICOM_ASSAULT_SAD  = 80};   //--- m: per-team FINAL hold-offset radius around the depot center. Each of up to 6 massed teams holds a   //--- distinct arc point this far off-center (bearing = 120*(lanejit+1)) so they spread around the capture   //--- ring instead of stacking on one 40m point (A2 PFM bunching). MUST stay < WFBE_C_TOWNS_CAPTURE_RANGE(40)   //--- or presence capture breaks; 28 leaves a 12m margin. 0 = rollback to center-stacking.   if (isNil "WFBE_C_AICOM_ASSAULT_ARC_RADIUS") then {WFBE_C_AICOM_ASSAULT_ARC_RADIUS = 28};  === EDIT 2: compute _holdPt once, right after _townCenter is established (RunCommanderTeam.sqf, after line 727) === BEFORE (line 727):   _townCenter = if (!isNull _townObj) then {getPos _townObj} else {_dest};   _capRange   = missionNamespace getVariable ["WFBE_C_TOWNS_CAPTURE_RANGE", 40]; AFTER (insert _holdPt derivation between them):   _townCenter = if (!isNull _townObj) then {getPos _townObj} else {_dest};   //--- ARC-DISPERSE (concentration-arrival-arc-by-lanejit): give each massed team a distinct hold offset   //--- around the depot so up to 6 teams ring the capture zone instead of stacking on the exact center.   //--- Reuse the persistent per-team lane seed (set at AI_Commander_AssignTowns.sqf:385). A2: groups use   //--- plain getVariable + isNil (no [name,default] form on groups).   _laneJit = _team getVariable "wfbe_aicom_lanejit";   if (isNil "_laneJit") then {_laneJit = 0};   _arcR  = missionNamespace getVariable ["WFBE_C_AICOM_ASSAULT_ARC_RADIUS", 28];   _brg   = 120 * (_laneJit + 1);                       //--- 0 / 120 / 240 deg, three 120-separated arcs   _holdPt = [ (_townCenter select 0) + (_arcR * (cos _brg)),               (_townCenter select 1) + (_arcR * (sin _brg)),               0 ];                                     //--- plain 2D arith on the coord array; no getPos on an array   _capRange   = missionNamespace getVariable [… [truncated — full sketch regenerable]

Soak test: SOAK / OBSERVABLE: (1) Add a one-shot diag breadcrumb next to the new _holdPt block so it's greppable in the server RPT — e.g. right after _holdPt is computed: ["INFORMATION", Format ["Common_RunCommanderTeam.sqf: [%1] team [%2] ARC-HOLD lanejit=%3 brg=%4 holdPt=%5 (center %6).", _side, _team, _laneJit, _brg, _holdPt, _townCenter]] Call WFBE_CO_FNC_AICOMLog; then grep the RPT for 'ARC-HOLD' and confirm DISTINCT brg values (0/120/240) across teams converging on the same town and holdPt != town center. (2) Behavioural: spectate a CONCENTRATION=6 spearhead flip — infantry should ring the depot in… [truncated — full sketch regenerable]

construction-hqsite-lock-timeout

Bound the unbounded wfbe_hqinuse waitUntil in Construction_HQSite so a stuck lock can't deadlock MHQ toggling

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Construction/Construction_HQSite.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_HQSITE_LOCK_TIMEOUT = 20 (Common/Init/Init_CommonConstants.sqf (B60 MHQ RELOCATION block, after WFBE_C_AICOM_MHQ_STUCK_SECS at :249))
PROBLEM ANCHOR (verified):   Construction_HQSite.sqf:13-15 (the ONLY unbounded wait in the base lifecycle):     13  /* Handle the LAG. */     14  waitUntil {!(_logik getVariable "wfbe_hqinuse")};     15  _logik setVariable ["wfbe_hqinuse", true];   Both AI MHQ-relocate paths ExecVM this script: AI_Commander_MHQReloc.sqf:169 (MOBILIZE) and :293 (RE-DEPLOY).   Those Spawns have their own 30s/8s grace (MHQReloc.sqf:170-171,180-181) which times out and clears   wfbe_mhqreloc_active -- but if wfbe_hqinuse is stuck true, the ExecV'd toggle here blocks FOREVER and the   HQ is left half-toggled with no recovery. Line 117 (_logik setVariable ["wfbe_hqinuse", false]) is the   normal release; if a prior toggle errored before reaching :117, the lock stays true permanently.  === EDIT 1: Construction_HQSite.sqf -- bound the wait + force-claim a stale lock ===  BEFORE (lines 13-15):     /* Handle the LAG. */     waitUntil {!(_logik getVariable "wfbe_hqinuse")};     _logik setVariable ["wfbe_hqinuse", true];  AFTER:     /* Handle the LAG. -- B68: bound this wait so a stale lock (a prior toggle that errored before        releasing wfbe_hqinuse at :117) cannot deadlock the MHQ toggle forever. Both AI relocate paths        (AI_Commander_MHQReloc.sqf:169 mobilize, :293 re-deploy) ExecVM this and the AI toggles far more        often than the legacy human-only path. */     private ["_hqLockDeadline"];     _hqLockDeadline = time + (missionNamespace getVariable ["WFBE_C_HQSITE_LOCK_TIMEOUT", 20]);     waitUntil {sleep 0.25; (!(_logik getVariable ["wfbe_hqinuse", false])) || {time > _hqLockDeadline}};     if (_logik getVariable ["wfbe_hqinuse", false]) then {         diag_log ("AICOMSTAT|v1|HQSITE|" + str _sideText + "|" + str (round (time / 60)) + "|LOCK-FORCED|stale-wfbe_hqinuse-after=" + str (missionNamespace getVariable ["WFBE_C_HQSITE_LOCK_TIMEOUT", 20]) + "s");         ["WARNING", Format ["Construction_HQSite.sqf: [%1] force-claiming a stale wfbe_hqinuse lock after %2s timeout (prior toggle never released).", _sideText, (missionNamespace getVariable ["WFBE_C_HQSITE_LOCK_TIMEOUT", 20])]] Call WFBE_CO_FNC_LogContent;     };     _logik setVariable ["wfbe_hqinuse", true];  Notes on the AFTER form (all A2-OA-1.64-safe):   - Read changed from bare `getVariable "wfbe_hqinuse"` to defaulted `getVariable ["wfbe_hqinuse", false]`:     matches the exact defensive shape already used at MHQReloc.sqf:181, and avoids `!nil` (which throws in     A2-OA) on any code path where the var was never seeded. Behaviour-identical when the var is set.   - `sleep 0.25` added inside waitUntil so the c… [truncated — full sketch regenerable]

Soak test: Greppable: search the server RPT for AICOMSTAT|v1|HQSITE| with LOCK-FORCED -- it should NOT appear in a healthy soak (the lock always releases normally). If it appears, a real deadlock was caught and recovered: the matching WFBE_CO_FNC_LogContent WARNING ("force-claiming a stale wfbe_hqinuse lock after 20s timeout") names the side and confirms the MHQ toggle proceeded instead of hanging. Observable behaviour: over a multi-hour AICOM soak with WFBE_C_AICOM_MHQ_RELOCATE=1, every MHQReloc cycle should still log its normal AICOMSTAT|v1|MHQRELOC|...|MOBILIZED and ...|DEPLOYED lines (no si… [truncated — full sketch regenerable]

diff-governor-teamcurve-cap-vs-humans

Let the governor cap the low-pop team-flood (PC_LOW=15) when humans are present and the AI is already winning

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Teams.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED = 0 (Common/Init/Init_CommonConstants.sqf (AICOM team-curve block, near line 158 after WFBE_C_AICOM_TEAMS_PC_FULL))
  • new const WFBE_C_AICOM_DIFF_TEAMCAP_TRIGGER = 0.7 (Common/Init/Init_CommonConstants.sqf (same block))
  • new const WFBE_C_AICOM_DIFF_TEAMCAP_CUT = 3 (Common/Init/Init_CommonConstants.sqf (same block))
PLACEMENT: AI_Commander_Teams.sqf, insert a new block immediately AFTER the banking-valve block (current line 100, closing `};`) and BEFORE the dyntarget log at current line 102. This guarantees we clamp the FINAL _target (the valve at 94-100 can re-raise it), and the existing dyntarget log + PC-cleanup retirement (133-154) consume the clamped value with no extra wiring.  DEPENDENCY NOTE (load-bearing): the diff-governor scalar `wfbe_aicom_diff_mult` is a SIBLING candidate (diff-governor-killratio-townvel-cashscalar) that is NOT yet in the tree (grep: 0 hits). So we read it DEFENSIVELY with default 1.0. Until that item ships, `_dg` is always 1.0 (>= TRIGGER 0.7), so the clamp NEVER fires regardless of the flag — this item is independently shippable and inert by default. Also ship default WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED=0 so even after the scalar lands the clamp stays off until explicitly A/B-enabled.  --- Init_CommonConstants.sqf (after line 158, WFBE_C_AICOM_TEAMS_PC_FULL = 2;) ADD --- //--- diff-governor team-cap (claude-gaming B68): when an ENEMY human is present AND the difficulty //--- governor (wfbe_aicom_diff_mult, sibling item) has eased to its floor, down-clamp the founding //--- target so a winning AI eases off via FEWER/bigger teams. Strictly a down-clamp (flat-or-DOWN AI //--- count). Inert while the governor scalar is absent (default _dg=1.0 > TRIGGER). Rollback: ENABLED 0. if (isNil "WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED") then {WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED = 0};   //--- 1 = arm the clamp if (isNil "WFBE_C_AICOM_DIFF_TEAMCAP_TRIGGER") then {WFBE_C_AICOM_DIFF_TEAMCAP_TRIGGER = 0.7};  //--- clamp only when _dg <= this (governor ease-off floor) if (isNil "WFBE_C_AICOM_DIFF_TEAMCAP_CUT")     then {WFBE_C_AICOM_DIFF_TEAMCAP_CUT     = 3};    //--- teams removed from target, bounded below by PC_FULL (2)  --- AI_Commander_Teams.sqf: add to the private[] array at line 19 (or a fresh private line) --- private ["_dg","_enemyHumans","_capFloor","_preCapTarget"];  --- AI_Commander_Teams.sqf: INSERT after the valve block (after line 100 `};`), before line 102 --- BEFORE (lines 100-104, for anchor):         };      //--- Log only when the effective target changes (avoid RPT spam).     _lastDynTarget = _logik getVariable ["wfbe_aicom_dyntarget", _base];     if (_target > _base && {_target != _lastDynTarget}) then {  AFTER (new block inserted between the valve `};` and the log comment):         };      //--- B68 diff-governor team-cap (claude-gaming, gated WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED default-OFF).     //--- Only when (a) the flag is armed, (b) at l… [truncated — full sketch regenerable]

Soak test: Greppable diag_log marker: AICOMSTAT|v2|EVENT|<side>|<min>|TEAMCAP_GOVERNOR|from=<n>|to=<n>|dg=<x>|enemyHumans=<n>|pc=<n> — emitted only when the clamp actually lowers the target. Soak verification: (a) NEGATIVE soak — overnight pure-AI rotation must show ZERO TEAMCAP_GOVERNOR lines (no enemy humans => never fires); confirm with Select-String 'TEAMCAP_GOVERNOR' arma2oaserver.rpt returning nothing. (b) POSITIVE manual test — set WFBE_C_AICOM_DIFF_TEAMCAP_ENABLED=1, stub _logik setVariable ['wfbe_aicom_diff_mult',0.6] for one side, join 1 human on the OPPOSING side, and confirm exactly one… [truncated — full sketch regenerable]

doctrine-counter-enemy-pick

Bias the second side's doctrine to COUNTER the opponent's already-chosen doctrine

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_DOCTRINE_COUNTER_BIAS = 0.70 (Common/Init/Init_CommonConstants.sqf (AICOM block, insert after line 138 WFBE_C_AI_COMMANDER_UPGRADE_INTERVAL; nil-guarded so a lobby/Parameters.hpp override survives))
GOAL: when a side resolves its doctrine SECOND (the other side's wfbe_aicom_doctrine is already set), pick the OPPOSITE factory path with probability WFBE_C_AICOM_DOCTRINE_COUNTER_BIAS (0.70) so rounds get rock-paper-scissors texture instead of mirror HF-vs-HF / LF-vs-LF. If we resolve first (enemy var still nil) we fall through to the EXISTING 50/50 map-blind pick unchanged. No wait-chain on the enemy var (read-once, fall-through on nil) -> no publicVariable JIP-durability hazard; total AI count unaffected (only WHICH factory tree is rushed changes, not unit counts); no sim-gating, no antistack touch.  === EDIT 1: Init_CommonConstants.sqf (new constant, after line 138) === BEFORE (line 138): \tWFBE_C_AI_COMMANDER_UPGRADE_INTERVAL = 300; //--- B67 ... Rollback: 120. \tWFBE_C_AI_COMMANDER_TOWN_INTERVAL = 120; AFTER: \tWFBE_C_AI_COMMANDER_UPGRADE_INTERVAL = 300; //--- B67 ... Rollback: 120. \tif (isNil \"WFBE_C_AICOM_DOCTRINE_COUNTER_BIAS\") then {WFBE_C_AICOM_DOCTRINE_COUNTER_BIAS = 0.70}; //--- B68 doctrine-counter: P(second-resolved side picks the OPPOSITE of the enemy's already-chosen factory path). <1.0 keeps variety; 0 reverts to blind 50/50. nil-guarded so a lobby override survives. \tWFBE_C_AI_COMMANDER_TOWN_INTERVAL = 120;  === EDIT 2: AI_Commander.sqf doctrine pick (lines 26-29) === Add the two locals to the private[] line 15 ('private [..., \"_enemySide\",\"_enemyLogik\",\"_enemyDoc\"];' -- or wrap in a local private[] inside the block to avoid touching the long master list).  BEFORE (lines 26-29): \tif (isNil {_logik getVariable \"wfbe_aicom_doctrine\"}) then { \t\t_doctrine = if (random 1 > 0.5) then {\"HF\"} else {\"LF\"}; \t\t_logik setVariable [\"wfbe_aicom_doctrine\", _doctrine]; \t\t[\"INFORMATION\", Format [\"AI_Commander.sqf: [%1] doctrine picked: %2 (primary factory path).\", str _side, _doctrine]] Call WFBE_CO_FNC_AICOMLog;  AFTER: \tif (isNil {_logik getVariable \"wfbe_aicom_doctrine\"}) then { \t\tprivate [\"_enemySide\",\"_enemyLogik\",\"_enemyDoc\",\"_cBias\"]; \t\t//--- B68 doctrine-counter: read the OTHER side's doctrine (proven A2-safe enemy-side idiom, AI_Commander_Strategy.sqf:30).\n\t\t_enemySide = if (_side == west) then {east} else {west};\n\t\t_enemyDoc = \"\";\n\t\t_enemyLogik = (_enemySide) Call WFBE_CO_FNC_GetSideLogic;\n\t\tif (!isNil \"_enemyLogik\") then {_enemyDoc = _enemyLogik getVariable [\"wfbe_aicom_doctrine\", \"\"]}; //--- object 2-arg getVariable is A2-OA-safe (used at :36/:59/:61); GROUP 2-arg is the unreliable one.\n\t\t_cBias = missionNamespace getVariable [\"WFBE_C_AICOM_DOCTRINE_COUNTER_BIAS\", 0.70];\n… [truncated — full sketch regenerable]

Soak test: SOAK GREP: in the RPT, after a fresh round start (one MISSINIT _b68), expect at least one line matching 'doctrine COUNTER vs enemy' OR the machine-readable 'AICOMSTAT|v1|EVENT|...|DOCTRINE_COUNTER|enemy=HF|pick=LF' (or enemy=LF|pick=HF). OBSERVABLE: across N soak rounds the rate of MIRROR rounds (both sides log the same 'doctrine picked: X') should drop from ~50% toward ~15-20% (0.70 counter on the ~half of rounds where the race resolves sequentially). Cross-check the existing two 'doctrine picked: %2' lines per round: when one side logs DOCTRINE_COUNTER its pick should oppose the enemy's earl… [truncated — full sketch regenerable]

doctrine-map-faction-bias-pick

Replace blind 50/50 doctrine flip with a map+faction-biased weighted pick

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_DOCTRINE_HF_BIAS = 0.62 (Common/Init/Init_CommonConstants.sqf (AICOM constant block, after the TYPE_MIX tier defaults at lines 179-189, i.e. insert at ~line 190 before the A/B EXPERIMENT block))
PURPOSE: Chernarus is documented armour-friendly terrain. The doctrine is a one-time per-round pick that locks the WHOLE round (build order at AI_Commander.sqf:38 -> research PROGRAM 39-52 -> type-mix x1.5 nudge read at Teams.sqf:318 + AssignTypes.sqf:155). A pure 50/50 means ~half of rounds a side rushes a LIGHT/mech tree on heavy terrain with no recovery. Bias the coin toward HF (heavy) on Chernarus while KEEPING a real random pick (round-to-round variety preserved, not deterministic). One line + one constant. Only the PICK changes; everything downstream reads the stored wfbe_aicom_doctrine string unchanged, so no edits to Base.sqf / Teams.sqf / AssignTypes.sqf.\n\n=== CHANGE 1: Init_CommonConstants.sqf -- new nil-guarded constant ===\nInsert immediately AFTER line 189 (WFBE_C_AICOM_TYPE_MIX_MATURE_LATE), BEFORE the A/B EXPERIMENT block at line 190. Follow the exact established isNil-guard pattern of the surrounding block:\n\n  //--- B68: doctrine PICK bias. The per-round doctrine (HF=heavy/LF=light) is rolled once and locks\n  //--- the whole round (build order + research program + type-mix nudge). A pure 50/50 commits a side\n  //--- to a light tree on armour-friendly terrain ~half the time with no recovery. This is the probability\n  //--- of picking HF (heavy); Chernarus rewards armour, so default 0.62. Still a genuine random pick -\n  //--- round-to-round variety is preserved (not deterministic). 0.5 = old symmetric behaviour; per-map override.\n  if (isNil \"WFBE_C_AICOM_DOCTRINE_HF_BIAS\") then {WFBE_C_AICOM_DOCTRINE_HF_BIAS = 0.62};\n\n=== CHANGE 2: AI_Commander.sqf:27 -- biased pick ===\nThe pick is GUARDED by isNil {_logik getVariable \"wfbe_aicom_doctrine\"} (line 26), so it fires exactly once per side per round. Replace ONLY line 27.\n\nBEFORE (line 27):\n  _doctrine = if (random 1 > 0.5) then {\"HF\"} else {\"LF\"};\n\nAFTER (line 27):\n  _doctrine = if (random 1 < (missionNamespace getVariable [\"WFBE_C_AICOM_DOCTRINE_HF_BIAS\", 0.62])) then {\"HF\"} else {\"LF\"};\n\nNote the comparison flips from `random 1 > 0.5 -> HF` to `random 1 < BIAS -> HF`: a HIGHER BIAS now yields MORE HF picks (P(HF) = BIAS), which is the intended direction. The 2-arg getVariable default 0.62 means the line is self-safe even if the constant file did not load (matches the existing 2-arg getVariable [..., \"LF\"] fallback style already used at Base.sqf:63 and Teams.sqf:318).\n\n=== OPTIONAL diag (recommended for the soak test_hook) ===\nThe existing AICOMLog at AI_Commander.sqf:29 already prints the picked doctrine. Extend its Format string to also print the bias … [truncated — full sketch regenerable]

Soak test: Soak grep on the server RPT: after each round start, every present AI side logs exactly one line matching AI_Commander.sqf: \[.*\] doctrine picked: (HF|LF) (HF bias 0.62) (with the optional diag edit). Greppable assertion across many soak rounds: count HF vs LF lines and confirm the HF share trends toward ~62% (e.g. Select-String 'doctrine picked: HF' count / total doctrine-pick lines ~= 0.62 over a large N), proving the bias is live and not stuck at 50/50 or pinned to one value. Observable in-game behaviour: on Chernarus, AI sides should noticeably more often rush the HEAVY factory (Base.… [truncated — full sketch regenerable]

enstr-contact-recency-decay

Posture strength read: weight enemy maneuver strength by HQ-proximity so a forward enemy reads as the real threat it is (cheap fog-of-engagement model)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_ENSTR_ENGAGE_WEIGHT = 0 (Common/Init/Init_CommonConstants.sqf (AICOM strategy constants block, beside WFBE_C_AICOM_STR_LONE_ALIVE / WFBE_C_AICOM_LASTSTAND_RATIO). 0 = exact current flat-sum behaviour (ships INERT). When >0 (e.g. 0.5) far-rear enemy teams count at this fraction. Range 0..1; values >=1 make it a no-op.)
  • new const WFBE_C_AICOM_ENSTR_ENGAGE_RADIUS = 4000 (Common/Init/Init_CommonConstants.sqf (same AICOM block). Metres from the AI's OWN HQ (_myHQ) within which an enemy team is 'forward/engaged' and counts at full weight; beyond it, counts at WEIGHT fraction. Only consulted when WEIGHT>0.)
GOAL: replace the flat _enStr body-count (lines 62-63) with an OPTIONAL HQ-proximity-weighted sum. _myHQ (line 46) is the AI's OWN HQ; a forward/engaged enemy = team whose leader is CLOSE to our HQ, so it counts full; a rear-idling enemy counts at the reduced WEIGHT fraction. Ships inert (WEIGHT default 0 => identical to today).  --- BEFORE (AI_Commander_Strategy.sqf:62-63) ---   _enStr = 0;   { if (!isNull _x) then {_enStr = _enStr + ({alive _x} count (units _x))} } forEach (_enemyLogik getVariable ["wfbe_teams", []]);  --- AFTER ---   //--- enstr-contact-recency-decay (claude-gaming): engagement-weighted enemy strength.   //--- WEIGHT=0 => exact flat sum (default, inert). WEIGHT>0 => rear (far from OUR HQ) enemy teams   //--- count at the WEIGHT fraction so a forward enemy reads as the real threat (cheap fog model).   private ["_enW","_enRad","_enLdr","_enA","_enRaw"];   _enW   = missionNamespace getVariable ["WFBE_C_AICOM_ENSTR_ENGAGE_WEIGHT", 0];   _enRad = missionNamespace getVariable ["WFBE_C_AICOM_ENSTR_ENGAGE_RADIUS", 4000];   _enStr = 0; _enRaw = 0;   {       if (!isNull _x) then {           _enA = {alive _x} count (units _x);           if (_enA > 0) then {               _enRaw = _enRaw + _enA;                       //--- preserved raw count for telemetry parity               if (_enW <= 0) then {                   _enStr = _enStr + _enA;                    //--- flat path = today's behaviour exactly               } else {                   _enLdr = leader _x;                   if (!isNull _enLdr && {!isNull _myHQ} && {(_enLdr distance _myHQ) <= _enRad}) then {                       _enStr = _enStr + _enA;                //--- forward / engaged -> full weight                   } else {                       _enStr = _enStr + (_enA * _enW);       //--- rear / idle -> reduced weight                   };               };           };       };   } forEach (_enemyLogik getVariable ["wfbe_teams", []]);   _enStr = round _enStr;                                     //--- keep integer for ratio gates / telemetry  NOTES ON A2-OA SAFETY: - Commands used: missionNamespace getVariable [name,default], alive, count, units, leader, isNull, distance, round, forEach, multiply. All A2-OA 1.64 core. No isEqualType/isEqualTo/params/typed-params. - _myHQ already fetched line 46 and is guarded with !isNull before the distance test (getPos/distance on null would throw). _enLdr null-guarded too. - No publicVariable, no wait-chain, no team move/retask, no AI count change -> total AI flat (read-only metric). No sim-gating, no antistack touched. - _enRaw is optional: if you… [truncated — full sketch regenerable]

Soak test: Add the raw-vs-weighted delta to the existing POSTURE telemetry so a soak can confirm the weighting fires and measure its swing without a redeploy. In the diag_log at AI_Commander_Strategy.sqf:577 append: + "|enRaw=" + str _enRaw + "|enW=" + str _enW (greppable line stays AICOMSTAT|v1|POSTURE). Soak greps: rg "AICOMSTAT|v1|POSTURE" -> compare enStr vs enRaw; with WEIGHT=0 they MUST be equal every tick (proves inert ship). With WEIGHT=0.5, enStr4000m from our HQ. Observable behaviour when enabled: the dominant-but-passive WEST-6-vs-EAST-1 stall … [truncated — full sketch regenerable]

enstr-symmetric-remnant-exclusion

Apply the same refit/remnant exclusions to enemy strength as to own strength (remove the anti-self DEFEND bias)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

GOAL: symmetrize _enStr with the _myStr exclusion block so refit/remnant enemy bodies stop inflating _enStr and biasing every compare (last-stand :66, HQ-strike :513/:515, DEFEND posture :573) toward reading our side as weaker.  NO new constants. Reuse the already-resolved _loneAlive (:47), _loneFar (:48) locals and existing WFBE_C_AICOM_STR_LONE_ALIVE / WFBE_C_AICOM_STR_LONE_FARHQ. Resolve the enemy HQ locally (the WEST/EAST _enemyHQ at :508 is computed LATER, so we resolve our own _enHQ here).  STEP 1 - declare two extra locals. Edit the private[] on line 45.   BEFORE:     private ["_myHQ","_loneAlive","_loneFar","_tAlive","_rf","_isRemnant"];   AFTER:     private ["_myHQ","_enHQ","_loneAlive","_loneFar","_tAlive","_rf","_isRemnant"];  STEP 2 - replace the raw _enStr scan. Edit lines 62-63.   BEFORE (:62-63):     _enStr = 0;     { if (!isNull _x) then {_enStr = _enStr + ({alive _x} count (units _x))} } forEach (_enemyLogik getVariable ["wfbe_teams", []]);   AFTER:     //--- B68 SYMMETRY (claude-gaming 2026-06-21): apply the SAME refit/remnant exclusions to the     //--- enemy as to _myStr (:50-61), so scattered enemy refit/lone-survivor bodies do not inflate     //--- _enStr and falsely read OUR side as weaker (DEFEND :573 / last-stand :66 / HQ-strike :513,515).     //--- A2-OA: plain get + isNil for the GROUP refit var; _enHQ may be null (alive HQ not required for a     //--- distance test, but isNull-guard like the _myStr block). _enemyLogik already non-nil past :31/:33.     _enHQ = (_enemySide) Call WFBE_CO_FNC_GetSideHQ;     _enStr = 0;     {         if (!isNull _x) then {             _tAlive = {alive _x} count (units _x);             if (_tAlive > 0) then {                 _isRemnant = false;                 _rf = _x getVariable "wfbe_aicom_refit";                 if (!isNil "_rf" && {_rf}) then {_isRemnant = true};                 if (!_isRemnant && {_tAlive < _loneAlive} && {_loneFar > 0} && {!isNull (leader _x)} && {!isNull _enHQ} && {((leader _x) distance _enHQ) > _loneFar}) then {_isRemnant = true};                 if (!_isRemnant) then {_enStr = _enStr + _tAlive};             };         };     } forEach (_enemyLogik getVariable ["wfbe_teams", []]);  This is a byte-for-byte mirror of :50-61 with _myStr->_enStr, _teams->(_enemyLogik getVariable ['wfbe_teams',[]]), _myHQ->_enHQ. Reuses _tAlive/_rf/_isRemnant scratch locals already declared. All commands (getVariable plain/[name,default], isNil "_str", count, units, alive, leader, distance, isNull) are A2-OA-1.64-safe; no isEqualType/params/select-on-non-array. No unit is moved - selection math … [truncated — full sketch regenerable]

Soak test: Grep the server RPT for the existing POSTURE/STALL telemetry already emitted this tick (no new diag_log needed - the change feeds these): "AICOMSTAT|v1|POSTURE" and "AICOMSTAT|v1|STALL". OBSERVABLE PASS CRITERIA over a soak: (a) the per-side enStr= field in the POSTURE line drops (enemy remnants no longer counted) and the myStr/enStr numbers for the two sides become near-mirror images of each other (WEST's enStr ~= EAST's myStr) - they were asymmetric before because only _myStr was filtered; (b) the count of STALL lines (territorial leader holding >=2x towns yet posture!=PRESS) FALLS over the … [truncated — full sketch regenerable]

fill-to-floor-composition-aware-pad

Make Produce FILL-TO-FLOOR pad with a role mix (AT/AR/medic) instead of N copies of the last rifleman, so floor-padded teams aren't generic-rifleman flood

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Produce.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_PAD_ROLE_MIX = 0 (Common/Init/Init_CommonConstants.sqf (AICOM tunables block, alongside WFBE_C_AICOM_PRODUCE_BATCH / CRITICAL_STRENGTH at lines ~344-347; add as if (isNil "WFBE_C_AICOM_PAD_ROLE_MIX") then {WFBE_C_AICOM_PAD_ROLE_MIX = 0}; so it ships dark for a clean A/B, default 0 = exact current last-rifleman behaviour))
  • new const WFBE_C_AICOM_PAD_ROLE_MAX_AT = 1 (Common/Init/Init_CommonConstants.sqf (same AICOM block; per-team cap on AT/AR pad bodies so the bulk stays cheap riflemen))
  • new const WFBE_C_AICOM_PAD_ROLE_MAX_MEDIC = 1 (Common/Init/Init_CommonConstants.sqf (same AICOM block; per-team cap on medic pad bodies))
GOAL: at the FILL-TO-FLOOR pad point (AI_Commander_Produce.sqf:227-229), when WFBE_C_AICOM_PAD_ROLE_MIX==1, prefer buying ONE AT/auto-rifleman + ONE medic the team currently lacks (resolved from the side's own factory unit-pools, faction-agnostic) before falling back to the existing last-rifleman pad. Default 0 = byte-for-byte current behaviour.  === A) NEW CONSTANTS (Init_CommonConstants.sqf, AICOM block ~line 347, after WFBE_C_AICOM_PRODUCE_BATCH) ===   if (isNil "WFBE_C_AICOM_PAD_ROLE_MIX")       then {WFBE_C_AICOM_PAD_ROLE_MIX = 0};       //--- 1 = pad floor with an AT/AR + medic mix before riflemen; 0 = exact current last-rifleman pad.   if (isNil "WFBE_C_AICOM_PAD_ROLE_MAX_AT")    then {WFBE_C_AICOM_PAD_ROLE_MAX_AT = 1};    //--- max AT/AR pad bodies per team this fill.   if (isNil "WFBE_C_AICOM_PAD_ROLE_MAX_MEDIC") then {WFBE_C_AICOM_PAD_ROLE_MAX_MEDIC = 1}; //--- max medic pad bodies per team this fill.  === B) PRODUCE: declare the per-team pad tallies (add to the private[] at :15 and reset inside the `if (_cur < _want)` block at :210, next to `_ordered = []`) ===   // add names to the file-top private[]: "_padAT","_padMed","_roleCls","_padMaxAT","_padMaxMed"   // at :211 (after `_ordered = [];`):   _padAT  = 0;   //--- AT/AR pad bodies bought this fill   _padMed = 0;   //--- medic pad bodies bought this fill   _padMaxAT  = missionNamespace getVariable ["WFBE_C_AICOM_PAD_ROLE_MAX_AT", 1];   _padMaxMed = missionNamespace getVariable ["WFBE_C_AICOM_PAD_ROLE_MAX_MEDIC", 1];  === C) ROLE RESOLVER helper (A2-OA-safe; place it just BEFORE the `if (_toBuild == "")` pad block, ~:226). It scans the side's Man-class pool (same WFBE_%1%2 lookup already at :236) for a class matching a role, config-driven, fully isNil/isClass guarded. ===    //--- FILL-TO-FLOOR ROLE MIX (B68): pick a pooled Man-class for the requested role the team   //--- currently lacks. role 0=AT/AR (a Man whose config lists an AT launcher OR a machinegunner),   //--- role 1=medic (attendant==1). A2-OA-safe: getNumber/getText/configFile/count/isKindOf only.   //--- Returns "" if no eligible class is found (caller falls through to the rifleman pad).   _pickRole = {       private ["_role","_pool","_pick","_cfg","_isAttendant","_hasLauncher"];       _role = _this;       _pick = "";       //--- merge every Man-pool the side can build (Barracks/Light/...); _facDefs already side-scoped.       _pool = [];       { _pool = _pool + (missionNamespace getVariable [Format ["WFBE_%1%2", _sideText, (_x select 1)], []]) } forEach _facDefs;       {           _cfg = configFile >> "CfgVehicles" >> _x;       … [truncated — full sketch regenerable]

Soak test: Add a diag line in the ROLE-MIX branch right after a role class is chosen (so it only fires when the feature is ON and actually picks a non-rifleman), greppable in the server RPT: ["INFORMATION", Format ["AI_Commander_Produce.sqf: [%1] team [%2] ROLE-MIX pad -> [%3] (padAT=%4 padMed=%5).", _sideText, _team, _toBuild, _padAT, padMed]] Call WFBE_CO_FNC_AICOMLog; SOAK OBSERVABLES: (1) grep server RPT for ROLE-MIX pad -> — expect a handful per side per few minutes once flipped to 1, each naming a distinct AT/medic class (NOT the basic rifleman). (2) Confirm ZERO occurrences when WFBE_C_AICOM… [truncated — full sketch regenerable]

forward-town-defensive-statics

Manned base defenses only ring the HQ; captured/front towns get zero AI-placed static emplacements

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Base.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_FWD_DEFENSES_PER_TOWN = 2 (Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after line 149 WFBE_C_AI_COMMANDER_DEFENSES_MAX))
  • new const WFBE_C_AICOM_FWD_DEFENSES_MAX = 4 (Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after line 149 WFBE_C_AI_COMMANDER_DEFENSES_MAX))
GOAL: after the existing HQ-defense block (AI_Commander_Base.sqf:457-488) add a small, bounded "forward town" defense pass. Same gates as HQ defenses (built Barracks + AI funds), reuses the exact same primitives already proven in this file: _findBuildPos is NOT reused (it rings the HQ); instead place near the FRONT town using getPos + a small outward ring, the atan2 outward-facing guard from :481, the wfbe_aicom_targets[0] front resolution from the factory-rally block (:389-404), and the ConstructDefense call shape from :482. Per-town tag (wfbe_aicom_fwd_def_count) + a side total tag (wfbe_aicom_fwd_defenses on _logik) keep object count hard-bounded.  == STEP 1: constants (Init_CommonConstants.sqf, AICOM block, after line 149) == BEFORE (line 149):   WFBE_C_AI_COMMANDER_DEFENSES_MAX = 4;      //--- V0.2: manned base statics the AI places around its HQ. AFTER (insert two lines below it):   WFBE_C_AI_COMMANDER_DEFENSES_MAX = 4;      //--- V0.2: manned base statics the AI places around its HQ.   WFBE_C_AICOM_FWD_DEFENSES_PER_TOWN = 2;    //--- B68: max AICOM static pods placed at one held FRONT town (tagged on the town, cap so it never runs away).   WFBE_C_AICOM_FWD_DEFENSES_MAX = 4;         //--- B68: hard side-wide cap on forward town statics so total object/group count stays bounded (FPS-safe). 0 disables the whole pass. Rollback: 0.  == STEP 2: declare the new locals (AI_Commander_Base.sqf:13 private[...] list) == Append to the private array (A2-OA quoting, same as the rest of the list):   ..."_fwdMax","_fwdSideCount","_fwdTargets","_fwdTarget","_fwdTgtPos","_fwdMyID","_fwdBest","_fwdBestD","_fwdD","_fwdTownPos","_fwdPerCap","_fwdTownCount","_fwdEHQ","_fwdEHQPos","_fwdDir","_fwdDx","_fwdDy","_fwdAng","_fwdPos","_fwdClass","_fwdData","_fwdPrice","_fwdFunds"];  == STEP 3: the new block, inserted right AFTER line 488 (end of HQ-defense block, before the    artillery block at :490). Pseudo-diff (all A2-OA-safe: getPos/distance/count/select/typeName/    isKindOf-free, atan2, isNil-guards, forEach): ==  + //--- 3b) B68 FORWARD-TOWN DEFENSE: place ONE static at the held front town per cadence, capped. + //--- Same gates as HQ defenses above (Barracks present + AI funds). Tag-and-cap so object count + //--- stays bounded; never placed by sim-gating and never deleted here (re-placed, never freezes AI). + _fwdMax = missionNamespace getVariable ["WFBE_C_AICOM_FWD_DEFENSES_MAX", 4]; + _fwdSideCount = _logik getVariable ["wfbe_aicom_fwd_defenses", 0]; + if (_fwdMax > 0 && {_fwdSideCount < _fwdMax}) then { +     //--- Barracks gate (reuse the same crewing-source chec… [truncated — full sketch regenerable]

Soak test: Greppable diag_log marker (in the RPT): "AICOMSTAT|...|FWD_DEFENSE_PLACED|town=...|n=X/4|cls=...". Soak assertions: (a) grep the RPT for FWD_DEFENSE_PLACED — expect >=1 per side once each side holds a Barracks + a contested front town, and NEVER more than WFBE_C_AICOM_FWD_DEFENSES_MAX(=4) lines per side per round (the n=X/4 counter must be monotonic and stop at 4). (b) per-town: no town should accumulate more than WFBE_C_AICOM_FWD_DEFENSES_PER_TOWN(=2) placements (count FWD_DEFENSE_PLACED lines grouped by the town= token). (c) observable in-game: a manned MG/AA pod stan… [truncated — full sketch regenerable]

fwd-resupply-truck-dispatch-to-spearhead

Dispatch a single crewed ammo/repair truck from HQ to the spearhead concentration (real forward resupply) reusing the proven W17 convoy lifecycle

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_FWD_RESUPPLY_ENABLE = 0 (Init_CommonConstants.sqf (AICOM SELF-SERVICE block, right after line 343 WFBE_C_AICOM_SVC_TRIGGER_DIST). Use the nil-guarded form: if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_ENABLE") then {WFBE_C_AICOM_FWD_RESUPPLY_ENABLE = 0}; // default-OFF, ships dark for A/B soak)
  • new const WFBE_C_AICOM_FWD_RESUPPLY_INTERVAL = 120 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_INTERVAL") then {WFBE_C_AICOM_FWD_RESUPPLY_INTERVAL = 120}; // s throttle for the dispatcher tick)
  • new const WFBE_C_AICOM_FWD_RESUPPLY_BAND = 1500 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_BAND") then {WFBE_C_AICOM_FWD_RESUPPLY_BAND = 1500}; // m: a team counts as 'at the spearhead' if within this of the primary target town)
  • new const WFBE_C_AICOM_FWD_RESUPPLY_MINTEAMS = 2 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_MINTEAMS") then {WFBE_C_AICOM_FWD_RESUPPLY_MINTEAMS = 2}; // require >= this many same-side teams in the band (no truck if no concentration))
  • new const WFBE_C_AICOM_FWD_RESUPPLY_STANDOFF = 250 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_STANDOFF") then {WFBE_C_AICOM_FWD_RESUPPLY_STANDOFF = 250}; // m: truck drops short of the spearhead, biased back toward own HQ)
  • new const WFBE_C_AICOM_FWD_RESUPPLY_SERVE_RANGE = 150 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_SERVE_RANGE") then {WFBE_C_AICOM_FWD_RESUPPLY_SERVE_RANGE = 150}; // m: on arrival, rearm/repair/heal every same-side team leader within this of the truck)
  • new const WFBE_C_AICOM_FWD_RESUPPLY_TIMEOUT = 600 (Init_CommonConstants.sqf (same block). if (isNil "WFBE_C_AICOM_FWD_RESUPPLY_TIMEOUT") then {WFBE_C_AICOM_FWD_RESUPPLY_TIMEOUT = 600}; // s: hard watcher timeout that ALWAYS deletes the truck and clears the single-truck flag (PV/desync can never pin it))
GOAL: a server-side forward-resupply dispatcher that sends ONE crewed faction supply truck from HQ to a standoff point behind the side's primary spearhead, then rearms/repairs/heals the teams massed there - replacing the per-team break-off detour (Common_AICOMServiceTick.sqf:122-144). Lifted almost verbatim from the W17 Supply Convoy lifecycle (AI_Commander_Wildcard.sqf:965-1031), which already proves every primitive in A2-OA: crewed CreateVehicle truck, AIPatrol + AWARE/YELLOW, waitUntil arrival<80m with a 600s timeout, self-clean of crew+truck+group.  KEY CORRECTION vs the finding's prose: wfbe_aicom_targets is an array of TOWN OBJECTS, not positions (AI_Commander_Strategy.sqf:196 _targets+[_bestTown], stored :370). So the dispatcher must do getPos on (targets select 0), exactly like W4 does (AI_Commander_Wildcard.sqf:591-605). The finding's "_logik getVariable 'wfbe_aicom_targets' select 0" yields an OBJECT - treat accordingly.  --- HOOK SITE: Server/AI/Commander/AI_Commander.sqf supervisor loop --- _side(:17), _logik(:18) are file-scoped; the full-command gate is the existing _humanCmd flag (:101-110, "full" state == !_humanCmd). _sideID is NOT defined here so the dispatcher derives it. Add a throttle var beside the others and an interval-gated Call, mirroring _ltMHQReloc (:178-180).    BEFORE (AI_Commander.sqf:72):     _ltTypes = 0; _ltUp = 0; _ltTown = 0; _ltProd = 0; _ltBase = 0; _ltTeams = 0; _ltStrat = 0; _ltStat = -301; _ltBrief = 0; _ltMHQReloc = 0;   AFTER:     _ltTypes = 0; _ltUp = 0; _ltTown = 0; _ltProd = 0; _ltBase = 0; _ltTeams = 0; _ltStrat = 0; _ltStat = -301; _ltBrief = 0; _ltMHQReloc = 0; _ltFwdResup = 0;  Add the dispatch call INSIDE the full-command branch (where _humanCmd is false). Place it next to the MHQReloc block (AI_Commander.sqf ~:178). It self-gates on the enable switch + interval + full-command:    //--- FWD-RESUPPLY (default OFF): one crewed ammo/repair truck HQ -> standoff behind the spearhead.   if ((missionNamespace getVariable ["WFBE_C_AICOM_FWD_RESUPPLY_ENABLE", 0]) > 0       && {!_humanCmd}       && {time - _ltFwdResup > (missionNamespace getVariable ["WFBE_C_AICOM_FWD_RESUPPLY_INTERVAL", 120])}) then {       [_side, _logik] Call WFBE_SE_FNC_AICOMFwdResupply;       _ltFwdResup = time;   };  The body can live inline as a private function defined once before the while-loop (preferred, keeps the diff in ONE file), OR as a sibling .sqf preprocessed by description.ext. Inline-function body (A2-OA-safe; mirrors W17 idioms exactly):    WFBE_SE_FNC_AICOMFwdResupply = {       private ["_side","_logik","_sideID","_sideText",… [truncated — full sketch regenerable]

Soak test: SOAK GREP: enable WFBE_C_AICOM_FWD_RESUPPLY_ENABLE=1 and watch the server RPT for two new diag_log lines: "AICOMSTAT|v1|EVENT|||FWD_RESUPPLY_DISPATCH|teams=N|town=..." (a truck launched when N>=MINTEAMS were massed at the spearhead and at least one was hurt) followed within ~timeout by "AICOMSTAT|v1|EVENT|||FWD_RESUPPLY_DELIVERED|served=M" (M same-side teams healed/rearmed on arrival). HEALTH CHECKS: (i) DISPATCH and DELIVERED lines should pair up 1:1 per side over a round - a DISPATCH with no DELIVERED within ~timeout+30s means the truck died or timed out (acceptable, bu… [truncated — full sketch regenerable]

graceful-withdrawal-evaluator-replaces-hc-blindspot

Strategy-side per-HC-team graceful-withdrawal evaluator (the cull only ever sees server-local teams; HC teams get nothing)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_WITHDRAW_FRAC = 0.34 (Init_CommonConstants.sqf AICOM block (add beside the LASTSTAND/STR_LONE knobs at lines ~277-280). isNil-guarded line: if (isNil "WFBE_C_AICOM_WITHDRAW_FRAC") then {WFBE_C_AICOM_WITHDRAW_FRAC = 0.34}; an HC team whose live alive has fallen below this fraction of its founding peak is pulled to a rally/refit. 0 disables the whole pass.)
  • new const WFBE_C_AICOM_WITHDRAW_HOME_RANGE = 900 (Init_CommonConstants.sqf AICOM block (same group). if (isNil "WFBE_C_AICOM_WITHDRAW_HOME_RANGE") then {WFBE_C_AICOM_WITHDRAW_HOME_RANGE = 900}; do NOT withdraw a team already within this many metres of its own HQ (it is effectively home/refitting already; avoids thrash with the Produce REFILL-AT-BASE path). Mirrors WFBE_C_AICOM_RETREAT_HOME_RANGE=800.)
CONTEXT (anchored): AI_Commander_Strategy.sqf runs per side every STRATEGY_INTERVAL. After the last-stand block it does `if (_lastStand) exitWith {};` (:99), then iterates `_teams` for the WEDGE-WATCHDOG (:460-505). The new pass is a SECOND `forEach _teams` inserted immediately AFTER line 505, sharing the same non-laststand region. The HC order idiom is `wfbe_aicom_order = [seq, mode, pos]`; the executor in Common_RunCommanderTeam.sqf acts ONLY on a fresh `_seq != _lastSeq` (:484) and already handles mode "DEFENSE" (:668), so re-broadcasting the same seq is a no-op (idempotent vs candidate-1 double-fire). KEY FACT verified by grep: `wfbe_aicom_peakalive` does NOT exist anywhere in the tree, so this pass STAMPS it itself, isNil-guarded (default = current alive => no withdrawal until a later cycle once a real peak is on record). That makes the pass self-contained (no dependency on the unshipped rally-break-off candidate).  ================= BEFORE (AI_Commander_Strategy.sqf, end of wedge-watchdog) ================= 504	    }; 505	} forEach _teams; 506 507	//--- 3) HQ HUNT: strike when clearly winning; ... 508	_enemyHQ = (_enemySide) Call WFBE_CO_FNC_GetSideHQ;  ================= AFTER (insert a new block between line 505 and the HQ-HUNT comment) ======== 505	} forEach _teams; 506 507	//--- 2b) GRACEFUL WITHDRAWAL for HC teams (graceful-withdrawal-evaluator). The Produce 508	//---     retreat/cull (AI_Commander_Produce.sqf:69) is gated !isPlayer && !wfbe_aicom_hc, 509	//---     so it NEVER runs for HC-resident teams = ~100% of live teams in this 2-HC mission. 510	//---     Catch a bleeding-out HC assault team on the MARCH/in-camp and pull it to HQ to 511	//---     refit BEFORE it is wiped (RunCommanderTeam.sqf:1070), without culling or founding a 512	//---     replacement (keeps total AI flat-or-down). A2-OA-safe: plain get + isNil for GROUP 513	//---     vars; classname-literal isKindOf for the MBT/attack-heli exemption; reuse the proven 514	//---     [seq,"DEFENSE",pos] HC order + SetTeamMoveMode "defense" idiom (same as :482-489). 515	_wdFrac = missionNamespace getVariable ["WFBE_C_AICOM_WITHDRAW_FRAC", 0.34]; 516	if (_wdFrac > 0) then { 517	  _wdHomeR = missionNamespace getVariable ["WFBE_C_AICOM_WITHDRAW_HOME_RANGE", 900]; 518	  _wdHQ = (_side) Call WFBE_CO_FNC_GetSideHQ; 519	  { 520	    _gTeam = _x; 521	    if (!isNull _gTeam 522	        && {(_gTeam getVariable ["wfbe_aicom_hc", false])}            //--- HC teams only (the blindspot) 523	        && {!isPlayer (leader _gTeam)}) then { 524	      _gLdr = leader _gTeam; 525	      _gAlive = {alive _x} coun… [truncated — full sketch regenerable]

Soak test: SOAK GREP: each withdrawal emits a machine-parseable diag_log line AICOMSTAT|v2|EVENT|||GRACEFUL_WITHDRAW|team=|alive=|peak=|frac= Grep the server RPT for GRACEFUL_WITHDRAW (parallel to the existing WEDGE_RELEASE line at Strategy:492). PASS criteria over a soak: (1) count > 0 on AI-vs-AI rounds with real attrition (proves HC teams now get a withdrawal path the Produce cull never gave them); (2) withdrawn teams reappear at the front at full strength within ~1-2 strategy cycles (cross-check the human-readable GRACEFUL-WITHDRAW ... -> HQ refit AICOMLog line follow… [truncated — full sketch regenerable]

hctopup-centralize-missing-constants

Define the HCTopUp/merge tunables centrally in Init_CommonConstants so the refill knobs are param-tunable and documented

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_HCTopUp.DRAFT.sqf

  • new const WFBE_C_AICOM_HC_TOPUP_ENABLE = 0 (Common/Init/Init_CommonConstants.sqf (AICOM block, after WFBE_C_AICOM_TEAM_FOUND_SIZE at line 315))
  • new const WFBE_C_AICOM_HC_TOPUP_FRAC = 0.6 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
  • new const WFBE_C_AICOM_HC_TOPUP_RANGE = 900 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
  • new const WFBE_C_AICOM_HC_TOPUP_INTERVAL = 45 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
  • new const WFBE_C_AICOM_HC_MERGE_ENABLE = 0 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
  • new const WFBE_C_AICOM_HC_MERGE_FRAC = 0.6 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
  • new const WFBE_C_AICOM_HC_MERGE_RANGE = 300 (Common/Init/Init_CommonConstants.sqf (AICOM block, after line 315))
GOAL: Make the HC top-up / merge knobs FIRST-CLASS, isNil-guarded, param-tunable constants in the AICOM block of Init_CommonConstants.sqf -- exactly like the sibling TEAM_SIZE_MIN/MAX/FOUND_SIZE trio at lines 306-315 -- instead of inline defaults buried in a DRAFT worker. PURE-ADDITIVE: defaults are byte-for-byte no-ops vs the current inline literals, and the two ENABLE flags ship OFF (0). Zero live behaviour change. ENABLE flags use 0/1 ints (not `false`) to match the AICOM block's existing numeric-flag convention (e.g. WFBE_C_AI_COMMANDER_HYBRID_REFILL=1 at line 295).  ========================================================================= PART 1 -- Init_CommonConstants.sqf : INSERT the central definitions ========================================================================= File: Common/Init/Init_CommonConstants.sqf Anchor: AFTER line 315 (WFBE_C_AICOM_TEAM_FOUND_SIZE) and BEFORE line 316         (the "//--- RELIEF HOLD:" comment). Same isNil-guarded one-per-line idiom.    --- BEFORE (lines 315-316) ---   if (isNil "WFBE_C_AICOM_TEAM_FOUND_SIZE") then {WFBE_C_AICOM_TEAM_FOUND_SIZE = 10}; //--- DRAFT founding target (>=MIN, <=MAX).   //--- RELIEF HOLD: a team diverted to defend a town holds for this long, then - if the town is    --- AFTER (insert this block between them) ---   if (isNil "WFBE_C_AICOM_TEAM_FOUND_SIZE") then {WFBE_C_AICOM_TEAM_FOUND_SIZE = 10}; //--- DRAFT founding target (>=MIN, <=MAX).   //--- HC-TEAM REFILL / MERGE surface (claude-gaming, B68). Centralises the knobs the   //--- HCTopUp.DRAFT worker reads inline. ALL ship as no-ops: defaults equal the inline   //--- literals, and both ENABLE flags are OFF (0) pending a wired consumer + soak. The   //--- consumer (AI_Commander_HCTopUp) refills HC-founded teams that have dribbled below   //--- the 8-12 band; merge folds two starved neighbours into one. NEVER freezes a team   //--- (add/join units only; live waypoints untouched). A2-OA 1.64 safe.   if (isNil "WFBE_C_AICOM_HC_TOPUP_ENABLE")   then {WFBE_C_AICOM_HC_TOPUP_ENABLE   = 0};    //--- 1=run the HC-team top-up pass; 0=inert (default OFF, pending soak).   if (isNil "WFBE_C_AICOM_HC_TOPUP_FRAC")     then {WFBE_C_AICOM_HC_TOPUP_FRAC     = 0.6};  //--- reinforce only teams below FRAC*TEAM_SIZE_MIN alive (0.6*8 -> floor 5).   if (isNil "WFBE_C_AICOM_HC_TOPUP_RANGE")    then {WFBE_C_AICOM_HC_TOPUP_RANGE    = 900};  //--- m: team must be within this of a friendly supply town to refill (rear/front, not open field).   if (isNil "WFBE_C_AICOM_HC_TOPUP_INTERVAL") then {WFBE_C_AICOM_HC_TOPUP_INTERVAL = 45};   //--- s: supervisor cadence… [truncated — full sketch regenerable]

Soak test: Boot-smoke: the build must still MISSINIT with ErrInExpr=0 (no parse error from the inserted block). Knob-presence check, run in debug console or a one-shot diag after init: diag_log format ["AICOM_HCKNOBS|en=%1|frac=%2|rng=%3|int=%4|men=%5|mfrac=%6|mrng=%7", WFBE_C_AICOM_HC_TOPUP_ENABLE, WFBE_C_AICOM_HC_TOPUP_FRAC, WFBE_C_AICOM_HC_TOPUP_RANGE, WFBE_C_AICOM_HC_TOPUP_INTERVAL, WFBE_C_AICOM_HC_MERGE_ENABLE, WFBE_C_AICOM_HC_MERGE_FRAC, WFBE_C_AICOM_HC_MERGE_RANGE] -- expect AICOM_HCKNOBS|en=0|frac=0.6|rng=900|int=45|men=0|mfrac=0.6|mrng=300 (all seven defined, both enables 0). Behaviour soak: bec… [truncated — full sketch regenerable]

hctopup-same-hc-merge-depleted-pairs

Add a same-HC depleted-team MERGE pass (fewer+bigger, group-count DOWN) as the FPS-preferred alternative to refill

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_HCTopUp.DRAFT.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Client/PVFunctions/HandleSpecial.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf

  • new const WFBE_C_AICOM_HC_MERGE_ENABLE = false (Common/Init/Init_CommonConstants.sqf (AICOM block, insert after WFBE_C_AICOM_TEAM_SIZE_MAX at line 307))
  • new const WFBE_C_AICOM_HC_MERGE_FRAC = 0.6 (Common/Init/Init_CommonConstants.sqf (AICOM block, after the ENABLE line))
  • new const WFBE_C_AICOM_HC_MERGE_RANGE = 300 (Common/Init/Init_CommonConstants.sqf (AICOM block, after the FRAC line))
GOAL: when two HC-resident infantry teams on the SAME side have both attrited below floor and sit close together out of combat, joinSilent the survivors of B into A (one ~7-man group, one whole group slot freed). Strictly better than refill for the FPS-bound server (total AI flat, group count -1). Hard catch: in this 2-HC mission A and B may be LOCAL on DIFFERENT HCs and joinSilent across machines is unreliable in OA, so the merge MUST run only where BOTH leaders are local to the SAME HC. Solution mirrors the existing dispatch fabric exactly: server PICKS the pair, broadcasts ['aicom-team-merge',[A,B]] to EVERY live HC via WFBE_CO_FNC_SendToClient (the same primitive Teams.sqf:491 uses for delegate-aicom-team), and each HC's consumer self-gates on local(leaderA) && local(leaderB). Cross-HC pairs are simply skipped on every HC (no-op, self-heals next cadence) -- zero orphan risk.  ================================================================= PART 1 -- SERVER PICKER (fold into AI_Commander_HCTopUp.DRAFT.sqf) Add this block at the TOP of the worker, right after the funds/templates setup and BEFORE the existing top-up pick loop (line ~62). It runs first; if it fires a merge it exitWith's (1 action per call = throttle, matching the topup 1/call rule). ----------------------------------------------------------------- // ---- B69 SAME-HC MERGE PASS (default-OFF, 1 merge/call) ---- _mergeEnable = missionNamespace getVariable ["WFBE_C_AICOM_HC_MERGE_ENABLE", false]; if (typeName _mergeEnable == "BOOL" && {_mergeEnable}) then {     _sizeMax  = missionNamespace getVariable ["WFBE_C_AICOM_TEAM_SIZE_MAX", 12];     _mFloor   = round (_sizeMin * (missionNamespace getVariable ["WFBE_C_AICOM_HC_MERGE_FRAC", 0.6]));     _mRange   = missionNamespace getVariable ["WFBE_C_AICOM_HC_MERGE_RANGE", 300];     // Build the list of merge-eligible HC infantry teams (below floor, NOT in combat, NOT armour/heli).     _cands = [];     {         _t = _x;         if (!isNull _t && {_t getVariable ["wfbe_aicom_hc", false]}) then {             _tl = leader _t;             if (!isNull _tl && {alive _tl} && {(behaviour _tl) != "COMBAT"}) then {                 _an = {alive _x} count (units _t);                 _ty = _t getVariable ["wfbe_teamtype", -1];                 _arm = false;                 if (!isNil "_templates" && {_ty >= 0} && {_ty < count _templates}) then {                     { if (_x isKindOf "Tank") exitWith {_arm = true} } forEach (_templates select _ty);                 };                 if (!_arm && {_an > 0} && {_an < _mFloor}) then { _cands = _cands + [[_t, _an, _t… [truncated — full sketch regenerable]

Soak test: Primary greppable soak line (server RPT): AICOMSTAT|v1|EVENT|||HC_MERGE|A=...|B=...|sum=N|range=M -- emitted once per merge by the server picker. Pair it with the HC RPT line AICOMSTAT|v1|HCEVENT|MERGE_DONE|A=...|B=...|moved=K|newSize=S to confirm the units actually moved on the owning HC (proves the locality gate matched). 2-HC smoke acceptance: (a) grep both lines appear for at least one merge with newSize<=12; (b) for a deliberately co-located SAME-HC pair, B's mil_arrow2 marker disappears (team-ended fired) and allGroups count for that side DROPS by 1 within ~60s (GC reap); (c… [truncated — full sketch regenerable]

hctopup-stamp-teamtype-at-founding

Stamp wfbe_teamtype (and a refill pad-class) on every HC team at founding so any refill/merge pass can resolve it

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Teams.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf

GOAL: every HC-founded team carries (a) wfbe_teamtype = its template index, and (b) wfbe_aicom_padclass = the last Man-class in its FINAL template, both broadcast. Purely additive; nothing reads them yet so default behaviour is unchanged. This unblocks AI_Commander_HCTopUp.DRAFT (its _type>=0 && _type<count _templates guard currently fails for 100% of live HC teams because wfbe_teamtype stays at the -1 read-default).  WHY THIS SHAPE: the team OBJECT only exists on the founding HC/server inside Common_RunCommanderTeam.sqf (line 34: _team = CreateGroup). The server brain (AI_Commander_Teams.sqf) knows the template INDEX _pick and the FINAL _template array but NOT the group object; the HC knows the group but receives only [sideID, template, pos, skill] over the delegate. So we carry _pick + _padClass across the existing delegate (append as trailing args, never insert) and stamp on the HC where _team is local. Stamping ,true broadcasts to server so both sides read it.  A2-OA SAFETY: uses only count / select / typeName / isKindOf / setVariable / forEach. No isEqualType/isEqualTo/typed-params. The W7 skill arg stays at _this select 3 (slot 4 of the inner array) untouched - the two new values are appended at slots 5 and 6 (_this select 4, select 5), each guarded by `count _this >` so the 3-arg W6/W19 server-local CreateTeam calls (which never pass them) skip the block exactly like the existing W7 guard at line 55.  ==================================================================== EDIT 1 - AI_Commander_Teams.sqf : compute _padClass at founding and append _pick + _padClass to the delegate payload. -------------------------------------------------------------------- The _template here is already the FINAL array (post B57 pad / B66 truck-prepend at lines 429). _pick is the template index (used at line 498). Insert the pad-class compute just BEFORE the dispatch at line 491, then extend the SendToClient args array.  BEFORE (line 488-492):     _w7SkillSend = _logik getVariable "wfbe_aicom_veteran_skill";     if (isNil "_w7SkillSend") then {_w7SkillSend = 0};     _logik setVariable ["wfbe_aicom_veteran_skill", 0]; // consume     [_hcUnit, "HandleSpecial", ['delegate-aicom-team', _sideID, _template, getPos _facObj, _w7SkillSend]] Call WFBE_CO_FNC_SendToClient;  AFTER:     _w7SkillSend = _logik getVariable "wfbe_aicom_veteran_skill";     if (isNil "_w7SkillSend") then {_w7SkillSend = 0};     _logik setVariable ["wfbe_aicom_veteran_skill", 0]; // consume     //--- B69 hctopup-stamp: carry the template index + refill pad-class so the HC can stamp the     //--- founded g… [truncated — full sketch regenerable]

Soak test: Soak grep on the server RPT: each HC team founding now emits one line AICOMSTAT|v2|EVENT|||TEAM_STAMP|via=HC|teamtype=|padclass= ASSERTIONS in a smoke round: (a) the count of TEAM_STAMP|via=HC lines == the count of TEAM_FOUNDED|via=HC lines emitted at AI_Commander_Teams.sqf:505 (every HC founding is stamped); (b) teamtype is a non-negative integer (never -1) and equals the template index in the paired TEAM_FOUNDED line (template=); (c) padclass is a non-empty Man classname for infantry/mixed templates and empty only for all-vehicle/armour foundings. Greppable one-… [truncated — full sketch regenerable]

hctopup-wire-consumer-on-owning-hc

Write the missing HC-side aicom-team-topup consumer and route the worker to the OWNING HC via SendToClient (not a broadcast RequestSpecial)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Client/PVFunctions/HandleSpecial.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_HCTopUp.DRAFT.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HC_TOPUP_ENABLE = false (Common/Init/Init_CommonConstants.sqf (AICOM block ~line 307, right after WFBE_C_AICOM_TEAM_SIZE_MAX). isNil-guarded. Ships DEFAULT-OFF (master gate; absent => false => worker early-exits).)
  • new const WFBE_C_AICOM_HC_TOPUP_INTERVAL = 120 (Common/Init/Init_CommonConstants.sqf (AICOM block). Seconds between top-up passes; slow cadence like Produce(45)/Teams(90). Used by the new _ltTopUp throttle in AI_Commander.sqf.)
  • new const WFBE_C_AICOM_HC_TOPUP_FRAC = 0.6 (Common/Init/Init_CommonConstants.sqf (AICOM block). Reinforce only teams below MIN*FRAC (=4.8@8) so 7/8 squads are left alone. Already referenced by DRAFT line 57 but currently undefined.)
  • new const WFBE_C_AICOM_HC_TOPUP_RANGE = 900 (Common/Init/Init_CommonConstants.sqf (AICOM block). Metres: 'near a friendly supply town' radius for the resupply-makes-sense gate. Already referenced by DRAFT line 86 but currently undefined.)
GOAL: make the existing AI_Commander_HCTopUp.DRAFT worker actually refill under-strength HC-founded teams, by (1) writing the missing HC-side consumer in the CORRECT file, (2) routing the worker straight to the live HCs via SendToClient (the founding mechanism) instead of the broken RequestSpecial->server path, and (3) wiring a slow throttle into AI_Commander.sqf. Ships default-OFF.  === ROOT-CAUSE ANCHORS (verified) === - HandleSpecial that runs ON THE HC = Client/PVFunctions/HandleSpecial.sqf (the `delegate-aicom-team` consumer lives there at line 51). The DRAFT header/spec wrongly names "Server/Functions/HandleSpecial.sqf" -> that file is the SERVER's RequestSpecial sink (Server_HandleSpecial.sqf) and does NOT own the HC's local units. So the DRAFT's RequestSpecial path (DRAFT:138) can never reach the owning HC. The consumer MUST go in Client/PVFunctions/HandleSpecial.sqf. - Founding routes correctly (Teams.sqf:491): `[_hcUnit,"HandleSpecial",['delegate-aicom-team',...]] Call WFBE_CO_FNC_SendToClient`. SendToClient (Common_SendToClient.sqf) routes by `owner (_pvf select 0)`, so element 0 must be an HC LEADER unit. Reuse this exact mechanism for top-up. - Live HC registry = WFBE_HEADLESSCLIENTS_ID (array of HC GROUPS); live filter (Teams.sqf:167-168 / Server_PickLeastLoadedHC.sqf): `{!isNull _x && {!isNull leader _x} && {alive leader _x}}`. - CreateUnit signature (Common_CreateTeam.sqf:106): `[classname,_team,_pos,_sideID,_globalFlag] Call WFBE_CO_FNC_CreateUnit` -> returns unit (objNull on fail). This keeps skill/markers consistent vs raw createUnit. - A2-OA group-bool read = WFBE_CO_FNC_GroupGetBool (Init_Common.sqf:114). Produce.sqf:69 and AI_Commander.sqf:210 already use it; the DRAFT's line-68 `_team getVariable ["wfbe_aicom_hc",false]` 2-arg form is the UNRELIABLE-on-unset trap -> switch it.  === CHANGE 1: HC-side consumer (NEW case in Client/PVFunctions/HandleSpecial.sqf) === Add next to the `delegate-aicom-team` case (line 51). The `local (leader _team)` guard means every HC receives it but only the OWNING HC acts (no-op everywhere else):    case "aicom-team-topup": {       Private ["_tuTeam","_tuClasses","_tuLdr","_tuSide","_tuSideID","_tuSp","_tuU","_tuAdded"];       _tuTeam    = _args select 0;       _tuClasses = _args select 1;       //--- only the machine that OWNS the team's local units acts; all others no-op.       if (isNull _tuTeam) exitWith {};       _tuLdr = leader _tuTeam;       if (isNull _tuLdr || {!local _tuLdr}) exitWith {};       _tuSide   = side _tuTeam;       _tuSideID = (_tuSide) Call WFBE_CO_FNC_GetSideID;       _tuSp     =… [truncated — full sketch regenerable]

Soak test: Soak gate is the diag_log pair (both already AICOMSTAT|v1|EVENT|, greppable): worker emits HC_TOPUP|team=...|add=N (DRAFT:141) and the NEW consumer emits HC_TOPUP_RECV|team=...|added=N|of=N. PASS = for each HC_TOPUP send you see a matching HC_TOPUP_RECV with added>0 within one cadence in the HC RPT (proves the message reached the OWNING HC and units were created HC-side); the old broken path produced HC_TOPUP with NO matching RECV. Behavioural confirm: grep CMDRSTAT unitsPerTeam over a 60-min round with ENABLE=true vs ENABLE=false (A/B) -- expect the live average to climb from the measured… [truncated — full sketch regenerable]

heli-cannon-nudge-prefer-nearest-threat-not-first

Cannon-nudge target picker prefers the nearest/most-dangerous enemy, not nearEntities[0]

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HELI_NUDGE_DEBUG = 0 (Common/Init/Init_CommonConstants.sqf (B60 HELI CANNON-NUDGE block, after line 256 WFBE_C_AICOM_HELI_NUDGE_PERIOD); 0 = silent (Ray default), 1 = emit the per-pick diag_log soak line)
GOAL: change ONLY the selection rule at Common_RunCommanderTeam.sqf:149 from "first enemy the engine returns" to "nearest in-band alive enemy". Same candidate set (the same nearEntities call), same _band, same muzzle-resolve / flyInHeight / doTarget / doFire downstream (lines 150-166). No new commands, no class logic (that is the separate kept item heli-cannon-nudge-target-aware-nearest-threat).  ------------------------------------------------------------------ EDIT 1 — Common_RunCommanderTeam.sqf, the Spawn `private` array (line 133) Add the two scalar-min locals so they are explicitly private inside the Spawn (matches the file's existing private-array style; avoids leaking into outer scope).    BEFORE (line 133):     private ["_tm","_sd","_vehs","_h","_tgt","_cannon","_cannonMuzzle","_muzzles","_isGuided","_ammo","_band","_enSide"]; //--- B66 +_cannonMuzzle/_muzzles    AFTER:     private ["_tm","_sd","_vehs","_h","_tgt","_cannon","_cannonMuzzle","_muzzles","_isGuided","_ammo","_band","_enSide","_best","_bestD"]; //--- B66 +_cannonMuzzle/_muzzles; B68 +_best/_bestD (nearest-pick)  ------------------------------------------------------------------ EDIT 2 — Common_RunCommanderTeam.sqf, the picker (line 149) Replace the exitWith-on-first loop with a scalar-min nearest-pick over the SAME nearEntities result. _tgt stays the output variable, so lines 150-166 are untouched.    BEFORE (line 148-149):     _tgt = objNull;     { if (alive _x && {side _x == _enSide} && {(_h distance _x) < _band}) exitWith {_tgt = _x} } forEach ((getPos _h) nearEntities [["Man","Car","Tank","Air"], _band]);    AFTER:     //--- B68: nearest-pick. The old line exitWith'd on the FIRST enemy nearEntities returned     //--- (engine order is unordered) -> could lock the cannon on a lone rifleman at 690m while a     //--- tank sits at 200m, and thrashed target between 7s ticks as array order shifted. Iterate     //--- the SAME candidate set, keep the closest alive in-band enemy. Pure A2-OA scalar min: alive     //--- / side / distance / forEach only - no findIf, no isEqualType, no class logic (that is the     //--- separate class-aware item). Selection rule only; muzzle-resolve/doTarget/doFire below unchanged.     _tgt  = objNull;     _best = objNull;     _bestD = 1e9;     {         if (alive _x && {side _x == _enSide} && {(_h distance _x) < _band}) then {             _d = _h distance _x;            //--- recomputed once; cheap, keeps the band test and min test consistent             if (_d < _bestD) then { _bestD = _d; _best = _x };         };     } forEach ((getPos _h) nearEntities [["M… [truncated — full sketch regenerable]

Soak test: Soak verification (set WFBE_C_AICOM_HELI_NUDGE_DEBUG=1 for the test round, revert to 0 for prod): grep the server RPT for [AICOM][HELI_NUDGE] lines. PASS criteria over a soak round: (a) each line names a tgt and an ascending-or-low d= that is < band= (proves in-band), and (b) across consecutive 7s ticks for the same heli, the chosen tgt should NOT thrash to a farther entity while a nearer in-band enemy persists — i.e. d should be monotone-nearest, not random. Observable in-engine (debug OFF): spectate an attack-heli team during contact — the gunner's cannon should track the CLOSEST in-ba… [truncated — full sketch regenerable]

heli-cannon-nudge-target-aware-nearest-threat

Attack-heli cannon-nudge force-fires the cannon at the FIRST entity returned (even a tank), denying the gunner's ATGM — make it target-aware and pick the nearest threat

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HELI_NUDGE_HARD_TARGETS = 0 (Common/Init/Init_CommonConstants.sqf (AICOM B60 HELI CANNON-NUDGE block, after line 256 / WFBE_C_AICOM_HELI_NUDGE_PERIOD))
GOAL: stop the nudge from force-firing the cannon at armour/air (which denies the gunner's guided ATGM). Two edits, both A2-OA-1.64-safe (only isKindOf / getNumber / count / select / forEach / objNull — all already used in this file). Behaviour change is a STRICT NARROWING of when the cannon force-fires, so it cannot make armour engagement worse (matches the item's Low-risk note). No new units, no sim-gating, no antistack, AI never frozen (the while/sleep loop and flyInHeight are untouched).  --- EDIT 1: Common_RunCommanderTeam.sqf line 149 — nearest-in-band pick instead of first-match ---  BEFORE (line 149):     _tgt = objNull;     { if (alive _x && {side _x == _enSide} && {(_h distance _x) < _band}) exitWith {_tgt = _x} } forEach ((getPos _h) nearEntities [["Man","Car","Tank","Air"], _band]);  AFTER (replaces the single line; declare _bestD in the existing private[] on line 133 — append "_bestD" to that array):     _tgt  = objNull;     _bestD = _band + 1;   //--- scalar min; nearEntities radius is _band so this is an unreachable seed     {         if (alive _x && {side _x == _enSide}) then {             private "_d";             _d = _h distance _x;             if (_d < _bestD) then { _bestD = _d; _tgt = _x };         };     } forEach ((getPos _h) nearEntities [["Man","Car","Tank","Air"], _band]);  (Plain scalar-min loop over the SAME candidate set; no sort-by-CODE, no findIf/selectMin — those are A3. nearEntities already bounds the radius to _band, so the old explicit `_h distance _x < _band` filter is subsumed by the seed _bestD=_band+1.)  --- EDIT 2: Common_RunCommanderTeam.sqf — classify _tgt, skip the force-fire on HARD targets --- Wrap the existing flyInHeight + muzzle-resolve + selectWeapon/doTarget/doFire block (current lines 150-166). Add the class test to the `if (!isNull _tgt)` guard.  BEFORE (line 150):     if (!isNull _tgt) then {         _h flyInHeight (...HELI_GUN_ALT...);         ... muzzle resolve ...         (gunner _h) selectWeapon _cannonMuzzle;         (gunner _h) doTarget _tgt;         (gunner _h) doFire _tgt;     };  AFTER:     if (!isNull _tgt) then {         private "_hard";         _hard = (_tgt isKindOf "Tank") || {_tgt isKindOf "Air"} || {_tgt isKindOf "Armored"};         if (_hard && {(missionNamespace getVariable ["WFBE_C_AICOM_HELI_NUDGE_HARD_TARGETS", 0]) <= 0}) then {             //--- nearest threat is armour/air: LEAVE the gunner on its self-picked guided weapon (ATGM path). Do NOT force the cannon.             diag_log format ["[WASP][HELI-NUDGE] skip-cannon hard tgt=%1 d=%2 side=%3", typeOf _tgt, round _bestD, _sd];… [truncated — full sketch regenerable]

Soak test: Grep the HC/server RPT for the two new diag_log lines: "[WASP][HELI-NUDGE] skip-cannon hard tgt=" (proves the ATGM path is preserved vs armour/air) and "[WASP][HELI-NUDGE] force-cannon soft tgt=" (proves cannon still fires on Man/Car). Soak pass = during a match with attack helis engaging armour, skip-cannon lines appear with tgt= a Tank/APC/Air class AND observed behaviour shows the heli launching its guided ATGM at the tank instead of strafing it ineffectually with cannon; force-cannon lines appear only with Man/Car classes. Fail signal = skip-cannon line shows a soft class (mis-classificati… [truncated — full sketch regenerable]

heli-cas-intercept-enemy-spearhead

Idle/over-strength attack-heli teams intercept the enemy's published spearhead instead of marching to a town

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_AssignTowns.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_AIR_INTERCEPT = 1 (Common/Init/Init_CommonConstants.sqf (AICOM block, after WFBE_C_AICOM_HELI_NUDGE_PERIOD at line ~256))
  • new const WFBE_C_AICOM_AIR_INTERCEPT_RANGE = 3500 (Common/Init/Init_CommonConstants.sqf (same AICOM block, immediately after WFBE_C_AICOM_AIR_INTERCEPT))
  • new const WFBE_C_AICOM_AIR_INTERCEPT_MIN_ALIVE = 3 (Common/Init/Init_CommonConstants.sqf (same AICOM block, after RANGE))
GOAL: give heli-only commander teams an AIR-INTERCEPT pre-pass: instead of marching to a town they cannot capture, they fly to the enemy's exposed maneuver column (an enemy team whose leader is in the open near our front). The order MODE stays "towns-target" so the existing loiter/CAS branch in Common_RunCommanderTeam handles the non-town destination on arrival (orbits the column); the capture phase resolves nearest-town as a benign fallback. Bounded to ONE heli team per pass -> AI count + behaviour stay flat.  === EDIT 1: Init_CommonConstants.sqf — new consts in the AICOM block (after line 256) === BEFORE (line 256): 	if (isNil "WFBE_C_AICOM_HELI_NUDGE_PERIOD") then {WFBE_C_AICOM_HELI_NUDGE_PERIOD = 7};   //--- s between nudges. AFTER (append immediately below): 	//--- B68 AIR-INTERCEPT (air-doctrine): an idle/over-strength HELI-ONLY commander team peels onto the 	//--- enemy's exposed maneuver column near our front instead of grinding a town it cannot capture. 	//--- Order mode stays "towns-target" so RunCommanderTeam's loiter/CAS branch orbits the column on arrival. 	if (isNil "WFBE_C_AICOM_AIR_INTERCEPT")           then {WFBE_C_AICOM_AIR_INTERCEPT           = 1};    //--- 1 = ON (default), 0 = off (heli teams target towns as today). 	if (isNil "WFBE_C_AICOM_AIR_INTERCEPT_RANGE")     then {WFBE_C_AICOM_AIR_INTERCEPT_RANGE     = 3500}; //--- m: max distance from an OWNED town to an enemy team leader for it to count as an intercept-worthy column near our front. 	if (isNil "WFBE_C_AICOM_AIR_INTERCEPT_MIN_ALIVE") then {WFBE_C_AICOM_AIR_INTERCEPT_MIN_ALIVE = 3};    //--- min alive units in the enemy team (don't chase a single retreating remnant).  === EDIT 2: AI_Commander_AssignTowns.sqf — pre-pass before the town-target assign === Anchor: in the per-team loop, _target is resolved by line ~345 and consumed at line 346 `if (!isNil "_target") then { if (!isNull _target) then { ... line 348 [_team,"towns"] Call SetTeamMoveMode ... }`. Insert the pre-pass RIGHT BEFORE line 346. It either leaves _target untouched (no intercept) or REPLACES the publish with a direct intercept order (mode "towns-target", dest = enemy leader getPos) and skips the town assign via a flag.  Add to the private[] decl on line 12 (append names): "_intDone","_intLdr","_intPos","_intEnLogik","_intEnTeams","_intRange","_intMinAlive","_intHeliOnly","_intVehs","_intHasHeli","_ownTownsForInt".  BEFORE (line 345-348): 			}; 				if (!isNil "_target") then { 					if (!isNull _target) then { 						[_team, "towns"] Call SetTeamMoveMode;  AFTER (insert the marked block; one heli team per pass via _… [truncated — full sketch regenerable]

Soak test: Grep the server RPT for the new event line: AICOMSTAT|v2|EVENT|<side>|<min>|AIR_INTERCEPT|team=...|enLdr=<heliEnemyClass>|dist=<m>. Soak pass = this line appears for attack-heli teams when the enemy has a 3+ man column near our front, and the named heli team's subsequent order pos is the column (cross-check against the existing ASSAULT_DISPATCH lines NOT firing for that same team that pass). Observable behaviour: an idle/over-strength attack heli flies to orbit/strike a moving enemy ground column near the front instead of parking over an uncapturable depot. FPS guard: total AI count and per-… [truncated — full sketch regenerable]

heli-nudge-avoid-human-armor

Heli cannon-nudge: do NOT force the cannon at a human-crewed Tank/APC (leave the gunner's guided ATGM); prefer cannon vs soft + human infantry

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HELI_SPARE_HUMAN_ARMOR = 1 (Common/Init/Init_CommonConstants.sqf (AICOM heli-nudge block, lines 252-256, right after WFBE_C_AICOM_HELI_NUDGE_PERIOD))
GOAL: when the cannon-nudge's first-returned target (_tgt, picked at line 149) is a HUMAN-CREWED Tank or APC, SKIP the force-cannon block for this tick (no selectWeapon/doTarget/doFire) so the heli gunner keeps its self-picked guided ATGM against the player armor. Force-cannon stays for soft targets (Man/Car) and AI-crewed armor. Minimal additive exemption clause — does NOT touch the picker, pairs with the separate nearest-threat key.  FILE 1 — Common_RunCommanderTeam.sqf, inside the force block that today runs at lines 150-166. Today line 150 opens `if (!isNull _tgt) then {` and immediately flies low + force-fires. We add ONE guarded early-skip just inside that block, BEFORE flyInHeight.  ------ BEFORE (lines 148-150, 166-167) ------         _tgt = objNull;         { if (alive _x && {side _x == _enSide} && {(_h distance _x) < _band}) exitWith {_tgt = _x} } forEach ((getPos _h) nearEntities [["Man","Car","Tank","Air"], _band]);         if (!isNull _tgt) then {             _h flyInHeight (missionNamespace getVariable ["WFBE_C_AICOM_HELI_GUN_ALT", 35]);             ... (muzzle resolve + selectWeapon + doTarget + doFire, lines 152-165) ...         };  ------ AFTER ------         _tgt = objNull;         { if (alive _x && {side _x == _enSide} && {(_h distance _x) < _band}) exitWith {_tgt = _x} } forEach ((getPos _h) nearEntities [["Man","Car","Tank","Air"], _band]);         //--- B68 HUMAN-ARMOR SPARE: if the nudge`s target is a HUMAN-crewed Tank/APC, do NOT force the cannon         //--- this tick. Bouncing cannon rounds off a player MBT force-denies the gunner`s guided ATGM (the round         //--- that actually kills it). Skip the force block; the heli keeps flying + re-engages next tick. Soft         //--- targets (Man/Car) and AI-crewed armor are unaffected. A2-OA-safe: isKindOf + isPlayer + count crew.         private "_spareThis"; _spareThis = false;         if ((missionNamespace getVariable ["WFBE_C_AICOM_HELI_SPARE_HUMAN_ARMOR", 1]) > 0 && {!isNull _tgt}) then {             if ((_tgt isKindOf "Tank") || {_tgt isKindOf "APC"}) then {                 if (({isPlayer _x} count (crew _tgt)) > 0) then {                     _spareThis = true;                     if (WFBE_C_AICOM_HELI_NUDGE_DIAG) then { diag_log format ["[AICOM][HELI-NUDGE] B68 spare human armor tgt=%1 by=%2", typeOf _tgt, typeOf _h]; };                 };             };         };         if (!isNull _tgt && {!_spareThis}) then {             _h flyInHeight (missionNamespace getVariable ["WFBE_C_AICOM_HELI_GUN_ALT", 35]);             ... (UNCHANGED muzzle resolve + selectWeapon + doTarget + … [truncated — full sketch regenerable]

Soak test: Greppable diag line: [AICOM][HELI-NUDGE] B68 spare human armor in the server/HC RPT. SOAK: set WFBE_C_AICOM_HELI_NUDGE_DIAG=1, join as a player in a Tank/APC, fly a friendly attack heli (or let an AICOM attack-heli team) within WFBE_C_AICOM_HELI_CANNON_RANGE (700m). PASS = the line fires AND the heli gunner is observed firing its guided ATGM (smoke trail / lock tone) at the player tank rather than spraying cannon that pings off armor. NEGATIVE control: a player in a soft vehicle (Car) or an AI-crewed tank should NOT produce the spare line and the cannon force should still occur. Boot-smoke g… [truncated — full sketch regenerable]

heli-team-loiter-cas-not-depot-park

Attack-heli teams loiter at a CAS standoff over the spearhead instead of parking on the depot flag

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_CAS_LOITER_RANGE = 600 (Common/Init/Init_CommonConstants.sqf — insert after line 256 (WFBE_C_AICOM_HELI_NUDGE_PERIOD), inside the B60 HELI block beside the other WFBE_C_AICOM_HELI_* consts)
  • new const WFBE_C_AICOM_CAS_LOITER_ALT = 90 (Common/Init/Init_CommonConstants.sqf — insert after line 256, beside WFBE_C_AICOM_CAS_LOITER_RANGE)
GOAL: a pure attack-heli team (no foot infantry, only Helicopter hulls with transportSoldier==0) must NOT take the lines 953-974 "pure-armour" branch, which doMoves the leader onto _townCenter and lays a 40m SAD — a heli doMove'd to a 40m point AT the depot descends/hovers/lands ON the flag and is killed by town AA/RPG/MG. Instead it should orbit a standoff point at altitude and let the existing B60 cannon-nudge (lines 118-174, already gated/running for this team) engage garrison/relief as they appear. Helis never count for the town presence scan (server_town.sqf), so capture math is unchanged.  PLACEMENT: inside the `if (_arrived && !_captureDone && _mode == \"towns-target\")` block, in the `} else {` arm at line 953 (the no-foot-infantry case). Split that arm into a heli-only sub-branch vs the existing pure-armour-hull sub-branch.  NEW CONSTANTS (Init_CommonConstants.sqf, after line 256): ------------------------------------------------------------   //--- B68 CAS-LOITER: a pure attack-heli team (no foot infantry) holds a standoff orbit   //--- OUTSIDE the depot AA/RPG kill-box instead of parking on the flag; the B60 cannon-nudge   //--- then softens garrison/relief from altitude. Never latches captureDone (re-issues each tick).   if (isNil \"WFBE_C_AICOM_CAS_LOITER_RANGE\") then {WFBE_C_AICOM_CAS_LOITER_RANGE = 600};  //--- m standoff from depot center (> 40m cap range, outside town AA close band).   if (isNil \"WFBE_C_AICOM_CAS_LOITER_ALT\")   then {WFBE_C_AICOM_CAS_LOITER_ALT   = 90};   //--- m orbit altitude (above small-arms; nudge drops to GUN_ALT 35 only when a target is in band).  PSEUDO-DIFF — Common_RunCommanderTeam.sqf, line 953 arm:    BEFORE:   953   } else {   954      //--- Pure-armour team (no infantry at all): ... Park the hull dead-center ...   ...   963      [_team, true, [[_townCenter, 'SAD', _capRange, 30, [], [], [\"COMBAT\",\"RED\",\"WEDGE\",\"NORMAL\"]]]] Spawn WFBE_CO_FNC_WaypointsAdd;   964      if (!isNull leader _team && {alive leader _team}) then {(leader _team) doMove _townCenter};   965      if ((!isNull _townObj) && {(_townObj getVariable [\"sideID\", -1]) == _sideID}) then { _captureDone = true; ... };   974   };    AFTER:   953   } else {         //--- B68 CAS-LOITER DETECT: no foot infantry AND every alive combat vehicle is an attack         //--- heli (Helicopter + transportSoldier==0). A2-OA-safe idiom mirrors lines 130/199/381.         private [\"_heliOnly\",\"_anyVeh\",\"_casHeli\"];         _heliOnly = true; _anyVeh = false; _casHeli = objNull;         {           if (!isNull _x && {alive _x} && {canMove _x}) the… [truncated — full sketch regenerable]

Soak test: SOAK TEST HOOK (greppable diag_log): grep the AICOM log / server RPT for the new line tag: "Common_RunCommanderTeam.sqf: [%1] team [%2] B68 CAS-LOITER standoff" Expected during a soak with at least one attack-heli AICOM team dispatched: the B68 CAS-LOITER line appears (with standoff/alt/town name) AND the old "(armour) CAPTURED" / "begin capture" depot-park line does NOT appear for that heli team. Observable behaviour: in-game the attack heli should ORBIT ~600m off the contested town at ~90m altitude and make gun-runs (B60 cannon-nudge dropping it to GUN_ALT 35 on a target-in-band), instead… [truncated — full sketch regenerable]

hqsite-lock-timeout

Bound the wfbe_hqinuse lock wait in Construction_HQSite so a stuck lock can't deadlock the mobilize/deploy chain

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Construction/Construction_HQSite.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_HQSITE_LOCK_TIMEOUT = 20 (Common/Init/Init_CommonConstants.sqf (B60 MHQ RELOCATION block, after WFBE_C_AICOM_MHQ_STUCK_SECS at line 249, using the same if (isNil ...) then {...} sibling-knob pattern))
PROBLEM (anchored to exact lines) ================================= Construction_HQSite.sqf:14-15 (the ONLY edit site in this script):    13  /* Handle the LAG. */   14  waitUntil {!(_logik getVariable "wfbe_hqinuse")};   // UNBOUNDED, 1-arg read   15  _logik setVariable ["wfbe_hqinuse", true];  Two faults:  (a) No timeout. If wfbe_hqinuse is left stuck true (a prior toggle errored      out, or two paths crossed), this ExecVM'd toggle blocks FOREVER. Both      AICOM relocation entry points route through here:        AI_Commander_MHQReloc.sqf:169  (MOBILIZE: ExecVM Construction_HQSite)        AI_Commander_MHQReloc.sqf:293  (RE-DEPLOY: ExecVM Construction_HQSite)      Their own guards (waitUntil ...:171 / :295) time out at +30s and the      spawn proceeds, but the construction never ran -> HQ neither mobilized      nor deployed, while wfbe_mhqreloc_active may already be cleared      (MHQReloc:297) -> half-toggled, base-less side.  (b) 1-arg getVariable returns nil when unset; !nil is truthy so it      "happens to pass" but is fragile. Use the 2-arg [name,false] form      (valid on the side-logic OBJECT _logik) so an unset lock reads false.  FIX -- Construction_HQSite.sqf, replace lines 13-15 =================================================== BEFORE:   /* Handle the LAG. */   waitUntil {!(_logik getVariable "wfbe_hqinuse")};   _logik setVariable ["wfbe_hqinuse", true];  AFTER:   /* Handle the LAG. B68: bound the lock wait so a stuck wfbe_hqinuse can't      deadlock the AICOM mobilize/deploy chain (MHQReloc:169/:293) or the      human deploy path. Generous deadline (default 20s, well past any real      toggle, whose internal sleeps are sub-second to a few seconds) so a      legitimate in-flight toggle always releases first; only a genuine      deadlock is force-claimed. 2-arg read so an unset lock reads false. */   private "_lockDeadline";   _lockDeadline = time + (missionNamespace getVariable ["WFBE_C_HQSITE_LOCK_TIMEOUT", 20]);   waitUntil {!(_logik getVariable ["wfbe_hqinuse", false]) || time > _lockDeadline};   if (_logik getVariable ["wfbe_hqinuse", false]) then {       diag_log ("AICOMSTAT|v1|HQLOCK|" + (str _sideText) + "|" + str (round (time / 60)) + "|FORCE_CLAIM|timeout=" + str (missionNamespace getVariable ["WFBE_C_HQSITE_LOCK_TIMEOUT", 20]));   };   _logik setVariable ["wfbe_hqinuse", true];  (Add `"_lockDeadline"` is done inline via `private`; no change needed to the  line-1 Private[] header since we use a local `private` declaration. The rest  of the script -- the deploy branch :20-67, the mobilize branch :68-114, and  the release `_logik setV… [truncated — full sketch regenerable]

Soak test: Greppable: on a genuine deadlock the new force-claim emits AICOMSTAT|v1|HQLOCK|<side>|<min>|FORCE_CLAIM|timeout=20 to the RPT/diag_log. In a healthy soak this line should NEVER appear (the lock always releases before the deadline) -- its presence flags a real stuck-lock event that previously would have silently deadlocked. Observable behaviour: across a multi-hour AICOM-relocation soak, every AICOMSTAT|v1|MHQRELOC|...|DEPLOYED event should continue to follow its mobilize with no side ever ending up base-less (no Construction_HQSite.sqf: [side] MHQ has been mobilized. without a later matc… [truncated — full sketch regenerable]

hqstrike-hc-order-contract

Fix HQ-strike order so HC strikers actually assault the enemy HQ instead of capturing the nearest town

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

ONE-LINE FIX. File: Server/AI/Commander/AI_Commander_Strategy.sqf, line 544 (inside the strike-team dispatch `while {_strikeCount < 3}` loop). Change the HC ORDER mode string from "towns-target" to "defense". Nothing else changes. No new constant, no new mode, the seq-bump idiom stays byte-identical.  WHY THIS IS THE RIGHT KNOB (confirmed in source, not assumed): - Order contract (Common_RunCommanderTeam.sqf:9-13): wfbe_aicom_order = [seq, mode, pos], mode in {"towns-target","defense"}. The HC driver reads ONLY this var (server-local teams ignore it and use the SetTeamMove* writes at :541-542). - "defense" branch (Common_RunCommanderTeam.sqf:668-669): on arrival lays `[_team, true, [[_dest,'SAD',100,30,[],[],["COMBAT","RED","WEDGE","NORMAL"]]]] Spawn WFBE_CO_FNC_WaypointsAdd` at the RAW order pos _dest. Takes a raw pos, needs NO town object, never latches _captureDone, re-asserts each cycle => strikers close on and fight at getPos _enemyHQ. - "towns-target" branch (Common_RunCommanderTeam.sqf:702): gated on `_mode == "towns-target"`; resolves _townObj = [_dest, towns] Call WFBE_CO_FNC_GetClosestEntity (:720-721) and holds THAT town's depot ring — i.e. the strongest teams capture whatever town is nearest the enemy base and never pressure the HQ. This is the bug. - This EXACTLY mirrors the already-proven relief idiom in the same file at :437-438, which broadcasts mode "defense" at getPos _town for HC teams and works.  PSEUDO-DIFF (AI_Commander_Strategy.sqf, the HC-order broadcast at line 544; surrounding lines unchanged):    540  _best setVariable ["wfbe_aicom_strike", true];   541  [_best, "move"] Call SetTeamMoveMode;                 // UNCHANGED (server-local path + sets wfbe_teammode="move")   542  [_best, getPos _enemyHQ] Call SetTeamMovePos;          // UNCHANGED   543  if (_best getVariable ["wfbe_aicom_hc", false]) then { - 544    _best setVariable ["wfbe_aicom_order", [(if (isNil {_best getVariable "wfbe_aicom_order"}) then {-1} else {(_best getVariable "wfbe_aicom_order") select 0}) + 1, "towns-target", getPos _enemyHQ], true]; + 544    _best setVariable ["wfbe_aicom_order", [(if (isNil {_best getVariable "wfbe_aicom_order"}) then {-1} else {(_best getVariable "wfbe_aicom_order") select 0}) + 1, "defense", getPos _enemyHQ], true];   545  };  The ONLY token changed: the string literal "towns-target" -> "defense" inside slot 1 of the order array on line 544.  A2-OA-1.64 SAFETY: pure string-literal swap; no new commands introduced. The surrounding idioms already in use (isNil {grpVar}, getVariable [name,default] on the LOGIC _logik only, setVariable… [truncated — full sketch regenerable]

Soak test: Soak observability (no new logging needed — reuse existing diag_log lines): (1) Strike launch already emits diag_log ("AICOMSTAT|v1|EVENT|"+_sideText+"|"+...+"|HQ_STRIKE|launched") at AI_Commander_Strategy.sqf:523 — grep server RPT for HQ_STRIKE|launched. (2) Per-team join is AICOMLog "joins the HQ strike" (:547). (3) BEHAVIOUR ASSERTION (the actual fix verification): grep Common_RunCommanderTeam.sqf:731 capture-begin line team [..] begin capture at [..] — AFTER the fix, strike teams must NOT emit this for the HQ run (the towns-target capture phase at :702 is gated out by _mode=="defense… [truncated — full sketch regenerable]

hqstrike-order-defense-not-townstarget

HQ-strike HC teams attack the nearest TOWN, not the enemy HQ — switch the strike order to 'defense'-mode SAD on the HQ

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_SAD = 100 (Common/Init/Init_CommonConstants.sqf (AICOM ASSAULT FINISH tunables block, alongside WFBE_C_AICOM_ASSAULT_SAD at line ~322))
PRIMARY (load-bearing, 1 token) — AI_Commander_Strategy.sqf line 544, the HC striker order-issue site inside the `if (_best getVariable ["wfbe_aicom_hc", false])` block:  BEFORE (544):   _best setVariable ["wfbe_aicom_order", [(if (isNil {_best getVariable "wfbe_aicom_order"}) then {-1} else {(_best getVariable "wfbe_aicom_order") select 0}) + 1, "towns-target", getPos _enemyHQ], true];  AFTER (544):   _best setVariable ["wfbe_aicom_order", [(if (isNil {_best getVariable "wfbe_aicom_order"}) then {-1} else {(_best getVariable "wfbe_aicom_order") select 0}) + 1, "defense", getPos _enemyHQ], true];  Only the mode token "towns-target" -> "defense" changes. seq-bump, pos=getPos _enemyHQ, and the `,true` publicVariable broadcast are all untouched. Leave the server-local path's [_best,"move"] SetTeamMoveMode at line 541 and SetTeamMovePos at 542 alone (that path is effectively dead — all live teams are HC-resident — but the move-mode and the order are independent and "move" is harmless).  WHY THIS WORKS (no new executor needed): the HC driver Common_RunCommanderTeam.sqf parses `_mode = _order select 1` (line 481) once per seq bump, then on arrival branches at line 668:   - "defense" (668-669): lays a HOLD SAD/100m/COMBAT/RED/WEDGE waypoint at _dest (=enemy HQ pos) and STOPS — it never enters the capture phase.   - else == "towns-target" (670-671): lays an 80m approach SAD, then the `if (_arrived && !_captureDone && _mode == "towns-target")` block at line 702 runs the dismount-capture phase, which at lines 720-721 re-resolves the objective to the NEAREST TOWN to _dest via `[_dest, towns] Call WFBE_CO_FNC_GetClosestEntity` — i.e. it grinds a town next to the base instead of pressing the base. The mode flip simply takes the team down the 668-669 branch and bypasses 702 entirely (the `_mode == "towns-target"` guard is now false).  SECONDARY (optional, recommended same-sitting) — make the HQ-press hold radius tunable instead of the hard-coded 100, mirroring the existing WFBE_C_AICOM_ASSAULT_SAD pattern. Two edits:  (a) Init_CommonConstants.sqf, in the "ASSAULT FINISH tunables" block right after line 322 (WFBE_C_AICOM_ASSAULT_SAD):   ADD:   if (isNil "WFBE_C_AICOM_HQSTRIKE_SAD") then {WFBE_C_AICOM_HQSTRIKE_SAD = 100};  //--- m: defense-mode HOLD-SAD radius for the HQ strike (was hard-coded 100). Lower toward 40-60 to charge into the base core.  (b) Common_RunCommanderTeam.sqf line 669, defense branch:   BEFORE:     [_team, true, [[_dest, 'SAD', 100, 30, [], [], ["COMBAT","RED","WEDGE","NORMAL"]]]] Spawn WFBE_CO_FNC_WaypointsAdd;   AFTER:     [_team, true, [[_dest, '… [truncated — full sketch regenerable]

Soak test: Soak: HQ-strike already emits a greppable launch line at AI_Commander_Strategy.sqf:523 — diag_log ("AICOMSTAT|v1|EVENT|"+_sideText+"|"+...+"|HQ_STRIKE|launched"). grep server RPT for HQ_STRIKE|launched, then OBSERVE the named striker teams (the "team [%1] joins the HQ strike" INFORMATION log at line 547) and confirm via map/spectate that they converge on and engage AT the enemy HQ position (getPos _enemyHQ) and HOLD/fight within ~100m of the base — NOT divert to and grind a neighbouring town. Pass criteria: after a launch, strikers are seen pressing the base and the AI-vs-AI round actually… [truncated — full sketch regenerable]

hqstrike-order-mode-defense-not-townstarget

HQ-strike sends HC teams a 'towns-target' order so they capture the town nearest the enemy HQ instead of assaulting the HQ

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

ONE-LINE MODE SWAP. The HQ-strike picks up to 3 strong field teams and, for HC teams (100% of live teams), publishes a "towns-target" order pointed at the enemy HQ pos. In the HC driver that mode routes into the DISMOUNT-CAPTURE phase (Common_RunCommanderTeam.sqf:702), which resolves the town object as the NEAREST town to _dest (L720-721) and holds its 40m depot ring -- so the strongest teams capture whatever town sits next to the enemy base and never pressure the HQ. The relief path already uses the correct contract: it publishes "defense" (AI_Commander_Strategy.sqf:438), which the driver turns into a SAD-at-pos hold (Common_RunCommanderTeam.sqf:668-669: [[_dest,'SAD',100,30,[],[],['COMBAT','RED','WEDGE','NORMAL']]]). Swap the strike's HC order mode from "towns-target" to "defense" so strikers run seek-and-destroy AT the enemy HQ pos -- engaging base defenders and able to damage the HQ object -- reusing an already-live, already-proven order contract. The server-local writes on lines 541-542 ([_best,"move"] SetTeamMoveMode + [_best,getPos _enemyHQ] SetTeamMovePos) stay as-is (server-local teams ignore the order var); only the HC publish string changes. All commands here (setVariable [.,.,true], isNil, getVariable, getPos, select) are A2-OA-1.64-safe and already used verbatim by the relief idiom 100 lines above -- no A3 constructs introduced.\n\nLINE 544 (AI_Commander_Strategy.sqf), inside `if (_best getVariable [\"wfbe_aicom_hc\", false]) then { ... }`:\n\nBEFORE:\n  _best setVariable [\"wfbe_aicom_order\", [(if (isNil {_best getVariable \"wfbe_aicom_order\"}) then {-1} else {(_best getVariable \"wfbe_aicom_order\") select 0}) + 1, \"towns-target\", getPos _enemyHQ], true];\n\nAFTER:\n  _best setVariable [\"wfbe_aicom_order\", [(if (isNil {_best getVariable \"wfbe_aicom_order\"}) then {-1} else {(_best getVariable \"wfbe_aicom_order\") select 0}) + 1, \"defense\", getPos _enemyHQ], true];\n\nPSEUDO-DIFF:\n- ... + 1, \"towns-target\", getPos _enemyHQ], true];\n+ ... + 1, \"defense\",      getPos _enemyHQ], true];\n\nThat is the ONLY change. The seq-counter increment, getPos _enemyHQ payload, and JIP-broadcast flag (the `true` 3rd arg) are all preserved so the existing strike-recall path (L549-559) and the strike refill loop (L528-548) continue to work unchanged. No new constants, no driver change (the \"defense\" branch already exists and is live for relief).\n\nOPTIONAL (do NOT bundle unless asked): the \"defense\" SAD radius is the literal 100 in the driver (L669). If soak shows strikers orbiting at ~100m instead of closing on the HQ, that 100 could lat… [truncated — full sketch regenerable]

Soak test: Soak observable: when the strike launches you already get diag_log (\"AICOMSTAT|v1|EVENT|\" + _sideText + ... + \"|HQ_STRIKE|launched\") at AI_Commander_Strategy.sqf:523 -- grep AICOMSTAT|HQ_STRIKE|launched to confirm the strike fires. The behavioural delta to watch (no new log needed for the minimal fix): after a launch, the AICOMLog "team [..] begin capture at [..]" line from Common_RunCommanderTeam.sqf:731 should NO LONGER appear for striking teams (that line is the towns-target capture phase the fix removes them from); instead striker leaders should converge to within ~100m of getPos (… [truncated — full sketch regenerable]

hqstrike-picker-weight-vehicle-punch

HQ-strike force is selected by raw alive body count, so it always picks infantry and never commits MBT/attack-heli teams (the actual punch)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_STRIKE_VEH_BONUS = 100 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf -- AICOM strategy block, insert immediately after the WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX line (227), same if (isNil ...) then {...} JIP-safe idiom)
GOAL: change the HQ-strike picker (AI_Commander_Strategy.sqf:528-538) so it ranks candidate teams by a PUNCH score (alive bodies + a large bonus if the team owns a crewed combat vehicle) instead of raw alive count. Any intact armour/heli team then outranks a full infantry squad, while two same-class teams still rank by size. ONLY A2-OA-1.64 commands: count / isKindOf / vehicle / alive / units / typeName-free. Reuses the proven heavy-detect idiom from Common_AICOMServiceTick.sqf:103 verbatim.  --- STEP 1: new constant (Init_CommonConstants.sqf, after line 227) ------------- BEFORE (lines 226-227):   if (isNil "WFBE_C_AICOM_CONCENTRATION") then {WFBE_C_AICOM_CONCENTRATION = 6};   if (isNil "WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX") then {WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX = 2};  AFTER (add one line, keep idempotent isNil idiom so it is JIP-safe / no publicVariable wait needed):   if (isNil "WFBE_C_AICOM_CONCENTRATION") then {WFBE_C_AICOM_CONCENTRATION = 6};   if (isNil "WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX") then {WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX = 2};   if (isNil "WFBE_C_AICOM_STRIKE_VEH_BONUS") then {WFBE_C_AICOM_STRIKE_VEH_BONUS = 100}; //--- B69: punch score bonus added to a strike candidate that owns a crewed Tank/APC/Air, so armour/heli teams (the floor-exempt PUNCH) outrank a full infantry squad in the HQ-strike picker. 0 = rollback to raw-bodycount selection.  --- STEP 2: the picker loop (AI_Commander_Strategy.sqf:528-538) ----------------- BEFORE (exact current text):   while {_strikeCount < 3} do {     _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending     {       _team = _x;       if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {         if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {           _alive = {alive _x} count (units _team);           if (_alive > _bestN) then {_bestN = _alive; _best = _team};         };       };     } forEach _teams;     if (isNull _best) exitWith {};  AFTER (only the inner score block changes; everything else byte-identical so all relief/garrison/strike-exclusion guards and the >=2-men floor are preserved):   while {_strikeCount < 3} do {     _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending (now a PUNCH-score threshold)     {       _team = _x;       if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {         if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(… [truncated — full sketch regenerable]

Soak test: Soak hook (greppable): the existing line-547 AICOMLog already prints "joins the HQ strike" per picked team via WFBE_CO_FNC_AICOMLog ("...team [%2] (%3 men) joins the HQ strike."). For a deterministic punch-selection signal, add ONE diag_log inside the if (_hasHeavy > 0) path right where _score gets the bonus, e.g.: diag_log ("AICOMSTAT|v1|EVENT|" + _sideText + "|" + str (round (time / 60)) + "|HQ_STRIKE|veh_team_picked|hasHeavy=" + str _hasHeavy). Then in soak: grep server RPT for HQ_STRIKE|veh_team_picked — its presence proves an armour/heli team was scored above infantry and committed to… [truncated — full sketch regenerable]

hqstrike-prefer-vehicle-punch-and-min-size

HQ-strike picks teams by raw body count — it never sends armour/attack-heli and can commit 2-man scraps; rank by punch and floor the size

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_STRIKE_MIN_ALIVE = 6 (Common/Init/Init_CommonConstants.sqf (AICOM block, near the other if (isNil "WFBE_C_AICOM_...") guards ~lines 167-242). Add: if (isNil "WFBE_C_AICOM_STRIKE_MIN_ALIVE") then {WFBE_C_AICOM_STRIKE_MIN_ALIVE = 6}; — pure-infantry teams below this alive count are skipped for the HQ strike; MBT/attack-heli teams are EXEMPT. 0 = rollback to old behaviour (floor of 2).)
  • new const WFBE_C_AICOM_STRIKE_PUNCH_BONUS = 100 (Common/Init/Init_CommonConstants.sqf (AICOM block, same spot). Add: if (isNil "WFBE_C_AICOM_STRIKE_PUNCH_BONUS") then {WFBE_C_AICOM_STRIKE_PUNCH_BONUS = 100}; — additive score bias so any team owning a non-transport Tank or non-transport attack Helicopter outranks a pure-infantry team. 0 = rollback to pure body-count ranking.)
GOAL: In the HQ-strike picker (AI_Commander_Strategy.sqf, the `while {_strikeCount < 3}` block at lines 528-548), stop ranking candidate teams by raw alive body-count. Instead (1) rank by a PUNCH score = alive + bonus when the team owns armour/attack-heli, so the AI throws its hardest punch (the player sees the armoured spearhead), and (2) skip pure-infantry remnants below a floor, while EXEMPTING vehicle teams (a 2-3 crew MBT/heli still qualifies — the vehicle is the punch). Selection-only: NO new units, FPS-neutral, total AI flat. Reuses the exact A2-OA classify idiom already proven at Common_RunCommanderTeam.sqf:130 & :139.  --- A2-OA-SAFETY NOTES --- * `vehicles _team` returns the group's crewed vehicles (group handle confirmed: the block already calls `units _team`/`leader _team`). A2-OA-safe. * isKindOf with classname literals ("Tank","Helicopter") + getNumber(configFile>>"CfgVehicles">>typeOf>>"transportSoldier")==0 is the SAME idiom at RunCommanderTeam.sqf:130/139. No A3 commands (no isEqualType/isEqualTo/typed params). * Constants read at runtime via `missionNamespace getVariable ["NAME", default]` so a JIP/late-init that hasn't run Init_CommonConstants still gets a safe default (mirrors the WFBE_C_AICOM_HELI_CANNON_NUDGE read at RunCommanderTeam.sqf:123). No publicVariable, no waits.  --- BEFORE (AI_Commander_Strategy.sqf:528-538) ---   while {_strikeCount < 3} do {       _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending       {           _team = _x;           if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {               if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {                   _alive = {alive _x} count (units _team);                   if (_alive > _bestN) then {_bestN = _alive; _best = _team};               };           };       } forEach _teams;       if (isNull _best) exitWith {};       ...  --- AFTER (replace the picker scan body; rest of block unchanged) ---   //--- read tunables once per while-pass (nil-guarded; JIP-safe defaults mirror Init_CommonConstants)   private ["_minAlive","_punchBonus"];   _minAlive   = missionNamespace getVariable ["WFBE_C_AICOM_STRIKE_MIN_ALIVE", 6];   _punchBonus = missionNamespace getVariable ["WFBE_C_AICOM_STRIKE_PUNCH_BONUS", 100];   while {_strikeCount < 3} do {       _best = grpNull; _bestScore = 0; //--- score-based now (was _bestN=1 body-count)       {           _team = _x;           if (!isNull _team && {!isPlayer (lead… [truncated — full sketch regenerable]

Soak test: Boot-smoke gate first: pack + MISSINIT must show ErrInExpr=0 (the new scope adds several locals — the private[] update prevents leak/parse issues). Behavioural soak: grep the server RPT for the new greppable line AICOMSTAT|v1|EVENT|<side>|<min>|HQ_STRIKE_PICK|alive=..|punch=... PASS criteria over a winning-side soak (a round where HQ_STRIKE|launched fires): at least one HQ_STRIKE_PICK line shows punch=true on a side that owns armour/air (proving the spearhead now goes to the HQ), AND no HQ_STRIKE_PICK line shows punch=false with alive<6 (proving the infantry floor holds, i.e. no … [truncated — full sketch regenerable]

hqstrike-staging-rally-mass-before-assault

HQ-strike: stage strikers at a forward rally and release the final assault only once a body-count threshold is massed (stop the piecemeal trickle into the enemy HQ)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_RALLY_STANDOFF = 800 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside the MHQ_RELOCATE cluster ~L242-249; isNil-guarded form))
  • new const WFBE_C_AICOM_HQSTRIKE_STAGE_RADIUS = 400 (Common/Init/Init_CommonConstants.sqf (same AICOM cluster))
  • new const WFBE_C_AICOM_HQSTRIKE_RELEASE_BODIES = 14 (Common/Init/Init_CommonConstants.sqf (same AICOM cluster))
  • new const WFBE_C_AICOM_HQSTRIKE_STAGE_TIMEOUT = 240 (Common/Init/Init_CommonConstants.sqf (same AICOM cluster))
GOAL: rally HQ-strike teams at a point ~800m short of the enemy HQ (on our side of the segment), hold them there until >=14 staged bodies OR a 240s timeout, then re-issue every striker to the enemy HQ. All A2-OA-1.64-safe (segment math by plain components, distance/count, getPos, getVariable+isNil; the timeout is the mandatory PV-not-JIP-durable fallback so the assault NEVER stalls). No team is ever frozen: staging teams hold a live MOVE order at the rally and re-task on release.  === 1) NEW CONSTANTS (Init_CommonConstants.sqf, paste beside MHQ_RELOCATE block ~L249) ===   if (isNil "WFBE_C_AICOM_HQSTRIKE_RALLY_STANDOFF") then {WFBE_C_AICOM_HQSTRIKE_RALLY_STANDOFF = 800}; //--- m short of enemy HQ, on our side of the segment, where strikers mass.   if (isNil "WFBE_C_AICOM_HQSTRIKE_STAGE_RADIUS")  then {WFBE_C_AICOM_HQSTRIKE_STAGE_RADIUS  = 400}; //--- m: a striker counts as "staged" when its leader is within this of the rally.   if (isNil "WFBE_C_AICOM_HQSTRIKE_RELEASE_BODIES")then {WFBE_C_AICOM_HQSTRIKE_RELEASE_BODIES= 14};  //--- staged alive bodies needed to release the assault (~2 floor-strength teams). Keep modest so a depleted late army can still release.   if (isNil "WFBE_C_AICOM_HQSTRIKE_STAGE_TIMEOUT")then {WFBE_C_AICOM_HQSTRIKE_STAGE_TIMEOUT = 240}; //--- s from first striker assignment before a forced release (PV-not-JIP-durable wait fallback). 0 disables staging entirely (legacy direct-rush).  === 2) Strategy.sqf — rewrite the HQ-HUNT strike body (current L518-548) ===  --- BEFORE (current L525-548, abridged) ---------------------------------------   //--- Keep up to 3 strongest field teams on the strike (refill as strikers die).   _strikeCount = 0;   { if (!isNull _x && {_x getVariable ["wfbe_aicom_strike", false]} && {({alive _x} count (units _x)) > 0}) then {_strikeCount = _strikeCount + 1} } forEach _teams;   while {_strikeCount < 3} do {       ... pick _best ...       _best setVariable ["wfbe_aicom_strike", true];       [_best, "move"] Call SetTeamMoveMode;       [_best, getPos _enemyHQ] Call SetTeamMovePos;          // <-- always straight at HQ       if (_best getVariable ["wfbe_aicom_hc", false]) then {           _best setVariable ["wfbe_aicom_order", [<seq+1>, "towns-target", getPos _enemyHQ], true];       };       _strikeCount = _strikeCount + 1;       ...   };  --- AFTER -----------------------------------------------------------------------   private ["_hqPos","_myHQ2","_rallyStandoff","_stageRad","_relBodies","_stageTO","_dx","_dy","_segLen","_t01","_rally","_firstT","_staged","_released","_relReason"];    _hqPos  = getPos _enemyHQ… [truncated — full sketch regenerable]

Soak test: Greppable diag_log lines added (server RPT): "AICOMSTAT|...|HQ_STRIKE_STAGING|staged=N|need=14|wait=Ss|to=240" each strategy tick while massing, then exactly once "AICOMSTAT|...|HQ_STRIKE_RELEASE|reason=MASS|staged=N|wait=Ss" (or reason=TIMEOUT). SOAK PASS CRITERIA: (1) on a winning round you see one or more STAGING lines with staged climbing, FOLLOWED by a single RELEASE line -- proving teams massed before the assault rather than trickling; (2) reason=TIMEOUT must appear in at least the stalled cases and never leave a strike stuck (no >300s gap of STAGING lines with no RELEASE); (3) observabl… [truncated — full sketch regenerable]

hqstrike-team-size-floor-exempt-vehicles

HQ-strike accepts 2-man remnants (floor of 1) onto a cross-map base rush, wasting the strike and bleeding the front

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE = 6 (Common/Init/Init_CommonConstants.sqf (AICOM punchy block, immediately after WFBE_C_AICOM_TEAM_SIZE_MAX at line 307))
GOAL: In the HQ-strike picker (AI_Commander_Strategy.sqf:528-548) require an INFANTRY candidate team to have >= WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE alive units before it qualifies, but EXEMPT crewed MBT / attack-heli teams (the vehicle is the punch). Today _bestN starts at 1, so any 2-man remnant is eligible.  === STEP 1: new constant (Init_CommonConstants.sqf, AICOM block) === Insert right after line 307 (WFBE_C_AICOM_TEAM_SIZE_MAX), inside the same `if (isNil ...)` idiom:    //--- HQ-STRIKE eligibility floor. An INFANTRY team must have >= this many alive units to   //--- join the war-ending HQ rush (no thin remnants on a cross-map base rush). MBT/attack-heli   //--- teams are EXEMPT (vehicle+crew is the punch). Mirrors the founding TEAM_SIZE_MIN policy.   //--- Applied in AI_Commander_Strategy.sqf HQ-strike picker. Rollback: set to 2 (legacy behaviour).   if (isNil "WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE") then {WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE = 6};  === STEP 2: classify helper + floored picker (AI_Commander_Strategy.sqf) === Add to the `private [...]` array on line 18: "_cmdrTpl","_tt","_isVehTeam","_minAlive". Fetch the side's templates ONCE before the while-loop (cheap; reused each pass). Insert just before line 528 (`while {_strikeCount < 3} do {`):    // ADD:   _cmdrTpl  = missionNamespace getVariable [Format ["WFBE_%1AITEAMTEMPLATES", _sideText], []];   _minAlive = WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE;  BEFORE (lines 528-538):   while {_strikeCount < 3} do {       _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending       {           _team = _x;           if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {               if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {                   _alive = {alive _x} count (units _team);                   if (_alive > _bestN) then {_bestN = _alive; _best = _team};               };           };       } forEach _teams;  AFTER:   while {_strikeCount < 3} do {       _best = grpNull; _bestN = 0; //--- pick the strongest qualifying team       {           _team = _x;           if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {               if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {                   _alive = {alive _x} count (units _team);                   //--- VEHICLE-team exemption (Tank or non-transport he… [truncated — full sketch regenerable]

Soak test: Add a one-line diag to the success path so soak logs are greppable. Just before line 546 (_strikeCount = _strikeCount + 1;) inside the picker, after _best is committed, insert: diag_log ("AICOMSTAT|v1|EVENT|" + _sideText + "|" + str (round (time / 60)) + "|HQ_STRIKE_JOIN|" + str _bestN + "|veh=" + str _isVehTeam); SOAK CHECK: grep the server RPT for HQ_STRIKE_JOIN. PASS = every infantry join (veh=false) shows a count >= 6; any veh=false line with count < 6 is a floor leak (regression). veh=true lines may show any count >=1 (exemption working). Also confirm HQ_STRIKE|launched (line 52… [truncated — full sketch regenerable]

hqstrike-time-to-enemy-away-window

HQ-strike timing: only commit the strike while the enemy's maneuver force is AWAY from its own HQ (exploit known enemy positions)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_AWAY_RADIUS = 1200 (Common/Init/Init_CommonConstants.sqf (B68 AICOM strategy block, after line 280 / WFBE_C_AICOM_STR_LONE_FARHQ))
  • new const WFBE_C_AICOM_HQSTRIKE_AWAY_MAX = 6 (Common/Init/Init_CommonConstants.sqf (B68 AICOM strategy block, after line 280 / WFBE_C_AICOM_STR_LONE_FARHQ))
GOAL: gate ONLY the fresh-launch HQ-strike branch (line 515) so the AI rushes the enemy HQ only when the enemy base is thin (its maneuver teams pushed forward onto us). Hysteresis branch (513) stays untouched so an in-flight rush never aborts mid-push. All commands below are A2-OA-1.64 core (getVariable/isNull/alive/count/units/leader/distance/<=/&&/forEach). _enemyHQ already exists at line 508; we reuse the same enemy-team collection already iterated for _enStr at line 63.  === EDIT 1: Init_CommonConstants.sqf — add 2 nil-guarded constants === Anchor: insert immediately AFTER line 280 (WFBE_C_AICOM_STR_LONE_FARHQ), inside the existing B68 AICOM block.  ADD:   //--- B68 (Ray 2026-06-22) HQ-STRIKE TIMING: only launch the cross-map HQ rush while the enemy's   //--- maneuver force is AWAY from its own base (omniscient-AI exploit of known enemy positions).   //--- Consumed in AI_Commander_Strategy.sqf fresh-launch branch only. Default-ON, tunable, rollback = set AWAY_MAX huge (e.g. 9999) to disable the gate.   if (isNil "WFBE_C_AICOM_HQSTRIKE_AWAY_RADIUS") then {WFBE_C_AICOM_HQSTRIKE_AWAY_RADIUS = 1200}; //--- m: enemy teams whose LEADER is within this of the enemy HQ count as "home defenders".   if (isNil "WFBE_C_AICOM_HQSTRIKE_AWAY_MAX")    then {WFBE_C_AICOM_HQSTRIKE_AWAY_MAX = 6};       //--- launch only when enemy home-defender ALIVE count <= this (base thin). Higher = launch more readily; 9999 disables the gate.  === EDIT 2a: AI_Commander_Strategy.sqf — declare the new local === Anchor: the private[...] list at line 18. Append "_enemyHomeDefenders" so it is not a leaked global.   ...,"_spStall","_enemyHomeDefenders"];  === EDIT 2b: AI_Commander_Strategy.sqf — gate the fresh-launch branch === Anchor: lines 511-517 (the "HQ HUNT" gate). _enemyHQ set at 508.  BEFORE (511-517):   if (!isNull _enemyHQ && {alive _enemyHQ}) then {       if (_wasStrike) then {           _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.2) && (_myStr >= _enStr);          //--- hysteresis       } else {           _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.5) && (_myStr >= _enStr * 1.1);   //--- Steff's rule       };   };  AFTER (511-...):   if (!isNull _enemyHQ && {alive _enemyHQ}) then {       if (_wasStrike) then {           _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.2) && (_myStr >= _enStr);          //--- hysteresis: stay committed (UNGATED on purpose - never abort an in-flight rush)       } else {           //--- B68 HQ-STRIKE TIMING: count enemy teams still parked at their own HQ (leader within AWAY_RADIUS).           //--- Reuses t… [truncated — full sketch regenerable]

Soak test: Greppable: the new fresh-launch path emits a distinct diag_log token at the moment the away-window opens a strike: search the server RPT for AICOMSTAT|v1|EVENT|||HQ_STRIKE|awaywin_homedef (N = enemy alive home-defenders at launch, must be <= WFBE_C_AICOM_HQSTRIKE_AWAY_MAX). Existing line 523 already logs HQ_STRIKE|launched on first commit; the new awaywin_homedef line will appear in the SAME tick as a fresh launch — pair them to confirm the gate fired. Observable behaviour during soak: HQ rushes should now coincide with the enemy having pushed teams onto your towns (thin base) an… [truncated — full sketch regenerable]

hqstrike-town-gate-fraction

Make the HQ-strike town gate a fraction of total towns, not the hard literal 8 (it never fires on the live 7-town config)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC = 0.45 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM block, insert right after WFBE_C_AICOM_RETREAT_MAX_DIST at line 284, alongside the LASTSTAND/STR_LONE siblings))
  • new const WFBE_C_AICOM_HQSTRIKE_TOWN_FLOOR = 3 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM block, same insertion point) - safety floor so a tiny-map config cannot trip the strike at 1-2 owned towns; max(floor, ceil(frac*total)) is the effective gate)
PROBLEM ANCHOR (verified) AI_Commander_Strategy.sqf computes the war-state town counts at :36-40 by iterating the global `towns` array, then gates the war-ending HQ strike on a HARD LITERAL 8:    :513  _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.2) && (_myStr >= _enStr);          //--- hysteresis   :515  _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.5) && (_myStr >= _enStr * 1.1);   //--- entry  Live config WFBE_C_TOWNS_AMOUNT=7 (Init_CommonConstants.sqf:620) -> Towns_RemovedSmallTowns template (Init_TownMode.sqf:13) -> well under 18 towns; a single side can essentially never own 9, so the strike is DEAD and AI-vs-AI rounds drag to timeout.  -------------------------------------------------------------------------- STEP 1 - new constants (Init_CommonConstants.sqf, AICOM block, after line 284) A2-OA-safe: plain isNil-guarded assignment, identical pattern to the sibling gates above it.    + //--- B69 HQ-strike gate (hqstrike-town-gate-fraction): the old literal `_myTowns > 8` never   + //--- fired on the live 7-town config (max owned < 9), so the war-ending HQ strike was dead.   + //--- Gate now scales to the actual town count. Effective gate = max(FLOOR, ceil(FRAC*total)).   + if (isNil "WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC")  then {WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC  = 0.45}; //--- own >= this fraction of ALL towns to launch/hold the HQ strike. Higher = stricter. The 1.5x/1.2x town-ratio AND strength gates still apply on top. Rollback to old behaviour: set FLOOR high (e.g. 99) so the strike never fires, or hard-revert the two strategy lines to `_myTowns > 8`.   + if (isNil "WFBE_C_AICOM_HQSTRIKE_TOWN_FLOOR") then {WFBE_C_AICOM_HQSTRIKE_TOWN_FLOOR = 3};    //--- absolute minimum owned towns regardless of fraction (anti-trigger-happy floor on tiny maps). On 7-town live this is below ceil(0.45*7)=4 so the fraction dominates.  -------------------------------------------------------------------------- STEP 2 - compute the gate ONCE per cycle, just before the strike block (AI_Commander_Strategy.sqf) Insert immediately before :507 (the "//--- 3) HQ HUNT" comment) OR just before :511. `count towns` and `_myTowns` are already live in scope here; ceil/count/max-via-if are all A2-OA-1.64 valid.    //--- B69: scale the HQ-strike town gate to the live town count (was a dead literal `> 8`).   _hqFrac      = missionNamespace getVariable ["WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC", 0.45];   _hqFloor     = missionNamespace getVariable ["WFBE_C_AICOM_HQSTRIKE_TOWN_FLOOR", 3];   _totalTowns  = count towns;   _strikeMinTowns = ceil (_totalTowns * _hqFrac);   if (… [truncated — full sketch regenerable]

Soak test: PRIMARY (grep server RPT during a 2-side AI soak): the rising-edge launch line now carries the gate values - grep 'AICOMSTAT|v2|EVENT' and look for '|HQ_STRIKE|launched|myTowns=4|gate=4|total=7' (or similar). On the live 7-town config this line should APPEAR within a round once a side gets clearly ahead - previously it NEVER appeared (the literal-8 gate was unreachable). Absence of any HQ_STRIKE|launched across a full round = the fix did not take (re-check the gate math / constant load). SECONDARY (existing telemetry, no code needed): watch the per-cycle posture/stall lines already in the f… [truncated — full sketch regenerable]

hqstrike-town-threshold-fraction-not-literal8

HQ-strike town gate is a hardcoded >8 owned towns, so the round-ending strike never fires on reduced-town lobby settings

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC = 0.5 (Common/Init/Init_CommonConstants.sqf (AICOM block, alongside the other WFBE_C_AICOM_* nil-guards, e.g. after line 292 WFBE_C_AICOM_MHQ_TOWN_BUFFER))
  • new const WFBE_C_AICOM_HQSTRIKE_TOWN_MIN = 4 (Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC). Hard floor so tiny-town maps still require a real lead before the finisher can fire.)
PROBLEM ANCHOR (AI_Commander_Strategy.sqf):   :40   } forEach towns;                 // <- `towns` is the global capturable-town array; count towns = total   :513  _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.2) && (_myStr >= _enStr);   :515  _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.5) && (_myStr >= _enStr * 1.1); The literal 8 is never derived from the live town count. If WFBE_C_TOWNS_AMOUNT yields < 9 capturable towns, NO side can ever reach >8 owned -> the only AI-vs-AI finisher is permanently dead -> rounds drag to timeout.  ----- CHANGE 1: Init_CommonConstants.sqf (new nil-guarded constants, AICOM block) ----- Insert after the MHQ_TOWN_BUFFER guard (~line 292), same idiom as every neighbouring guard:    //--- B68 (Ray 2026-06-21) HQ-STRIKE TOWN GATE: the round-ending HQ rush was gated on a hardcoded   //--- ">8 owned towns" literal, so on reduced-town lobby settings (WFBE_C_TOWNS_AMOUNT < 9) the   //--- finisher could NEVER fire and AI-vs-AI rounds ran to timeout. Now derived as a fraction of the   //--- live capturable-town count (count towns) with a hard floor. On full Chernarus this lands ~8 so   //--- live behaviour is essentially unchanged. Consumed in AI_Commander_Strategy.sqf just before :511.   if (isNil "WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC") then {WFBE_C_AICOM_HQSTRIKE_TOWN_FRAC = 0.5}; //--- own >= this fraction of all towns to launch/hold the HQ strike. Rollback to legacy: set so frac*count==8 (e.g. on a 15-town map use ~0.54), or pin TOWN_MIN=9 + FRAC=0.   if (isNil "WFBE_C_AICOM_HQSTRIKE_TOWN_MIN")  then {WFBE_C_AICOM_HQSTRIKE_TOWN_MIN  = 4};   //--- hard floor: never strike below this many owned towns regardless of fraction (guards tiny maps). Rollback: 9 to fully restore the old >8 behaviour.  ----- CHANGE 2: AI_Commander_Strategy.sqf (compute _strikeMin before :511, swap the two literals) ----- First add _strikeMin to the private[] list at :18 (append "_strikeMin").  BEFORE (:507-516):   //--- 3) HQ HUNT: strike when clearly winning; stand down when the edge is gone.   _enemyHQ = (_enemySide) Call WFBE_CO_FNC_GetSideHQ;   _wasStrike = _logik getVariable ["wfbe_aicom_strike_on", false];   _strikeOn = false;   if (!isNull _enemyHQ && {alive _enemyHQ}) then {       if (_wasStrike) then {           _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.2) && (_myStr >= _enStr);          //--- hysteresis...       } else {           _strikeOn = (_myTowns > 8) && (_myTowns >= _enemyTowns * 1.5) && (_myStr >= _enStr * 1.1);   //--- Steff's rule...       };   };  AFTER:   //--- 3) HQ HUNT: strike when clearly w… [truncated — full sketch regenerable]

Soak test: Soak grep target (new diag line added right after the gate computation): AICOMSTAT|v1|HQSTRIKEGATE| — each commander tick prints townsTotal=, strikeMin=, myTowns=, strikeOn=. ACCEPTANCE on a reduced-town soak (set WFBE_C_TOWNS_AMOUNT so count towns < 9): grep the server RPT for HQSTRIKEGATE and confirm strikeMin is < 8 and that a leading side eventually logs strikeOn=true, followed by the existing AICOMSTAT|v1|EVENT|<side>|<min>|HQ_STRIKE|launched line (:523) — which on the old build would NEVER appear below 9 towns. On a full-Chernarus control soak, confirm strikeMin print… [truncated — full sketch regenerable]

hqstrike-vehicle-weighted-picker

Weight the HQ-strike picker toward armour/attack-heli teams and raise the per-team alive floor

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HQSTRIKE_TEAMS = 3 (Common/Init/Init_CommonConstants.sqf (AICOM block, after WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX line ~227, isNil-guarded like its neighbours))
  • new const WFBE_C_AICOM_HQSTRIKE_MIN_ALIVE = 4 (Common/Init/Init_CommonConstants.sqf (AICOM block, same insertion point as above))
  • new const WFBE_C_AICOM_HQSTRIKE_VEH_BOOST = 100 (Common/Init/Init_CommonConstants.sqf (AICOM block, same insertion point; additive punch bias, tunable))
PROBLEM ANCHOR (AI_Commander_Strategy.sqf:525-548, the "Keep up to 3 strongest field teams" loop):   528  while {_strikeCount < 3} do {   529      _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending   530      {   531          _team = _x;   532          if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {   533              if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {   534                  _alive = {alive _x} count (units _team);   535                  if (_alive > _bestN) then {_bestN = _alive; _best = _team};   536              };   537          };   538      } forEach _teams;   539      if (isNull _best) exitWith {};   ...   546      _strikeCount = _strikeCount + 1;   547      ["INFORMATION", Format ["...team [%2] (%3 men) joins the HQ strike.", _sideText, _best, _bestN]] Call WFBE_CO_FNC_AICOMLog; Ranking is raw body-count (_alive > _bestN, _bestN init 1) -> 8-12-man infantry ALWAYS outranks a 1-3-crew MBT/attack-heli, and a 2-man scrap can be committed.  KEY CORRECTION vs the raw findings JSON snippet: do NOT use a bare `(vehicle _x) isKindOf "Helicopter"` -- that boosts TRANSPORT choppers too. The proven codebase idiom (AI_Commander.sqf:411-412, AI_Commander_Teams.sqf:380-381, AI_Commander_Produce.sqf:191-193) gates the heli on transportSoldier==0 so ONLY attack helis count as punch. Reuse that exact gate.  ----- AFTER (pseudo-diff) -----  @@ line 528 -- parameterise the team count - while {_strikeCount < 3} do { + while {_strikeCount < WFBE_C_AICOM_HQSTRIKE_TEAMS} do {  @@ lines 529-536 -- punch-weighted score + vehicle-exempt floor -     _best = grpNull; _bestN = 1; //--- need at least 2 men to be worth sending +     _best = grpNull; _bestScore = -1; _bestN = 0; _bestVeh = false; //--- punch-weighted: armour/attack-heli outrank a full inf squad       {           _team = _x;           if (!isNull _team && {!isPlayer (leader _team)} && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {               if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team}) then {                   _alive = {alive _x} count (units _team); +                 //--- A2-OA-safe vehicle-team detect off the LIVE crewed hull (transportSoldier==0 = attack heli, same gate as AI_Commander.sqf:411-412) +                 _isVehTeam = false; +                 { +                     if (alive _x && {(vehicle _x) != _x}) then { … [truncated — full sketch regenerable]

Soak test: Grep the server RPT for the new picker line: AICOMSTAT|v1|EVENT|||HQ_STRIKE_PICK|veh -- a single veh pick on a side that fields an MBT/attack-heli team proves armour/heli now outranks infantry (pre-change this NEVER appears; every striker was inf). Soak gate: after a winning side launches an HQ_STRIKE (existing HQ_STRIKE|launched line), confirm at least one subsequent HQ_STRIKE_PICK|veh within the same strike window when that side has a live Tank/attack-heli team. Observable in-match: the strike spearhead now contains the side's tank/Mi-24-class instead of pure infantry. Negat… [truncated — full sketch regenerable]

laststand-defenses-max-bump

DEFENSES_MAX=4 is fixed regardless of threat — a side genuinely in last-stand cannot field more emplaced defenders even when it has the funds

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Base.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_DEFENSES_MAX_LASTSTAND = 8 (Common/Init/Init_CommonConstants.sqf — AICOM block, immediately after WFBE_C_AI_COMMANDER_DEFENSES_MAX on line 149)
GOAL: make the base-defense ceiling threat-aware. Keep the flat 4 in normal play; raise it to 8 ONLY while the side is in last-stand (flag already on the side logic). One ceiling swap in Base.sqf + one isNil-guarded constant. No new units in normal play, funds gate unchanged.  --- A) Init_CommonConstants.sqf — add the constant right after line 149 ----------- BEFORE (line 149):     WFBE_C_AI_COMMANDER_DEFENSES_MAX = 4;      //--- V0.2: manned base statics the AI places around its HQ. AFTER:     WFBE_C_AI_COMMANDER_DEFENSES_MAX = 4;      //--- V0.2: manned base statics the AI places around its HQ.     WFBE_C_AICOM_DEFENSES_MAX_LASTSTAND = 8;   //--- B68: when a side is in last-stand (Strategy.sqf:74 flag), raise the manned-static ceiling 4->8 so an almost-eliminated side can spend banked funds on its final perimeter. Bounded; only fires for a near-dead side (low total-AI regime). Rollback: set == WFBE_C_AI_COMMANDER_DEFENSES_MAX (4) to disable. (Both are inside the same `if (isNil "WFBE_C_..." ) then {...}` first-load block, so they are isNil-guarded by the existing constants pattern — do NOT add a second guard.)  --- B) AI_Commander_Base.sqf — make _defMax threat-aware at line 458 ------------ Context (unchanged, line 459-460):     _defCount = _logik getVariable ["wfbe_aicom_defenses", 0];     if (_defCount < _defMax) then { _logik is already in scope (Base.sqf:17 = (_side) Call WFBE_CO_FNC_GetSideLogic) — the SAME side logic Strategy.sqf:73-74 sets wfbe_aicom_laststand on (true on entry, cleared false on exit at Strategy.sqf:92-93). Both workers are server-side; the flag is a local setVariable (no publicVariable), so no JIP/timeout concern.  BEFORE (line 458):     _defMax = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSES_MAX", 4]; AFTER (line 458, replace the one line):     _defMax = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_DEFENSES_MAX", 4];     if (_logik getVariable ["wfbe_aicom_laststand", false]) then {         _defMax = missionNamespace getVariable ["WFBE_C_AICOM_DEFENSES_MAX_LASTSTAND", 8];         diag_log ("AICOMSTAT|v1|EVENT|" + str _side + "|" + str (round (time / 60)) + "|LASTSTAND_DEFMAX|max=" + str _defMax + "|cur=" + str (_logik getVariable ["wfbe_aicom_defenses", 0]));     };  A2-OA-1.64 SAFETY NOTES: - getVariable with [name, default] array form, setVariable, missionNamespace getVariable, str, round, diag_log, time, +, if/then — all 1.64-native. No isEqualType/isEqualTo/typed params/typeName-comparison needed here. - _defMax stays a Number on both branches; the existing `_defCount < _defMax` comparison and the `_de… [truncated — full sketch regenerable]

Soak test: Soak signal 1 (the bump firing): grep the server RPT for AICOMSTAT|v1|EVENT|...|LASTSTAND_DEFMAX|max=8 — appears each Base tick a side is in last-stand and confirms _defMax was raised. Signal 2 (extra builds actually happening): the existing AICOMLog line at Base.sqf:484 should now show counts past 4, e.g. placed base defense 5/8, 6/8, ... 8/8 — grep AICOMLog for placed base defense [5-8]/8. Both should appear ONLY in matches where a side reached <=1 town (cross-check against the existing Strategy LAST-STAND log LAST-STAND - towns). Observable in-game: when a side is being pushed t… [truncated — full sketch regenerable]

laststand-perimeter-dispersion

Last-stand recalls every team onto a single HQ point and re-issues the move every 60s — teams bunch, block, and present one massed target

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_LASTSTAND_RING = 120 (Common/Init/Init_CommonConstants.sqf (B68 ATTACK-BIAS AICOM block, immediately after WFBE_C_AICOM_LASTSTAND_RATIO at line 278))
  • new const WFBE_C_AICOM_LASTSTAND_REISSUE_DIST = 150 (Common/Init/Init_CommonConstants.sqf (same block, fallback re-issue threshold so a team that wandered far is still re-tasked = never-frozen guardrail))
GOAL: in the LAST-STAND block of AI_Commander_Strategy.sqf (lines 78-88) replace the "every team -> same HQ getPos, re-issued every tick" recall with a STABLE per-team perimeter ring, only (re)issued when a team's assigned slot CHANGES (plus a never-frozen fallback). Also fix a latent bug: the current block only writes wfbe_teammode/wfbe_teamgoto via the helpers, which the HC driver (Common_RunCommanderTeam.sqf, reads wfbe_aicom_order=[seq,mode,pos]) IGNORES — so HC-resident commander teams (i.e. all of them) never actually move on last-stand. The dispersion rewrite ALSO broadcasts a per-slot "defense" order, mirroring the relief idiom at :437-438.  === STEP 1: NEW CONSTANTS (Init_CommonConstants.sqf, after line 278) ===   if (isNil "WFBE_C_AICOM_LASTSTAND_RING") then {WFBE_C_AICOM_LASTSTAND_RING = 120};         //--- last-stand perimeter radius (m) inside the base footprint; 0 = old centroid-on-HQ behaviour. Rollback: 0.   if (isNil "WFBE_C_AICOM_LASTSTAND_REISSUE_DIST") then {WFBE_C_AICOM_LASTSTAND_REISSUE_DIST = 150}; //--- never-frozen fallback: re-issue the move even on an unchanged slot if the leader has drifted > this many m from its slot pos.  === STEP 2: REWRITE THE RECALL LOOP (AI_Commander_Strategy.sqf, lines 77-88) === BEFORE (lines 77-88):   //--- Recall every non-garrison, non-HC AI team to HQ in defense posture.   {     _team = _x;     if (!isNull _team && {!isPlayer (leader _team)}) then {       _team setVariable ["wfbe_aicom_relief", objNull];       _team setVariable ["wfbe_aicom_strike", false];       _team setVariable ["wfbe_aicom_townorder", []];       [_team, "defense"] Call SetTeamMoveMode;       [_team, getPos ((_side) Call WFBE_CO_FNC_GetSideHQ)] Call SetTeamMovePos;     };   } forEach _teams;  AFTER:   //--- B68 LAST-STAND DISPERSION: ring the recalled teams around HQ instead of stacking them on one point.   //--- Pass 1: collect the eligible (alive AI, non-garrison) teams so the ring divisor is the REAL count.   private ["_lsTeams","_lsHQ","_lsHQPos","_lsRing","_lsReissue","_lsN","_lsI","_lsAng","_lsSlot","_lsPrev","_lsLdr","_lsDrift"];   _lsHQ = (_side) Call WFBE_CO_FNC_GetSideHQ;   _lsHQPos = if (!isNull _lsHQ) then {getPos _lsHQ} else {[0,0,0]};   _lsRing = missionNamespace getVariable ["WFBE_C_AICOM_LASTSTAND_RING", 120];   _lsReissue = missionNamespace getVariable ["WFBE_C_AICOM_LASTSTAND_REISSUE_DIST", 150];   _lsTeams = [];   {     _team = _x;     if (!isNull _team && {!isPlayer (leader _team)} && {(_logik getVariable ["wfbe_aicom_garrison", grpNull]) != _team} && {({alive _x} count (units _team)) > 0}) then {       _lsTe… [truncated — full sketch regenerable]

Soak test: SOAK GREP: new line "AICOMSTAT|v1|LASTSTAND_RING|||slot=/|drift=|ring=120" — on a fresh last-stand entry expect ONE per eligible team (slot=0/N .. N-1/N), then NO further LASTSTAND_RING lines for that side across subsequent ~60s Strategy ticks while the stand holds (proves the slot-change-only gate killed the every-tick re-bunch). Pair with the existing "AI_Commander_Strategy.sqf: [] LAST-STAND" INFORMATION log to bracket entry. OBSERVABLE: in last-stand the side's teams form a visible ring ~120m around HQ instead of one dense pile on the flag; a drift re-issue (team … [truncated — full sketch regenerable]

mhq-final-deploy-clearance-revalidate

Re-validate the FINAL deploy position against the hostile-ring buffer AND friendly-structure spacing before re-deploying

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_MHQReloc.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_MHQ_FINAL_STEPBACK = 120 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AICOM_MHQ_TOWN_BUFFER at ~line 292))
  • new const WFBE_C_AICOM_MHQ_FINAL_MAXTRIES = 12 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AICOM_MHQ_TOWN_BUFFER at ~line 292))
PROBLEM ANCHOR: The carefully-validated _destPos clearance (every enemy/GUER town ring-clear by 600+TOWN_BUFFER, MHQReloc.sqf:119-128) is DISCARDED at the final deploy. On a stuck/deadline/water-escape exit the HQ deploys at _finPos = getPos _mhq (line 281), and the water random-walk (283-291) picks a DRY spot with NO enemy-ring and NO friendly-structure check before the final ExecVM at line 293. So a relocation that strands mid-route can plant the static HQ inside a freshly-contested enemy/GUER town's 600m activation ring, or crowd/overlap an existing AI factory -- something the Base worker explicitly forbids (AI_Commander_Base.sqf:173,197 reject build spots within WFBE_C_AICOM_STRUCT_SPACING).  This adds a bounded clearance re-validation on _finPos that REUSES the two proven idioms already live in this codebase, then steps _finPos back toward _destPos (which is already ring-clear by construction) and finally hard-falls-back to _destPos if the step-budget is exhausted.  ================ EDIT 1: Init_CommonConstants.sqf (~line 292, in the B67 MHQ RELOCATION block) ================ ADD after the WFBE_C_AICOM_MHQ_TOWN_BUFFER line:     if (isNil "WFBE_C_AICOM_MHQ_FINAL_STEPBACK") then {WFBE_C_AICOM_MHQ_FINAL_STEPBACK = 120}; //--- m per step walking _finPos back toward the validated _destPos when the final deploy spot fails the ring/structure re-check.     if (isNil "WFBE_C_AICOM_MHQ_FINAL_MAXTRIES") then {WFBE_C_AICOM_MHQ_FINAL_MAXTRIES = 12};  //--- max step-back attempts before hard-falling-back to the already-ring-clear _destPos. (Reuses existing WFBE_C_AICOM_STRUCT_SPACING=45 and WFBE_C_AICOM_MHQ_TOWN_BUFFER=1000 -- no new spacing/ring constants needed.)  ================ EDIT 2a: MHQReloc.sqf line 151 -- widen the Spawn arg list ================ The change-note claim "currently only _myID is passed" is imprecise: line 151 actually passes   [_side, _sideText, _logik, _myID, _destPos, _arriveDist, _deadline, _stuckSecs, _enemyClear, _enemySideObj] Spawn {...} We must additionally pass _enemyID, _guerID, _ringClear (all already computed in the supervisor scope at lines 62/66/87). Append them:  BEFORE:   [_side, _sideText, _logik, _myID, _destPos, _arriveDist, _deadline, _stuckSecs, _enemyClear, _enemySideObj] Spawn { AFTER:   [_side, _sideText, _logik, _myID, _destPos, _arriveDist, _deadline, _stuckSecs, _enemyClear, _enemySideObj, _enemyID, _guerID, _ringClear] Spawn {  ================ EDIT 2b: MHQReloc.sqf line 152-162 -- bind the new args + private them ================ Extend the inner private[] (line 152) with the 3 new names plus the re-check loca… [truncated — full sketch regenerable]

Soak test: Greppable diag_log lines (new): "FINAL_REVALIDATE|CLEAR" and "FINAL_REVALIDATE|FALLBACK_DESTPOS" in the AICOMSTAT|v1|MHQRELOC stream. On a clean arrive-at-dest relocation expect CLEAR|tries=0. To force the corrective path during soak, drop WFBE_C_AICOM_MHQ_DEADLINE low (e.g. 60) so the teleport/stuck exit fires while the route still passes near a hostile town, then grep the log: a FINAL_REVALIDATE line with tries>0 (or FALLBACK_DESTPOS) proves the re-check moved the deploy off a ring/structure conflict. Observable in-game: after a stranded relocation the re-deployed HQ no longer sits inside an… [truncated — full sketch regenerable]

mhq-min-advance-floor

Require a minimum NET forward advance before paying the full mobilize/drive/deploy cost on a relocation

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_MHQReloc.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_MHQ_MIN_ADVANCE = 600 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM MHQ-reloc block, insert after WFBE_C_AICOM_MHQ_TOWN_BUFFER at line 292; or alongside the STANDOFF siblings at line 245))
PROBLEM ANCHOR (AI_Commander_MHQReloc.sqf): - Trigger is pure straight-line: line 76  `if ((_hq distance _frontPos) <= _frontDist) exitWith {};` - Destination for the nearest own town is capped to `_standoff min _d` (line 117). When the nearest own-held town hugs the OLD HQ, `_d` (HQ<->town) is tiny, so `_back` is tiny and `_cand` lands almost on top of the current HQ. The relocation then pays full mobilize+drive+deploy (and a blind drive) for a base that barely moved, and the next 180s interval can re-pick and oscillate. No net-advance floor exists. - Accept happens at line 128: `if (_clear && {!surfaceIsWater _cand}) then {_destPos = _cand};`  We have `_hqPos` (line 49) and `_frontPos` (line 57) already in scope. Net forward advance of a candidate = how much closer to the front it sits than the current HQ does:     netAdvance = (_hqPos distance _frontPos) - (_cand distance _frontPos) Reject the candidate unless netAdvance >= WFBE_C_AICOM_MHQ_MIN_ADVANCE. Use plain getVariable + scalar subtraction; `distance` on two pos ARRAYs is A2-OA-safe.  === EDIT 1: AI_Commander_MHQReloc.sqf ===  (a) Read the constant once, next to the other knobs. After line 86:   BEFORE:     86  _townBuffer = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_TOWN_BUFFER", 1000];     87  _ringClear  = 600 + _townBuffer;   //--- B67: required clearance from every hostile town centre.   AFTER:     86  _townBuffer = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_TOWN_BUFFER", 1000];     87  _ringClear  = 600 + _townBuffer;   //--- B67: required clearance from every hostile town centre.     87a //--- B68: minimum NET forward advance toward the front a candidate standoff must give vs the CURRENT HQ.     87b _minAdvance = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_MIN_ADVANCE", 600];    Also add the local to the private[] array on line 26 (append "_minAdvance"):     ...,"_etPos","_etD","_minAdvance"];  (b) Add the rejection at the accept site. Change line 128:   BEFORE:     119 //--- Validate: clear of EVERY enemy-held AND resistance-held town by _ringClear.     120 _clear = true;     121 {     122   if (((_x getVariable "sideID") == _enemyID) || {(_x getVariable "sideID") == _guerID}) then {     123     _etPos = getPos _x;     124     _etD   = sqrt (((_cand select 0) - (_etPos select 0))^2 + ((_cand select 1) - (_etPos select 1))^2);     125     if (_etD < _ringClear) then {_clear = false};     126   };     127 } forEach towns;     128 if (_clear && {!surfaceIsWater _cand}) then {_destPos = _cand};   AFTER (add the netAdvance term to the same accept condition; short-circuits so dis… [truncated — full sketch regenerable]

Soak test: Soak observability (grep the server RPT for the existing AICOMSTAT|MHQRELOC stream): 1) PRIMARY behavioural signal -- after deploy, the move must be meaningful. On a TRIGGER/DEPLOYED pair compute (TRIGGER frontDist) vs (DEPLOYED pos distance front): with the floor active you should NO LONGER see near-zero back= values on TRIGGER lines (line 148 logs back=). Pre-patch soak shows tiny back= followed by a DEPLOYED pos almost equal to the prior HQ; post-patch those degenerate moves are gone. 2) ABORT-rate signal -- count AICOMSTAT|v1|MHQRELOC|<side>|...|ABORT|no-buffer-clear-standoff lines… [truncated — full sketch regenerable]

mhq-redrive-unstuck-nudge

Re-issue doMove every monitor tick + add a sub-window unstuck nudge so the MHQ stops stranding mid-route

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_MHQReloc.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_MHQ_NUDGE_SECS = 45 (Init_CommonConstants.sqf (AICOM MHQ-reloc block, after WFBE_C_AICOM_MHQ_STUCK_SECS at line 249))
  • new const WFBE_C_AICOM_MHQ_NUDGE_TURN = 25 (Init_CommonConstants.sqf (AICOM MHQ-reloc block, after WFBE_C_AICOM_MHQ_NUDGE_SECS))
GOAL: the MHQ drive currently issues ONE doMove at launch (line 220) and the 5s monitor (lines 229-262) only watches arrive/stuck/deadline. A CARELESS+FULL A2 PFM driver wedges on terrain and is then left idling until STUCK_SECS(210) or DEADLINE(600) fires, deploying mid-route far short. Fix = (a) re-issue doMove every tick (idempotent re-path), (b) a short non-progress sub-window that does a brief reverse/velocity-zero + corner-turn nudge BEFORE the stuck/deadline rails fire. All commands are A2-OA 1.64 (doMove/setVelocity/setDir/getDir/distance on an object).  ================================================================ PART A -- Init_CommonConstants.sqf: 2 new constants in the AICOM MHQ block. After line 249 (WFBE_C_AICOM_MHQ_STUCK_SECS), insert:    if (isNil "WFBE_C_AICOM_MHQ_NUDGE_SECS") then {WFBE_C_AICOM_MHQ_NUDGE_SECS = 45}; //--- s of no >25m progress before a brief unstuck nudge (must be < STUCK_SECS so it fires first).   if (isNil "WFBE_C_AICOM_MHQ_NUDGE_TURN") then {WFBE_C_AICOM_MHQ_NUDGE_TURN = 25}; //--- deg added to MHQ heading on a nudge to break a corner wedge. 0 = velocity-zero + re-doMove only.  ================================================================ PART B -- AI_Commander_MHQReloc.sqf  B1) Extend the Spawn private[] (line 152). Add TWO read-from-namespace locals + ONE rate-limit timestamp. The constants are read ONCE before the loop (publicVariable is NOT used here; these are mission-local constants set in Init, so a plain getVariable with a default is reliable -- no JIP/timeout concern). ADD to the private list:  "_nudgeSecs","_nudgeTurn","_lastNudge"  B2) Seed the new locals just after _reason is set (after line 227, before the while on 229):   ----- AFTER (new) -----   _reason      = "arrive";   _nudgeSecs   = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_NUDGE_SECS", 45];   _nudgeTurn   = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_NUDGE_TURN", 25];   _lastNudge   = 0;   //--- last nudge time; 0 = none yet.  B3) Inside the loop, change the stuck block (current lines 237-240) to (1) re-issue doMove every tick and (2) fire a rate-limited nudge in the sub-window. Note _stuckSecs is read at line 73 and passed in as _this select 7, so it IS in scope.    ----- BEFORE (lines 237-240) -----   if (!_done) then {       if (_curD < (_lastClose - 25)) then {_lastClose = _curD; _lastImprove = time};       if ((time - _lastImprove) > _stuckSecs) then {_done = true; _reason = "stuck"};   };    ----- AFTER -----   if (!_done) then {       if (_curD < (_lastClose - 25)) then {_lastClose = _curD; _lastImprove = time};        //--… [truncated — full sketch regenerable]

Soak test: Soak grep on the server RPT: AICOMSTAT|v1|MHQRELOC|...|NUDGE| -- presence proves the sub-window fired on a wedged hull. Success signal = a NUDGE line followed (same _sideText, within STUCK_SECS) by a |DEPLOYED|reason=arrive line at/near the intended standoff, INSTEAD of the pre-B69 |DEPLOYED|reason=stuck or |TELEPORT_STEP mid-route. Quick A/B: grep counts of reason=arrive vs reason=stuck vs deadline-teleport across a 4h GUER soak rotation -- arrive share should rise, stuck/teleport share should fall. Observable in-game: the MHQ visibly jiggles/re-orients and resumes driving when it catches a c… [truncated — full sketch regenerable]

mhq-reloc-avoid-human-front-overrun

MHQ relocation: treat nearby friendly HUMAN players' contact as a route-danger signal (don't creep the base into a human-contested front)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_MHQReloc.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_MHQ_HUMAN_FRONT_DIST = 900 (Common/Init/Init_CommonConstants.sqf (AICOM MHQ block, insert after line 246 WFBE_C_AICOM_MHQ_ENEMY_CLEAR; set =0 to disable the human-front defer gate))
GOAL: After the two existing enemy-clear gates pass (dest + source) but BEFORE single-flight is claimed (line 147), add one more gate that DEFERS this relocation interval if a friendly HUMAN player is in active COMBAT contact near the planned destination. Pure read-only scan, same nearEntities/forEach idiom already used on lines 140 & 143. Only ever skips the eval (exitWith), retries next 180s INTERVAL — never touches the committed drive/deploy lifecycle, never alters combat behaviour.  === CHANGE 1: Init_CommonConstants.sqf — new nil-guarded constant in the MHQ block ===  BEFORE (lines 246-247):     if (isNil "WFBE_C_AICOM_MHQ_ENEMY_CLEAR")       then {WFBE_C_AICOM_MHQ_ENEMY_CLEAR       = 700};  //--- m: do NOT mobilize/deploy if an enemy is within this of the current HQ or the destination.     if (isNil "WFBE_C_AICOM_MHQ_ARRIVE_DIST")       then {WFBE_C_AICOM_MHQ_ARRIVE_DIST       = 400};  //--- m: MHQ within this of the destination = arrived -> deploy.  AFTER (insert one line between them):     if (isNil "WFBE_C_AICOM_MHQ_ENEMY_CLEAR")       then {WFBE_C_AICOM_MHQ_ENEMY_CLEAR       = 700};  //--- m: do NOT mobilize/deploy if an enemy is within this of the current HQ or the destination.     if (isNil "WFBE_C_AICOM_MHQ_HUMAN_FRONT_DIST")  then {WFBE_C_AICOM_MHQ_HUMAN_FRONT_DIST  = 900};  //--- m: DEFER relocation if a friendly HUMAN player is in COMBAT near the destination (don't creep base into a human-contested front). 0 = disable.     if (isNil "WFBE_C_AICOM_MHQ_ARRIVE_DIST")       then {WFBE_C_AICOM_MHQ_ARRIVE_DIST       = 400};  //--- m: MHQ within this of the destination = arrived -> deploy.  === CHANGE 2: AI_Commander_MHQReloc.sqf — new gate after the existing enemy-clear gates ===  Add "_hfDist" and "_hNear" to the top-level private[] on line 26 (the supervisor scope, NOT the Spawn scope). e.g. append:     ...,"_etPos","_etD","_hfDist","_hNear"];  BEFORE (lines 142-147):     _eNear = false;     { if (side _x == _enemySideObj && {alive _x}) then {_eNear = true} } forEach (_hqPos nearEntities [["Man","Car","Tank","Air"], _enemyClear]);     if (_eNear) exitWith {};      //--- All gates passed: claim single-flight + LAUNCH the lifecycle Spawn.     _logik setVariable ["wfbe_mhqreloc_active", true];  AFTER (insert the new gate block immediately before the "All gates passed" comment):     _eNear = false;     { if (side _x == _enemySideObj && {alive _x}) then {_eNear = true} } forEach (_hqPos nearEntities [["Man","Car","Tank","Air"], _enemyClear]);     if (_eNear) exitWith {};      //--- B68 (mhq-reloc-avoid-human-front-overrun): HUMAN-FRONT DEFER. The … [truncated — full sketch regenerable]

Soak test: Soak grep: new diag_log marker AICOMSTAT|v1|MHQRELOC|<side>|<min>|DEFER|human-front|dist=900|dest=... fires each time the gate blocks a relocation. Validate by tailing the RPT for MHQRELOC|.*|DEFER|human-front during a populated match — it should appear when (and only when) a friendly player is fighting near the would-be destination, and the matching MHQRELOC|.*|TRIGGER for that side should be absent that interval and appear a later interval once the human front clears. Boot-smoke gate first: deploy and confirm ErrInExpr=0 / MISSINIT present (catches any A2-OA parse error in the new hunk… [truncated — full sketch regenerable]

mhq-route-contact-deescalate

Sample the route for enemies each tick and de-escalate the MHQ driver near contact instead of driving blind toward the front

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_MHQReloc.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_MHQ_ROUTE_DEESC = 1 (Common/Init/Init_CommonConstants.sqf (AICOM MHQ-relocate block, after line 249 WFBE_C_AICOM_MHQ_STUCK_SECS))
  • new const WFBE_C_AICOM_MHQ_ROUTE_GRACE = 12 (Common/Init/Init_CommonConstants.sqf (AICOM MHQ-relocate block, after WFBE_C_AICOM_MHQ_ROUTE_DEESC))
GOAL: inside the existing 5s drive-monitor loop (AI_Commander_MHQReloc.sqf:229-262), sample the area around the MOVING MHQ for live enemies once per tick. On contact -> de-escalate the driver group (AWARE / NORMAL) so it can react/evade instead of barrelling in CARELESS+BLUE with AUTOTARGET off; AND push the stuck/deadline timers forward by ROUTE_GRACE so a brief contact pause doesn't instantly trip 'stuck'/'deadline' deploy. When contact clears, restore the ORIGINAL careless transit profile so the MHQ never stalls (re-wakes / keeps moving -- never frozen).  The driver group _drvGrp, _enemyClear (arg idx 8) and _enemySide (arg idx 9) are ALL already in this Spawn's scope, and the loop already mutates _lastImprove and _t0 -- so no new plumbing is needed beyond two tracker locals.  === EDIT 1: Init_CommonConstants.sqf -- add the two knobs (sibling-knob pattern, after line 249) ===   if (isNil "WFBE_C_AICOM_MHQ_STUCK_SECS")        then {WFBE_C_AICOM_MHQ_STUCK_SECS        = 210}; + //--- B68 ROUTE-CONTACT DE-ESCALATE (default-ON): sense enemies around the MOVING MHQ each tick; + //--- near contact, flip the careless transit driver to AWARE/NORMAL so it reacts/evades, and grant + //--- the stuck/deadline timers a short grace so a brief pause doesn't instantly trip a deploy. Restore + //--- careless transit when clear (never freeze). Set WFBE_C_AICOM_MHQ_ROUTE_DEESC = 0 to disable. + if (isNil "WFBE_C_AICOM_MHQ_ROUTE_DEESC")       then {WFBE_C_AICOM_MHQ_ROUTE_DEESC       = 1};   //--- 1 = ON. + if (isNil "WFBE_C_AICOM_MHQ_ROUTE_GRACE")       then {WFBE_C_AICOM_MHQ_ROUTE_GRACE       = 12};  //--- s pushed onto stuck/deadline clocks per contact tick.  === EDIT 2a: AI_Commander_MHQReloc.sqf -- declare the new state in the Spawn's private[] (line 152) === Add _routeDeesc, _routeGrace, _inContact to the existing private array so they don't leak to the global namespace.   ...,"_finAng","_structClass"]); becomes ...,"_finAng","_structClass","_routeDeesc","_routeGrace","_inContact"]);  === EDIT 2b: AI_Commander_MHQReloc.sqf -- init trackers, just BEFORE the loop (after line 227 _reason="arrive") ===   _done        = false;   _reason      = "arrive"; + _routeDeesc  = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_ROUTE_DEESC", 1]; + _routeGrace  = missionNamespace getVariable ["WFBE_C_AICOM_MHQ_ROUTE_GRACE", 12]; + _inContact   = false;   //--- latched de-escalation state; restore transit profile exactly once on clear.  === EDIT 2c: AI_Commander_MHQReloc.sqf -- the per-tick sweep, inside the loop AFTER the mhq-lost guard (line 231) and BEFORE the arrive check (line 23… [truncated — full sketch regenerable]

Soak test: SOAK GREP: tail the server RPT for the two new tags. CONTACT: grep 'MHQRELOC.*ROUTE_CONTACT' -> appears when an MHQ drives within 700m of a live enemy mid-relocation (driver de-escalated). RESUME: grep 'MHQRELOC.*ROUTE_CLEAR' -> appears when it breaks contact and resumes careless transit. PAIRING CHECK: every ROUTE_CONTACT for a side should eventually be followed by a ROUTE_CLEAR OR a terminal DEPLOYED/RELEASE/TELEORT line for that same relocation -- a dangling ROUTE_CONTACT with no follow-up means the MHQ got stuck de-escalated (investigate). HEALTH METRIC: compare count of 'MHQRELOC.*mhq-los… [truncated — full sketch regenerable]

pending-slot-timeout-reaper

Add a pending-founding-slot timeout reaper so a lost HC dispatch ack can't pin founding below target forever

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Teams.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_PENDING_TIMEOUT = 270 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (isNil-guarded AICOM block, insert right after WFBE_C_AICOM_TEAM_SIZE_MAX at line 307; default 270 = 3 * WFBE_C_AI_COMMANDER_TEAMS_INTERVAL[=90]))
GOAL: when wfbe_aicom_pending has been stuck >0 for longer than WFBE_C_AICOM_PENDING_TIMEOUT with no decrement (a lost server->HC->server ack: HC hiccup / JIP / desync / HC died mid-create), force-free ONE leaked slot per cycle so (_foundedTeams + _pending) >= _target (the exit at L156) stops blocking founding forever.  KEY REUSE: the existing B35 probe (L118-125) already maintains wfbe_aicom_pending_since = the time the CURRENT continuous pending>0 window started (set to time when it first goes >0; reset to -1 when pending hits 0). That stamp's AGE is exactly the "no-decrement-in-this-window" signal we need. A real created/ended ack decrements pending; if that drops it to 0 the probe resets the stamp to -1, so a still-positive stamp older than the timeout == a genuinely stuck slot. After reaping we RE-STAMP to time so at most ONE slot reaps per TEAMS_INTERVAL cycle (no over-correction burst).  === FILE 1: Init_CommonConstants.sqf === Insert in the isNil-guarded AICOM block, right after L307 (WFBE_C_AICOM_TEAM_SIZE_MAX):    if (isNil "WFBE_C_AICOM_TEAM_SIZE_MAX") then {WFBE_C_AICOM_TEAM_SIZE_MAX = 12}; + //--- B69 pending-slot timeout reaper (claude-gaming): a lost server->HC->server team-create/ended ack + //--- (HC hiccup / JIP / desync / HC died mid-create) leaves wfbe_aicom_pending incremented forever, so + //--- (_foundedTeams + _pending) >= _target pins founding below target while banked funds pile up. After + //--- this many seconds of CONTINUOUS pending>0 with no decrement, AI_Commander_Teams reaps ONE stale slot + //--- per cycle. Must stay comfortably above worst-case ack latency under heavy HC load; 270 = 3x the 90s + //--- TEAMS_INTERVAL. Set very high (e.g. 1e9) to effectively disable. Rollback: raise the value. + if (isNil "WFBE_C_AICOM_PENDING_TIMEOUT") then {WFBE_C_AICOM_PENDING_TIMEOUT = 270};  === FILE 2: AI_Commander_Teams.sqf === Replace the existing probe block (L118-125). BEFORE:    if (_pending > 0) then {       private ["_pendSince"];       _pendSince = _logik getVariable ["wfbe_aicom_pending_since", -1];       if (_pendSince < 0) then {_pendSince = time; _logik setVariable ["wfbe_aicom_pending_since", _pendSince]};       diag_log ("AICOMSTAT|v2|EVENT|" + (str _side) + "|" + str (round (time / 60)) + "|HCDISPATCH|pending=" + str _pending + "|founded=" + str _foundedTeams + "|target=" + str _target + "|pendingAgeSec=" + str (round (time - _pendSince)));   } else {       _logik setVariable ["wfbe_aicom_pending_since", -1];   };  AFTER (probe kept verbatim; reaper appended inside the same _pending>0 branch, after the diag_log so the … [truncated — full sketch regenerable]

Soak test: Greppable: a new TEAM_PENDING_REAPED AICOMSTAT line fires in the server RPT only when a slot is actually reaped, e.g. AICOMSTAT|v2|EVENT|WEST|<min>|TEAM_PENDING_REAPED|pending=<n>|founded=<f>|target=<t>|ageSec=270, plus a matching WARNING AI_Commander_Teams.sqf: [WEST] reaped 1 stale pending slot. SOAK PROCEDURE: (a) BASELINE healthy run - grep the RPT: HCDISPATCH lines should show pendingAgeSec staying SMALL (resets to ~0 each time pending clears) and ZERO TEAM_PENDING_REAPED lines (no false reaps under normal HC latency). (b) INDUCED-LEAK test - temporarily lower WFBE_C_AICOM_PENDING_T… [truncated — full sketch regenerable]

rally-break-off-depot-hold-attrition-trigger

Break-off-and-rally trigger inside the depot-hold capture loop (fighting withdrawal instead of grind-to-extinction)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_WITHDRAW_FRAC = 0.40 (Common/Init/Init_CommonConstants.sqf (AICOM "ASSAULT FINISH tunables" block, right after WFBE_C_AICOM_CAPTURE_MAXPASSES at ~line 332). Pattern: if (isNil "WFBE_C_AICOM_WITHDRAW_FRAC") then {WFBE_C_AICOM_WITHDRAW_FRAC = 0.40};)
GOAL: a damaged-but-viable HC commander team that is being OUT-FOUGHT at a depot breaks off (returns to 'towns' mode -> AssignTowns re-tasks it next cycle, re-engaging on proximity) instead of being annihilated one man at a time and then re-founded from scratch.  === EDIT 1 of 3: Init_CommonConstants.sqf (~line 332, in the AICOM "ASSAULT FINISH / WAVE-1 break tunables" block, right after the WFBE_C_AICOM_CAPTURE_MAXPASSES line) ===  BEFORE:   if (isNil "WFBE_C_AICOM_CAPTURE_MAXPASSES") then {WFBE_C_AICOM_CAPTURE_MAXPASSES = 2};  AFTER (append one line):   if (isNil "WFBE_C_AICOM_CAPTURE_MAXPASSES") then {WFBE_C_AICOM_CAPTURE_MAXPASSES = 2};   //--- WITHDRAW_FRAC (rally-break-off): if a contested depot-hold team's live strength falls BELOW this   //--- fraction of its self-baselined founding peak WHILE still out-fought (res-near>0), it breaks off and   //--- returns to 'towns' mode (AssignTowns re-tasks it). Low (0.40) = only bail a clearly-losing assault,   //--- never a winnable one. Vehicle-punch teams (Tank / attack-heli) are EXEMPT. Rollback: set to 0 to disable.   if (isNil "WFBE_C_AICOM_WITHDRAW_FRAC") then {WFBE_C_AICOM_WITHDRAW_FRAC = 0.40};  === EDIT 2 of 3: Common_RunCommanderTeam.sqf, founding peak baseline (~line 66, right after the existing wfbe_aicom_hc / wfbe_queue stamps) ===  BEFORE:   _team setVariable ["wfbe_aicom_hc", true, true];   //--- brain: do not Produce/waypoint this one directly.   _team setVariable ["wfbe_queue", [], false];  AFTER (insert one stamp; broadcast true to mirror the adjacent hc write):   _team setVariable ["wfbe_aicom_hc", true, true];   //--- brain: do not Produce/waypoint this one directly.   _team setVariable ["wfbe_queue", [], false];   //--- rally-break-off: self-baselined founding PEAK strength (no founding-size plumbing needed). The depot-hold   //--- break-off test compares live count against this. count _units = the just-created roster (alive at founding).   _team setVariable ["wfbe_aicom_peakalive", count _units, true];  === EDIT 3 of 3: Common_RunCommanderTeam.sqf, the depot-center HOLD while-loop (:882-898) === Add the break-off test as the FIRST statement each pass, AFTER _resNear is computed (so we know we're actively out-fought) but BEFORE the straggler re-press. On trigger: clear bookkeeping with the SAME idiom as the on-capture re-task block (:914-918), latch _captureDone, and break the loop.  First, just ABOVE the while-loop header at :881, compute the vehicle-punch EXEMPT flag once (reuses the existing isKindOf "Tank" + attack-heli idiom already in this file at :130):    _resNear = 1;   //--- r… [truncated — full sketch regenerable]

Soak test: Greppable diag line: search the HC/server RPT (or AICOMLog sink) for "BREAK-OFF at" - emitted once per team that withdraws, with live/peak/frac/res-near values, e.g. grep "BREAK-OFF at" *.rpt. SOAK PASS CRITERIA over a multi-hour GUER/CTI match: (a) the line appears at a low, sane rate (a handful per side per match, NOT every contested town - high frequency means WITHDRAW_FRAC is too eager, lower it / verify the _resNear>0 gate); (b) NO "BREAK-OFF" line ever names a team whose vehicles include a Tank or attack heli (exemption working); (c) cross-check the wiki dispatch:arrive churn baseline … [truncated — full sketch regenerable]

rally-mode-bounding-withdrawal-executor

New 'rally' order mode in the HC executor: bounding fall-back that fights while withdrawing, never a hold-and-die SAD

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_RunCommanderTeam.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RALLY_ARRIVE_PAD = 20 (Common/Init/Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AICOM_ASSAULT_SAD ~line 322) — OPTIONAL, only if you decouple the rally arrival radius from CAPTURE_RANGE; skippable, see sketch)
GOAL: teach the HC executor a THIRD order mode "rally" = bounding fall-back (fast MOVE, never SAD) that fights while it withdraws, then auto re-engages on arrival so it never idles at the rally point. This is the executor that CONSUMES a "rally" order; the Strategy-side producer that BROADCASTS it is the sibling item (graceful-withdrawal-evaluator-replaces-hc-blindspot) — this item only makes the executor understand the mode.  CRITICAL CASING NOTE (verified in source): the arrival branch compares `_mode == "defense"` with an exact-case `==` (Common_RunCommanderTeam.sqf:668). Any producer of a rally order MUST broadcast the mode literal lowercase `"rally"`, exactly as the executor matches below.  -------------------------------------------------------------------- EDIT 1 of 3 — header mode comment (Common_RunCommanderTeam.sqf:10) -------------------------------------------------------------------- BEFORE (:10):   wfbe_aicom_order = [seq, mode, pos]   (mode: "towns-target" | "defense") AFTER:   wfbe_aicom_order = [seq, mode, pos]   (mode: "towns-target" | "defense" | "rally")   //--- "rally" = bounding withdrawal: fast AWARE/RED/COLUMN MOVE to pos (returns   //--- fire + uses cover en route, never a stand-and-die SAD), then on arrival   //--- re-tasks itself back to "towns" so it re-engages (never idles at rally).  -------------------------------------------------------------------- EDIT 2 of 3 — fresh-order leg (inside `if (_seq != _lastSeq)`, AFTER the road-march/foot if/else that closes at :620, BEFORE the block's closing `};` at :621) -------------------------------------------------------------------- The existing fresh-order block already lays a FAST AWARE/RED/COLUMN/FULL MOVE to _dest for every mode (road-march :584-607 for vehicles >700m, foot fast-transit :618 otherwise). That IS the fighting withdrawal leg. For "rally" we only need to remember we are rallying so the arrival branch (Edit 3) re-tasks instead of laying a SAD.  INSERT:   //--- RALLY MODE (bounding withdrawal). The road-march / foot-transit above already laid a   //--- FAST AWARE/RED/COLUMN MOVE to _dest = exactly the fall-back leg we want (returns fire +   //--- uses cover en route, never SAD). Stamp a rally flag so the arrival branch re-tasks to   //--- "towns" instead of the COMBAT/WEDGE assault SAD. A2-safe: plain setVariable, no A3 cmds.   if (_mode == "rally") then {       _team setVariable ["wfbe_aicom_rallying", true];       ["INFORMATION", Format ["Common_RunCommanderTeam.sqf: [%1] team [%2] order #%3 RALLY bounding-withdrawal to rally pos.", _side, _team, _seq]] Call WFBE_CO… [truncated — full sketch regenerable]

Soak test: Greppable soak signals (emitted by the diag_log lines in the sketch): 1. AICOMSTAT|v2|EVENT|...|RALLY_FALLBACK|team=...|seq=... — a team consumed a fresh "rally" order and started the bounding withdrawal. 2. AICOMSTAT|v2|EVENT|...|RALLY_REENGAGE|team=... — that team reached the rally pos and re-tasked to towns. PASS over a soak: every RALLY_FALLBACK is followed by a matching RALLY_REENGAGE within ~2 min (team did NOT idle at rally), and NO assault/defense SAD log appears on a rally seq (confirms the casing is right and the rally branch never fell through). OBSERVABLE (live spectate/playte… [truncated — full sketch regenerable]

relief-eta-and-strength-and-scale

Relief sends the nearest team by straight-line leader distance regardless of strength or mobility, and answers only ONE attacked town — make it ETA/strength-aware and let it scale to multi-town pushes

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4 (Init_CommonConstants.sqf (AICOM block, beside WFBE_C_AI_COMMANDER_RELIEF_MAX at line 273; isNil-guarded so a missing const is safe))
  • new const WFBE_C_AICOM_RELIEF_REACH_FACTOR = 2 (Init_CommonConstants.sqf (AICOM block, beside line 273; isNil-guarded; effective distance divisor for a mounted reliever — mirrors the ASSAULT_REACH_MOUNTED/FOOT idiom))
GOAL: make the reactive-defense reliever pick (AI_Commander_Strategy.sqf:405-452) ETA-aware, strength-gated, and able to answer up to 2 simultaneously-attacked towns — while keeping relief a minority of the army (B68 attack-bias) and AI count unchanged. Three sub-changes, all reusing existing in-file A2-OA-safe idioms.  REUSED IDIOMS (already in this mission, A2-OA-1.64 verified): - mounted/drivable test  AI_Commander_AssignTowns.sqf:275 -> (vehicle _x)!=_x && (vehicle _x) isKindOf "LandVehicle" && canMove (vehicle _x) - MBT/attack-heli test    AI_Commander.sqf:411-412 / Produce.sqf:191-193 -> _v isKindOf "Tank"; (_v isKindOf "Helicopter") && getNumber(... transportSoldier)==0 No A3 commands (no isEqualType/isEqualTo/typed params); only count/select/isKindOf/getNumber/distance.  ------------------------------------------------------------------ PART 0 -- constants (Init_CommonConstants.sqf, AICOM block at line 273) AFTER the existing line 273 (WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;) add:   if (isNil "WFBE_C_AICOM_RELIEF_MIN_ALIVE")    then {WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4};    //--- skip a candidate below this many alive UNLESS it owns an MBT/attack-heli. 0 disables the floor. Rollback: 0.   if (isNil "WFBE_C_AICOM_RELIEF_REACH_FACTOR") then {WFBE_C_AICOM_RELIEF_REACH_FACTOR = 2};  //--- a mounted reliever's effective distance is divided by this (ETA weight). 1 disables. Rollback: 1. (WFBE_C_AI_COMMANDER_RELIEF_MAX stays hard-assigned =1; the SCALE in Part 3 lets it reach 2 only when 2 towns are actually attacked.)  ------------------------------------------------------------------ PART 1 -- fix the stale default literal at line 410 (so a missing const cannot silently double relief) BEFORE:   if (_relieved < (missionNamespace getVariable ["WFBE_C_AI_COMMANDER_RELIEF_MAX", 2])) then { AFTER (introduce a scaled cap once, above the forEach _attacked at line 408; default literal -> 1):   //--- B69 relief-scale: answer up to RELIEF_MAX towns, but never more than the number actually attacked, hard-capped at 2 so offence stays the majority (AI count unchanged).   _reliefMax = ((missionNamespace getVariable ["WFBE_C_AI_COMMANDER_RELIEF_MAX", 1]) max 1) min 2;   _reliefMax = _reliefMax min (count _attacked);   ... then line 410 becomes:   if (_relieved < _reliefMax) then {  ------------------------------------------------------------------ PART 2 -- ETA-weight + strength-floor inside the nearest-team scan (lines 417-426) BEFORE (current raw straight-line pick):   {       _team = _x;       if (!isNull _team && {!isPlayer (leader _team)} && {({alive _x} count (units… [truncated — full sketch regenerable]

Soak test: SOAK GREP: grep the server RPT for AICOMSTAT|v1|EVENT|.*|RELIEF| and confirm the new appended fields are present and sane: alive= should be >= 4 on the vast majority of relief events (a alive= 2 or 3 line must co-occur with bigVeh=true, i.e. the vehicle-exempt fallback, OR be a rare all-below-floor fallback — never a routine remnant dispatch). Confirm reliefMax=2 lines appear ONLY in windows where two towns are simultaneously wfbe_active (cross-check the two adjacent |RELIEF|<town> events sharing the same minute stamp); reliefMax=1 otherwise. OBSERVABLE BEHAVIOUR: in a live watch… [truncated — full sketch regenerable]

relief-eta-mounted-reach

Reliever chosen by raw straight-line leader distance ignores mobility — a far MBT/mounted team loses to a near foot team that arrives too late

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_MOUNTED_REACH_BONUS = 2.0 (Common/Init/Init_CommonConstants.sqf (AICOM block, alongside WFBE_C_AICOM_ASSAULT_REACH_MOUNTED at line 423))
PROBLEM ANCHOR — AI_Commander_Strategy.sqf:417-426 (the reliever candidate loop). The pick is by raw Euclidean leader distance only:    416   _freeD = 1e9;   417   {   418       _team = _x;   419       if (!isNull _team && {!isPlayer (leader _team)} && {({alive _x} count (units _team)) > 0}) then {   420           if ((toLower (_team getVariable ["wfbe_teammode", "towns"])) == "towns") then {   423               if (isNull (_team getVariable ["wfbe_aicom_relief", objNull]) && {!(_team getVariable ["wfbe_aicom_strike", false])}) then {   424                   _d = (leader _team) distance _town;   425                   if (_d < _freeD) then {_freeD = _d; _free = _team};   426               };  A fast mounted/armour team at 1500m loses to a foot team at 800m across a ridge that arrives far later.  FIX — replace ONLY lines 424-425 with an ETA-weighted EFFECTIVE distance. Reuse the EXACT proven A2-safe mounted-classify idiom already live at AI_Commander_AssignTowns.sqf:275 (isKindOf "LandVehicle" + canMove + alive-driver-capacity), extended to also reward air teams. The bonus divides effective distance so a mounted team within ~2x a foot team's range wins.  --- BEFORE (Strategy.sqf:424-425) ---                     _d = (leader _team) distance _town;                     if (_d < _freeD) then {_freeD = _d; _free = _team};  --- AFTER ---                     _d = (leader _team) distance _town;                     //--- relief-eta-mounted-reach: weight raw leader distance by mobility so a far MOUNTED/AIR                     //--- team that physically arrives sooner beats a near FOOT team. Same classify idiom as                     //--- AssignTowns.sqf:275 (any alive unit in a drivable land vehicle = mounted), plus AIR.                     _reliefMounted = false;                     {                         if (!_reliefMounted && {alive _x} && {(vehicle _x) != _x}                             && {((vehicle _x) isKindOf "LandVehicle") || {(vehicle _x) isKindOf "Air"}}                             && {canMove (vehicle _x)}) then {_reliefMounted = true};                     } forEach (units _team);                     _reliefBonus = missionNamespace getVariable ["WFBE_C_AICOM_RELIEF_MOUNTED_REACH_BONUS", 2.0];                     if (_reliefBonus < 1) then {_reliefBonus = 1};               //--- guard: never inflate distance                     _dEff = if (_reliefMounted) then {_d / _reliefBonus} else {_d};                     if (_dEff < _freeD) then {_freeD = _dEff; _free = _team};  NEW CONSTANT — Init_CommonConstants.sqf, in the AICOM block right after line 423 (WF… [truncated — full sketch regenerable]

Soak test: Add one diag_log inside the if (!isNull _free) block (after line 442 _relieved increment), so each relief dispatch records whether the winner was mounted and its effective vs raw distance: diag_log ("AICOMSTAT|v1|RELIEF_PICK|" + _sideText + "|" + str (round (time / 60)) + "|town=" + (_town getVariable ["name","town"]) + "|mounted=" + (str (_free getVariable ["__last_relief_mounted", false])) + "|dEff=" + str (round _freeD)); (set _free setVariable ["__last_relief_mounted", _reliefMounted] on the winning branch, or simpler: re-derive once for the chosen team). SOAK CHECK: grep the serve… [truncated — full sketch regenerable]

relief-multi-town-scale

Allow relief to scale to >1 town under a coordinated multi-town attack via a bounded fraction, instead of hard RELIEF_MAX=1

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

  • new const WFBE_C_AICOM_RELIEF_FRAC = 0.34 (Init_CommonConstants.sqf (RELIEF block, insert right after the WFBE_C_AICOM_RELIEF_HOLD line ~319))
  • new const WFBE_C_AICOM_RELIEF_MAX_HARD = 2 (Init_CommonConstants.sqf (RELIEF block, insert right after the WFBE_C_AICOM_RELIEF_HOLD line ~319))
  • new const WFBE_C_AICOM_RELIEF_STRENGTH_RADIUS = 200 (Init_CommonConstants.sqf (RELIEF block, insert right after the WFBE_C_AICOM_RELIEF_HOLD line ~319))
GROUND-TRUTH CORRECTION to the finding's evidence: the live B68 cap is NOT an inline literal at Strategy.sqf:410. Line 410 reads the constant `WFBE_C_AI_COMMANDER_RELIEF_MAX` (inline default 2), and that constant is DEFINED =1 at Init_CommonConstants.sqf:273. So the effective live per-tick cap is 1. `_attacked` (Strategy.sqf:405-406) is already the list of own active(under-attack) towns -> `_nAttacked = count _attacked` needs NO new scan. The reliever selection (417-426) is nearest-by-leader-distance with NO strength check and NO COMBAT-skip. The COMBAT-skip idiom (`behaviour x != "COMBAT"`) and the enemy-count idiom (`nearEntities [["Man",...],R]`) BOTH already exist in this same file (lines 466 and 607/610) -> reuse verbatim, all A2-OA-1.64-native.  ================================================================== PART A - Init_CommonConstants.sqf : add 3 nil-guarded constants ================================================================== INSERT immediately AFTER line 319 (the RELIEF_HOLD line), inside the same RELIEF block:    + //--- RELIEF MULTI-TOWN SCALE (relief-multi-town-scale): under a coordinated multi-town raid,   + //--- relieve a BOUNDED FRACTION of the own towns under attack this tick instead of a flat 1.   + //--- reliefMax = (1 max round(nAttacked*FRAC)) min RELIEF_MAX_HARD. nAttacked=1 -> exactly 1   + //--- (identical to B68). Re-tasks EXISTING towns-mode teams only (no new founds -> AI count flat).   + if (isNil "WFBE_C_AICOM_RELIEF_FRAC")            then {WFBE_C_AICOM_RELIEF_FRAC = 0.34};            //--- relieve ~1/3 of attacked towns. 0 -> always 1.   + if (isNil "WFBE_C_AICOM_RELIEF_MAX_HARD")        then {WFBE_C_AICOM_RELIEF_MAX_HARD = 2};           //--- hard ceiling; keeps offense the majority. Set 1 to fully restore B68.   + if (isNil "WFBE_C_AICOM_RELIEF_STRENGTH_RADIUS") then {WFBE_C_AICOM_RELIEF_STRENGTH_RADIUS = 200};  //--- m: enemy-Man count radius around the town for the reliever-strength gate.  (WFBE_C_AI_COMMANDER_RELIEF_MAX=1 at line 273 is LEFT UNTOUCHED; it now only governs the single-town path via the `1 max` floor below, so rollback is: set RELIEF_MAX_HARD=1.)  ================================================================== PART B - AI_Commander_Strategy.sqf : scale the cap + strength/COMBAT gate ================================================================== --- B1: compute the scaled per-tick cap. AFTER line 407 (`_relieved = 0;`), BEFORE the `{ ... } forEach _attacked;` loop. `_attacked` is already built at 405-406.      _relieved = 0;   + //--- relief-multi-town-scale: bounded fraction of towns und… [truncated — full sketch regenerable]

Soak test: SOAK GREP: grep "AICOMSTAT|v1|EVENT|.*|RELIEFSCALE|" <rpt> — should appear once per side per Strategy tick (~60s) that has >=1 attacked town. Expect nAtk=1 cap=1 on isolated single-town attacks (proves additive-only / B68-identical), and cap=2 ONLY on ticks where nAtk>=5 (round(5*0.34)=2). Cross-check: on a multi-town raid you should now see >1 RELIEF|<town> line within the SAME minute bucket (str(round(time/60))) for the same side — under B68 you only ever see one per bucket. Observable in-engine: two different own towns simultaneously gaining diverted defenders in one 60s window. Boo… [truncated — full sketch regenerable]

relief-prioritize-human-held-towns

Relief triage: prioritize relieving own towns that friendly HUMAN players are defending/standing in over empty contested towns

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_HUMAN_FIRST = 1 (Common/Init/Init_CommonConstants.sqf (AICOM block, alongside WFBE_C_AI_COMMANDER_RELIEF_MAX at line 273, using the same if (isNil ...) then {...} restart-safe idiom))
  • new const WFBE_C_AICOM_RELIEF_HUMAN_RADIUS = 250 (Common/Init/Init_CommonConstants.sqf (AICOM block, next to the above; metres around town center counted as 'a human is holding this town'))
GOAL: With only WFBE_C_AI_COMMANDER_RELIEF_MAX=1 relief slot per tick, the single consumed-order of `_attacked` decides which contested own-town gets the one reliever. Today that order is arbitrary `forEach towns`. Sort `_attacked` so towns with friendly HUMAN players nearby come first. Pure target-list reorder; no change to RELIEF_MAX, reliever selection (line 424 nearest-by-leader), hold window, or watchdog.  ALL commands below are confirmed in-use in THIS codebase & A2-OA-1.64-safe:   - playableUnits + isPlayer _x + alive _x + (_x distance _pos)  -> MHQReloc.sqf:247-248   - side _x == _side                                            -> Strategy.sqf:607   - hand-rolled insertion sort via select/set/while/for         -> MHQReloc.sqf:96-105 NO A3 commands (no isEqualType/isEqualTo/sort-by-CODE/typed params). `playableUnits` is correct here (we want HUMANS, not AI); it is JIP-safe (engine-maintained list, no publicVariable wait).  --- EDIT 1: Init_CommonConstants.sqf, immediately AFTER line 273 (WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;) --- BEFORE:   273  WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;           //--- punchy-AICOM (Ray 2026-06-17): 2->1 ... AFTER (insert two restart-safe nil-guarded lines):   273  WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;           //--- punchy-AICOM (Ray 2026-06-17): 2->1 ...   +    //--- relief-prioritize-human-held-towns (B68): with only 1 relief slot/tick, sort the contested-own-town list   +    //--- so towns a friendly HUMAN is standing in/defending get the single reliever first. Pure reorder; default-ON, A/B via 1->0.   +    if (isNil "WFBE_C_AICOM_RELIEF_HUMAN_FIRST")  then {WFBE_C_AICOM_RELIEF_HUMAN_FIRST = 1};   //--- 0 disables (legacy arbitrary order). Rollback: 0.   +    if (isNil "WFBE_C_AICOM_RELIEF_HUMAN_RADIUS") then {WFBE_C_AICOM_RELIEF_HUMAN_RADIUS = 250}; //--- metres around town center counted as 'human holding'.  --- EDIT 2: AI_Commander_Strategy.sqf, between line 406 (build _attacked) and line 407 (_relieved = 0;) --- Note: every var used below (_attacked, _side, _town) is ALREADY in the line-18 private[] block, EXCEPT loop locals which we declare local. _attacked is a flat array of town objects -> getPos _x is valid.  BEFORE:   405  _attacked = [];   406  { if ((_x getVariable "sideID") == _sideID && {_x getVariable ["wfbe_active", false]}) then {_attacked = _attacked + [_x]} } forEach towns;   407  _relieved = 0;  AFTER:   405  _attacked = [];   406  { if ((_x getVariable "sideID") == _sideID && {_x getVariable ["wfbe_active", false]}) then {_attacked = _attacked + [_x]} } forEach towns;   +    //--- relief-prioritize-h… [truncated — full sketch regenerable]

Soak test: Greppable: new diag_log line AICOMSTAT|v1|RELIEFSORT| emitted once per side per relief tick when >1 town is contested. In RPT, grep "AICOMSTAT|v1|RELIEFSORT" and confirm: (a) topScore > 0 appears in rounds where a human is defending a contested own-town, and (b) the immediately-following existing AICOMSTAT|v1|EVENT|...|RELIEF|<town> line names the SAME town as the RELIEFSORT topTown (proves the human-held town actually got the reliever, not just got sorted first). Observable in-game: stand a human player inside a friendly town that is under attack (wfbe_active) while a second empty c… [truncated — full sketch regenerable]

relief-reliever-strength-gate

Relief picks the nearest team with no strength check — a 1-2-man remnant gets sent to relieve a town under full assault

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM block, immediately AFTER line 273 WFBE_C_AI_COMMANDER_RELIEF_MAX = 1; isNil-guarded form))
GOAL: Add a minimum-strength floor to the relief reliever-selection loop so a depleted remnant is never diverted to defend a town under full assault. MBT/attack-heli teams stay exempt (the vehicle is the punch). Pure selection change — if nothing clears the floor the town is simply left unrelieved and the candidate team keeps its existing offense order (never a frozen AI).  === ANCHOR (exact live code) === File: AI_Commander_Strategy.sqf, reliever-selection forEach, lines 417-429. The eligibility test is line 419:     if (!isNull _team && {!isPlayer (leader _team)} && {({alive _x} count (units _team)) > 0}) then { The matching constant is WFBE_C_AI_COMMANDER_RELIEF_MAX = 1; at Init_CommonConstants.sqf:273 (the item's "RELIEF_MAX:273"). RELIEF_MAX is read at Strategy.sqf:410. NOTE: the live const name is WFBE_C_AI_COMMANDER_RELIEF_MAX, not the shorthand "RELIEF_MAX" in the finding.  === NEW CONSTANT (Init_CommonConstants.sqf, after line 273) === BEFORE (line 273):     WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;           //--- punchy-AICOM (Ray 2026-06-17): 2->1 ... AFTER (insert directly below 273):     WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;           //--- punchy-AICOM (Ray 2026-06-17): 2->1 ...     //--- B69 (relief-reliever-strength-gate): minimum alive bodies for a team to be diverted to RELIEVE a town.     //--- A lone/2-man remnant accomplishes nothing vs a full assault and wastes the side's only relief slot.     //--- EXEMPT: MBT/attack-heli teams (vehicle = the punch) are eligible regardless of body count. 0 disables.     if (isNil "WFBE_C_AICOM_RELIEF_MIN_ALIVE") then {WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4}; //--- Rollback: 0 (old alive>0 behaviour). (Use the isNil-default constant form, NOT the [name,default] missionNamespace form, per the finding. This matches the existing block idiom at lines 277-288.)  === STRATEGY.sqf EDIT (lines 417-429) === The exemption must classify the team WITHOUT a template var (not in scope here). Use the LIVE units of the team and their assigned vehicles — the same A2-OA-safe classname-literal isKindOf + getNumber transportSoldier idiom already live at Produce.sqf:191-193, AI_Commander.sqf:411-412, Teams.sqf:380-381, and the vehicle-from-unit pattern at AssignTowns.sqf:275.  BEFORE (419, inside the `{ ... } forEach _teams` reliever scan):     {         _team = _x;         if (!isNull _team && {!isPlayer (leader _team)} && {({alive _x} count (units _team)) > 0}) then {             if ((toLower (_team getVariable ["wfbe_teammode", "towns"])) == "towns") then {  AFTER:     {         _team = _x;         if (!isNull _team && {!isPlaye… [truncated — full sketch regenerable]

Soak test: Soak observability — two layers. (A) Behavioural: the existing relief telemetry already emits, at Strategy.sqf:446, diag_log ("AICOMSTAT|v1|EVENT|...|RELIEF|"+townName). With the gate live, grep the RPT for AICOMSTAT|v1|EVENT| lines containing |RELIEF| and confirm the diverted team's alive-count at divert time is >=4 OR it is an MBT/attack-heli team — i.e. no 1-3-man non-veh remnant should ever trigger a RELIEF event. (B) Add a cheap diagnostic when a town is intentionally LEFT unrelieved because nothing cleared the floor (insert just before the } forEach _attacked; close at line 452, … [truncated — full sketch regenerable]

relief-strength-and-scaling

Triage relief by reliever strength + town value and let it scale when multiple own towns are attacked at once

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4 (Init_CommonConstants.sqf (AICOM block, beside RELIEF_HOLD at :319; nil-guarded if (isNil ...) then {...}))
  • new const WFBE_C_AICOM_RELIEF_MAX_CAP = 2 (Init_CommonConstants.sqf (AICOM block, beside :273 RELIEF_MAX; nil-guarded). Soft per-tick ceiling; reached only when 2+ own towns are actually attacked. Set =1 to fully restore B68 single-relief behaviour.)
GROUNDING (all verified against live B68 tree): - Relief block is AI_Commander_Strategy.sqf:405-452. Live line 410 reads WFBE_C_AI_COMMANDER_RELIEF_MAX (in-code default 2); Init_CommonConstants.sqf:273 hard-sets it =1 with NO isNil guard. - Eligibility test at :419; nearest-by-leader-distance pick at :424. NO strength check today. - _attacked is built at :405-406 in arbitrary `towns` order, then `forEach _attacked` at :408 -> a tiny town can grab the one slot before a rich town. - Town-value vars supplyValue + wfbe_town_value are REAL and already read in THIS file (184-188, 312-316) via the [name,0] default form. Reuse them verbatim. - Canonical A2-OA MBT/attack-heli detection lives in AI_Commander_Produce.sqf:190-193: classname isKindOf "Tank"; isKindOf "Helicopter" && getNumber(...>>"transportSoldier")==0. For a LIVE reliever team I test the team's VEHICLE, not a template. - None of the new constant names collide (grep clean).  === CHANGE 0 (constants) — Init_CommonConstants.sqf === BEFORE (:273, unguarded literal, plus note the live read at Strategy:410 uses default 2):   WFBE_C_AI_COMMANDER_RELIEF_MAX = 1;           //--- punchy-AICOM ... 2->1 ... AFTER (keep :273 exactly as-is for rollback parity; ADD two nil-guarded lines, e.g. right after the RELIEF_HOLD line at :319):   if (isNil "WFBE_C_AICOM_RELIEF_MIN_ALIVE") then {WFBE_C_AICOM_RELIEF_MIN_ALIVE = 4}; //--- relief-strength-and-scaling: min alive in a candidate team before it may be SENT as a reliever (skip 2-man scraps). MBT/attack-heli teams EXEMPT. 0 disables the floor.   if (isNil "WFBE_C_AICOM_RELIEF_MAX_CAP") then {WFBE_C_AICOM_RELIEF_MAX_CAP = 2};      //--- relief-strength-and-scaling: soft per-tick relief ceiling; reached only when this many own towns are actually under attack. 1 = restore B68 single-relief. NOTE: do NOT change :273. Instead change the in-code CAP read (Change 2) so RELIEF_MAX stays the floor and RELIEF_MAX_CAP is the soft top.  === CHANGE 1 — value-sort _attacked (insert BETWEEN current :406 and :407) === A2-safe hand-rolled descending insertion sort on (supplyValue + wfbe_town_value). The file already hand-sorts (no `sort` lambda relied on). INSERT after `... } forEach towns;` (:406), before `_relieved = 0;`:   //--- relief-strength-and-scaling: process most-valuable contested town FIRST (was arbitrary `towns` order).   if (count _attacked > 1) then {     private ["_sorted","_a","_av","_bi","_bv"];     _sorted = [];     {       _a = _x;       _av = (_a getVariable ["supplyValue", 0]) + (_a getVariable ["wfbe_town_value", 0]);       _bi = count _sorted;  //--- default:… [truncated — full sketch regenerable]

Soak test: Existing per-divert log already emits: diag_log "AICOMSTAT|v1|EVENT|||RELIEF|" at :446. Soak observation: in a multi-town raid, grep AICOMSTAT|...|RELIEF lines and confirm (a) >1 distinct town can appear within one Strategy tick window (cap scaled), and (b) the relieved town is the higher (supplyValue+wfbe_town_value) of the contested set. ADD one explicit diag at the cap computation for greppability: diag_log ("AICOMSTAT|v1|RELIEFCAP|" + _sideText + "|" + str (round (time/60)) + "|attacked=" + str (count _attacked) + "|cap=" + str _reliefCap). PASS = under a coordinated 2-3… [truncated — full sketch regenerable]

relief-triage-by-value-and-multi

Multi-town defense is unprioritized and capped at one: relief iterates towns in raw order and abandons every contested town after the first

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_MAX_HARD = 2 (Init_CommonConstants.sqf (AICOM block, alongside WFBE_C_AI_COMMANDER_RELIEF_MAX at line 273))
  • new const WFBE_C_AICOM_RELIEF_MULTI_MIN = 3 (Init_CommonConstants.sqf (AICOM block, alongside WFBE_C_AI_COMMANDER_RELIEF_MAX at line 273))
FILE: AI_Commander_Strategy.sqf  — touches only the relief block (currently lines 405-452). All other lines unchanged.  == STEP 0: declare new locals (extend the private[] list at line 18) == Append these names to the existing private[...] array on line 18 (A2-OA: one flat list, no typed params):   ,"_relMax","_relMaxHard","_relMultiMin","_relTriRank","_i2","_j","_tmp","_tval","_tt","_ar"  == STEP 1: BEFORE (current, lines 405-410) ==   _attacked = [];   { if ((_x getVariable "sideID") == _sideID && {_x getVariable ["wfbe_active", false]}) then {_attacked = _attacked + [_x]} } forEach towns;   _relieved = 0;   {       _town = _x;       if (_relieved < (missionNamespace getVariable ["WFBE_C_AI_COMMANDER_RELIEF_MAX", 2])) then {  == STEP 1: AFTER ==   _attacked = [];   { if ((_x getVariable "sideID") == _sideID && {_x getVariable ["wfbe_active", false]}) then {_attacked = _attacked + [_x]} } forEach towns;    //--- relief-triage (B68): sort contested own-towns by value DESC so the richest/hardest town is relieved   //--- FIRST, and raise the cap to RELIEF_MAX_HARD only when RELIEF_MULTI_MIN+ towns are contested at once.   if (count _attacked > 1) then {       //--- A2-OA has NO sort-by-CODE: hand-rolled insertion sort on a parallel [score,town] array.       private ["_ar"];       _ar = [];       {           _town = _x;           //--- reuse the same value signal the spearhead scorer uses (171-188); hardTier 0..4 inline.           _tt = switch (_town getVariable ["wfbe_town_type", ""]) do {               case "TinyTown1":{0}; case "SmallTown1":{1}; case "SmallTown2":{1};               case "MediumTown1":{2}; case "MediumTown2":{2}; case "LargeTown1":{3};               case "LargeTown2":{3}; case "HugeTown1":{4}; case "HugeTown2":{4};               case "PMCAirfield":{2}; default {1};           };           _tval = (_town getVariable ["supplyValue", 0]) + (_town getVariable ["wfbe_town_value", 0]) + _tt;           _ar = _ar + [[_tval, _town]];       } forEach _attacked;       //--- insertion sort DESC by score (element select 0). ~<=30 items @ 60s cadence = negligible.       for "_i2" from 1 to ((count _ar) - 1) do {           _tmp = _ar select _i2;           _j = _i2 - 1;           while {_j >= 0 && {((_ar select _j) select 0) < (_tmp select 0)}} do {               _ar set [_j + 1, _ar select _j];               _j = _j - 1;           };           _ar set [_j + 1, _tmp];       };       //--- rebuild _attacked in priority order (highest value first).       _attacked = [];       { _attacked = _attacked + [_x select 1] } forEach _ar;   };    //--- soft cap: def… [truncated — full sketch regenerable]

Soak test: Soak grep on the live RPT: AICOMSTAT|...|RELIEFTRIAGE|<N>|cap=<K> — emitted once per strategy tick whenever any own town is contested. Assert (1) it appears at all (relief path reached), and (2) cap=2 lines appear ONLY in ticks where N>=3 (the multi-town push case) and cap=1 otherwise. Cross-check the existing ...|RELIEF|<townname> event lines (line 446, unchanged): when cap=2 you should see up to 2 distinct RELIEF town names in the same minute, and the FIRST relieved town in a multi-contest minute should be the higher-value one (e.g. a Huge/Large town name appearing before a Tiny/Small on… [truncated — full sketch regenerable]

relief-trigger-last-seen-fallback

Relief trigger: react to a town bleeding ownership-risk even when wfbe_active misses the raid (close the fog gap with a supply-drop signal)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RELIEF_DRAIN_DELTA = 40 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM RELIEF block, right after WFBE_C_AICOM_RELIEF_HOLD at line 319))
GOAL: widen the SET of towns the existing single relief slot may consider, by adding a per-town supplyValue-DRAIN trigger as a SECOND path into `_attacked`, alongside the momentary `wfbe_active` flag. Touches ONLY the `_attacked` builder (line 405-406). The RELIEF_MAX cap, reliever-eligibility scan, and hold logic (408-452) are UNCHANGED, so this never dispatches MORE reliefs (total AI flat) -- it only changes WHICH towns are eligible for the one slot.  === CONSTANT (Init_CommonConstants.sqf, insert immediately after line 319) ===   //--- RELIEF DRAIN: a per-town supplyValue drop > this between two Strategy ticks marks the town "draining"   //--- (a fast raid/edge-of-radius depot bleed that wfbe_active misses) and makes it relief-eligible. Conservative   //--- so only a real combat/capture drain trips it (side-level spend is not per-town). AI_Commander_Strategy.sqf.   if (isNil "WFBE_C_AICOM_RELIEF_DRAIN_DELTA") then {WFBE_C_AICOM_RELIEF_DRAIN_DELTA = 40}; //--- supply. Rollback: 9e9 (effectively off).  === STRATEGY (AI_Commander_Strategy.sqf) === Step A -- declare the two new private locals in the line-18 private[...] block. APPEND to that array:   ,"_svPrev","_svDelta","_svIdx","_svNow"  BEFORE (lines 405-406, verbatim):   _attacked = [];   { if ((_x getVariable "sideID") == _sideID && {_x getVariable ["wfbe_active", false]}) then {_attacked = _attacked + [_x]} } forEach towns;  AFTER (replace those two lines with the block below):   _attacked = [];   //--- relief-trigger-last-seen-fallback (B68): per-side last-seen supplyValue memory on the side logic.   //--- Parallel arrays [town, lastSV] keyed by `find` (A2-OA-safe; mirrors the wfbe_aicom_spearhead_bl idiom @121-124).   _svPrev = _logik getVariable ["wfbe_aicom_town_supply_prev", []]; //--- array of [townObj, supplyValueAtLastTick]   private ["_svPrevTowns","_svNext","_svNextTowns"];   _svPrevTowns = []; //--- parallel: just the town objects, so we can `find` an index   { if ((typeName (_x select 0) == "OBJECT") && {!isNull (_x select 0)}) then {_svPrevTowns set [count _svPrevTowns, (_x select 0)]} } forEach _svPrev;   _svNext = [];      //--- rebuilt fresh each tick (drops dead/captured towns naturally)   {     if ((_x getVariable "sideID") == _sideID) then {       _svNow = _x getVariable ["supplyValue", 0];       //--- (1) momentary ground-truth trigger (UNCHANGED behaviour) ...       if (_x getVariable ["wfbe_active", false]) then {         if (!(_x in _attacked)) then {_attacked = _attacked + [_x]};       } else {         //--- (2) NEW trend trigger: did this owned town's depot drop > DELTA sin… [truncated — full sketch regenerable]

Soak test: Soak grep for the new event line: AICOMSTAT|v1|EVENT|<side>|<min>|RELIEF_DRAIN|<townName>|drop=<n> in the server RPT. Expect it to fire ONLY on owned towns that bled >40 supply between two Strategy ticks WITHOUT wfbe_active being set that tick (i.e. the fog-gap raids the old path missed). Cross-check: every RELIEF_DRAIN line should, within ~1-2 Strategy intervals, be followed by an existing AICOMSTAT|v1|EVENT|<side>|...|RELIEF|<townName> line for the same town (confirming the widened candidate actually drew the slot when free). Behavioural observable: a town flipped by a fast raid between … [truncated — full sketch regenerable]

retreat-cull-combat-guard

Add a behaviour!=COMBAT guard to the retreat-cull deleteVehicle so a far survivor actively fighting is never deleted mid-firefight

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Produce.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RETREAT_CULL_COMBAT_GUARD = 1 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM block, immediately after the B68 RETREAT-CULL hardening lines 283-284, alongside WFBE_C_AICOM_RETREAT_MAX_ISSUES / _MAX_DIST))
GOAL: never deleteVehicle a lone retreat-cull survivor while its leader is behaviour "COMBAT" (in a firefight). Mirror the codebase's own canonical safe-delete idiom at Common_RunCommanderTeam.sqf:464-465. Default-ON, A/B-able, A2-OA-1.64-safe (no isEqualType/typed-params; only isNull / behaviour / == / forEach).  FILE 1 - Common/Init/Init_CommonConstants.sqf  (insert after line 284) --- BEFORE (lines 281-285) ---   //--- B68 (Ray 2026-06-21) RETREAT-CULL hardening: ...   if (isNil "WFBE_C_AICOM_RETREAT_MAX_ISSUES") then {WFBE_C_AICOM_RETREAT_MAX_ISSUES = 8};   if (isNil "WFBE_C_AICOM_RETREAT_MAX_DIST")   then {WFBE_C_AICOM_RETREAT_MAX_DIST = 6000};   //--- B67 (Ray 2026-06-21) BUILD PLACEMENT (item #10): ... --- AFTER (add one line before the B67 BUILD PLACEMENT comment) ---   if (isNil "WFBE_C_AICOM_RETREAT_MAX_ISSUES") then {WFBE_C_AICOM_RETREAT_MAX_ISSUES = 8};   if (isNil "WFBE_C_AICOM_RETREAT_MAX_DIST")   then {WFBE_C_AICOM_RETREAT_MAX_DIST = 6000};   //--- B68 RETREAT-CULL COMBAT GUARD: never delete a far survivor whose leader is mid-firefight; defer the cull   //--- until it breaks contact. The monotonic _rIssues cap still guarantees eventual cull once it disengages.   if (isNil "WFBE_C_AICOM_RETREAT_CULL_COMBAT_GUARD") then {WFBE_C_AICOM_RETREAT_CULL_COMBAT_GUARD = 1}; //--- 1=defer cull while leader behaviour=="COMBAT". 0=exact old behaviour (cull regardless).  FILE 2 - Server/AI/Commander/AI_Commander_Produce.sqf  (cull branch, lines 121-140) Compute the in-contact flag just before the cull condition (after line 120 where _rProgress/_rTries are finalised), using the EXACT isNull-guarded idiom from RunCommanderTeam.sqf:464. _ldr is already in scope (set at :81 = leader _team).  --- BEFORE (lines 121-127) ---   if (_rTries >= _rBudget || {_rIssues >= _rMaxIssues} || {_curDist > _rMaxDist}) then {       //--- Budget exhausted with no progress: cull the stuck survivor. ...       { if (!(isPlayer _x)) then {deleteVehicle _x} } forEach (units _team);       ["INFORMATION", Format ["AI_Commander_Produce.sqf: [%1] team [%2] retreat-thrash CULLED (alive=%3, dist=%4, tries=%5, issues=%6) - recycled (no-progress OR issue-cap OR too-far).", _sideText, _team, _aliveNow, _curDist, _rTries, _rIssues]] Call WFBE_CO_FNC_AICOMLog;       deleteGroup _team;       _canProduce = false;   } else {       //--- Still within budget: re-issue retreat ...  --- AFTER ---   //--- B68 RETREAT-CULL COMBAT GUARD: a lone survivor pinned in a firefight far from HQ is legitimately   //--- stationary (which is exactly why dist stops closing and tries accrue). Do NOT delete it mid-… [truncated — full sketch regenerable]

Soak test: Soak observable + greppable log: grep the server RPT for the new line "retreat-cull DEFERRED - leader in COMBAT" - it must appear for at least one team during a contested round and that team's wfbe_teams entry must SURVIVE that cycle (no matching "retreat-thrash CULLED" for the same [team] in the same minute). Behavioural check: spawn/observe a lone OPFOR or BLUFOR commander survivor (alive=1) pinned far from HQ in a firefight - it must NOT vanish while firing; confirm it IS culled (the existing "retreat-thrash CULLED ... recycled" line) within a couple cycles AFTER it breaks contact. A/B conf… [truncated — full sketch regenerable]

rich-flag-health-gate-prevents-thin-team-doubling

Gate the wealth-conversion rich flag on team HEALTH so surplus cash refills existing thin teams instead of doubling Produce into more dribble

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_RICH_HEALTH_GATE = 1 (Init_CommonConstants.sqf (AICOM block, alongside WFBE_C_AICOM_TEAM_SIZE_MIN at ~line 306-307). Idiom: if (isNil "WFBE_C_AICOM_RICH_HEALTH_GATE") then {WFBE_C_AICOM_RICH_HEALTH_GATE = 1}; -- 1 = only count full-strength teams toward the rich-flag target (this fix); 0 = legacy count-all behaviour (rollback to current B68).)
GOAL: In AI_Commander.sqf the adaptive-spend supervisor counts _fTeams as ANY hc-or-founded group with NO health check, then trips the rich flag (which doubles Produce's batch) when _fTeams>=_dynTarget. Fix: only count a team toward _fTeams when it is at/above the size floor (alive-count >= WFBE_C_AICOM_TEAM_SIZE_MIN), with MBT/attack-heli teams counted healthy regardless. Then "rich" means a genuinely full-strength army + surplus cash, not a field of 2-4 man remnants reporting "target met".  FILE 1: AI_Commander.sqf -- the _fTeams accumulation loop (current lines 207-215).  BEFORE (lines 207-215):   _fTeams = 0;   {       if (!isNull _x) then {           if (([_x, "wfbe_aicom_hc", false] Call WFBE_CO_FNC_GroupGetBool) || {[_x, "wfbe_aicom_founded", false] Call WFBE_CO_FNC_GroupGetBool}) then {               _fTeams = _fTeams + 1;           };       };   } forEach (_logik getVariable ["wfbe_teams", []]);   _richFlag = _fundsRich && (_fTeams >= _dynTarget);  AFTER:   //--- RICH HEALTH-GATE (fix rich-flag-health-gate-prevents-thin-team-doubling): only count a team toward   //--- _fTeams when it is at/above the size floor, so the wealth-conversion flag trips on a genuinely   //--- FULL-STRENGTH army (not a field of 2-4 man remnants). MBT / attack-heli teams are healthy regardless   //--- (vehicle+crew is the punch - reuse the same isKindOf "Tank" / "Helicopter"+transportSoldier==0 idiom   //--- as AI_Commander_Produce.sqf:190-194, but applied to LIVE units (typeOf/vehicle) since we only have a   //--- live group here, not a template). A2-OA-safe: isNull, isKindOf classname-literal, count, getNumber,   //--- WFBE_CO_FNC_GroupGetBool for the GROUP bool reads (the [name,default] form is unreliable on groups).   _richHealthGate = (missionNamespace getVariable ["WFBE_C_AICOM_RICH_HEALTH_GATE", 1]) > 0;   _richFloor = missionNamespace getVariable ["WFBE_C_AICOM_TEAM_SIZE_MIN", 8];   _fTeams = 0;   {       if (!isNull _x) then {           if (([_x, "wfbe_aicom_hc", false] Call WFBE_CO_FNC_GroupGetBool) || {[_x, "wfbe_aicom_founded", false] Call WFBE_CO_FNC_GroupGetBool}) then {               _countIt = true;               if (_richHealthGate) then {                   //--- classify this LIVE group as a vehicle team (MBT / attack-heli) -> always healthy.                   _grpVeh = false;                   {                       _v = vehicle _x;                       if (_v isKindOf "Tank") exitWith {_grpVeh = true};                       if (_v isKindOf "Helicopter" && {(getNumber (configFile >> "CfgVehicles" >> (typeOf _v) >> "transportSoldier")) == 0}) exitWith… [truncated — full sketch regenerable]

Soak test: SOAK GREP: the existing WEALTH_CONVERSION transition log is the hook. After deploy, grep the server RPT for: AICOMSTAT|v1|EVENT|...|WEALTH_CONVERSION|funds and the paired AICOMLog line "wealth conversion active (funds %2 > threshold %3, teams %4/%5)". EXPECTED CHANGE vs B68: with the gate ON, the rich flag should trip materially LESS often and only when teams %4 (now the gated full-strength count) genuinely reaches %5 (_dynTarget) -- i.e. you should NO LONGER see "teams 4/4 - Produce batch doubled" while live CMDRSTAT shows unitsPerTeam ~2-4. Cross-check against the periodic CMDRSTAT units… [truncated — full sketch regenerable]

service-detour-resupply-cap-not-free-snap

Make the self-service detour a graded forward-RESUPPLY (partial repair + ammo cost from supply) instead of a free instant full snap-repair

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_AICOMServiceTick.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_SVC_SUPPLY_COST = 400 (Init_CommonConstants.sqf (AICOM SELF-SERVICE block, insert right after line 343 / WFBE_C_AICOM_SVC_TRIGGER_DIST))
  • new const WFBE_C_AICOM_SVC_INF_HEAL_FLOOR = 0.25 (Init_CommonConstants.sqf (AICOM SELF-SERVICE block, insert right after line 343 / WFBE_C_AICOM_SVC_TRIGGER_DIST))
GOAL: change ONLY the arrival-restore block in Common_AICOMServiceTick.sqf (lines 73-79). The IDLE-decide, en-route drive, abort/timeout, and never-frozen retarget logic (55-68, 80-145) stay byte-for-byte identical. No new commands beyond setDamage/getDammage/setVehicleAmmo/setFuel/ChangeSideSupply, plus a non-blocking missionNamespace getVariable read — all already used in this file family and A2-OA-1.64-safe (no isEqualType/isEqualTo/typed params; min/forEach/isKindOf/count only).  === CONSTANTS: Init_CommonConstants.sqf, insert immediately AFTER line 343 (WFBE_C_AICOM_SVC_TRIGGER_DIST) ===   if (isNil "WFBE_C_AICOM_SVC_SUPPLY_COST") then {WFBE_C_AICOM_SVC_SUPPLY_COST = 400}; //--- S charged from the team's side pool per completed service. If side supply < this, vehicles are NOT rearmed/refuelled (infantry still heal to the floor); logs SERVICE_NOSUPPLY. 0 = free (old B48 behaviour).   if (isNil "WFBE_C_AICOM_SVC_INF_HEAL_FLOOR") then {WFBE_C_AICOM_SVC_INF_HEAL_FLOOR = 0.25}; //--- field-aid-station, not a hospital: members healed DOWN to at most this damage (never below their current dmg), so they stay combat-effective but not pristine. 0 = full heal.  === BEHAVIOUR: Common_AICOMServiceTick.sqf, replace the ARRIVED block body (current lines 73-80) ===  --- BEFORE (lines 73-80) ---   {if (alive _x) then {_x setDamage 0}} forEach (units _team);   {       if (!isNull _x && {alive _x}) then {           _x setDamage 0; _x setVehicleAmmo 1;           if (_x isKindOf "Air") then {_x setFuel 1};       };   } forEach _vehicles;   diag_log ("AICOMSTAT|v1|EVENT|" + (str _side) + "|" + str (round (time / 60)) + "|SERVICE_DONE|" + (str _team));  --- AFTER ---   private ["_supCost","_floor","_haveSup","_canAfford"];   _supCost = missionNamespace getVariable ["WFBE_C_AICOM_SVC_SUPPLY_COST", 400];   _floor   = missionNamespace getVariable ["WFBE_C_AICOM_SVC_INF_HEAL_FLOOR", 0.25];    //--- A2-OA + locality: this runs HC-LOCAL (Common_RunCommanderTeam:1062). DO NOT call   //--- WFBE_CO_FNC_GetSideSupply here: its west/east branch hits publicVariableServer + waitUntil   //--- when the mirror is nil -> "Suspending not allowed" in an unscheduled HC loop. The server   //--- publicVariable's wfbe_supply_<side> globally (Server_ChangeSideSupply.sqf:21/45/69), so a   //--- non-blocking 0-default read of the mirror is correct and never suspends.   _haveSup   = missionNamespace getVariable [format ["wfbe_supply_%1", _side], 0];   _canAfford = (_supCost <= 0) || {_haveSup >= _supCost};    //--- (1) HEAL members to the floor (graded). min keeps anyone already healthier untouched… [truncated — full sketch regenerable]

Soak test: SOAK: grep the server/HC RPT for the two new tagged lines. Healthy economy → "AICOMSTAT|v1|EVENT|...|SERVICE_DONE|...|cost=400|sup=" (supply decremented by 400 each, confirm wfbe_supply trend drops on every service). Broke side → "AICOMSTAT|v1|EVENT|...|SERVICE_NOSUPPLY|...|need=400|sup=<N<400>" appears and the serviced team's vehicles stay damaged/low-ammo (visually: an MBT leaves a friendly town still scorched) while infantry are healed to ~0.25. A/B contrast vs B66: count SERVICE_DONE lines per hour should be similar, but total side supply must now show step-down debits aligned 1:1 with … [truncated — full sketch regenerable]

spearhead-avoid-enemy-concentration

Spearhead scorer: deprioritise towns shielded by a known enemy team concentration (exploit omniscience to flank)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_SHIELD_RADIUS = 600 (Common/Init/Init_CommonConstants.sqf (AICOM spearhead-scorer block, insert after the WFBE_C_AICOM_VALUE_DIVISOR line 221))
  • new const WFBE_C_AICOM_SHIELD_PENALTY = 25 (Common/Init/Init_CommonConstants.sqf (AICOM spearhead-scorer block, insert after the WFBE_C_AICOM_VALUE_DIVISOR line 221))
GOAL: when the enemy has massed maneuver teams ON/AROUND a candidate town, that town scores LOWER, so at comparable distance the AI prefers the flank the enemy left open. Penalty is a modest deprioritiser (default 25), NOT a ban; distance terms (FAR_PENALTY=1000, dNear/50) still dominate so the front stays coherent. AI count is UNCHANGED (pure scorer term, no spawns/merges). Build the enemy-leader-position list ONCE per tick (already-fetched registry, ~15 getPos), then both scorer copies read it.  === A2-OA SAFETY NOTES === - leader _x -> OBJECT; getPos OBJECT -> position array (A2-safe). Cache POSITIONS at build time, not objects (a leader can die between build and scorer; a stale pos is harmless, just a slightly-off penalty for one tick). - _t distance _pos  (town OBJECT distance position-ARRAY) is valid in A2-OA. No getDir, no isEqualType, no typed params. Only count / forEach / distance / getPos used. - _enemyLogik comes from GetSideLogic and CAN be objNull -> guard with isNull before getVariable. Registry read uses the [name,default] form on a LOGIC object (valid; the file already does the identical read at line 63). - _enLdrs declared in the existing private[] block (line 18) so it never leaks to a global. It is built in the SAME function scope as both scorer copies, so copy #2 (re-pick, 288-321) reuses it for free.  === EDIT 1: Init_CommonConstants.sqf — add 2 nil-guarded constants AFTER line 221 (WFBE_C_AICOM_VALUE_DIVISOR) ===   if (isNil "WFBE_C_AICOM_VALUE_DIVISOR") then {WFBE_C_AICOM_VALUE_DIVISOR = 50};   //--- (existing, line 221) + //--- INTEL-FLANK (B68): deprioritise towns the enemy has CONCENTRATED maneuver teams on, exploiting the + //--- registry the commander already has in hand (Strategy.sqf:63). Modest deprioritiser, NOT a ban: + //--- distance/FAR_PENALTY still dominate so the front stays coherent and a deep flank can't win. + if (isNil "WFBE_C_AICOM_SHIELD_RADIUS")  then {WFBE_C_AICOM_SHIELD_RADIUS  = 600};   //--- m: count enemy maneuver-team leaders within this radius of a candidate town = its "shield". + if (isNil "WFBE_C_AICOM_SHIELD_PENALTY") then {WFBE_C_AICOM_SHIELD_PENALTY = 25};    //--- score points SUBTRACTED per shielding enemy team (~0.5 town-spacing at DISTANCE_DIVISOR=50). Under FAR_PENALTY/per-tier swing so distance dominates. 0 = rollback to no shield-avoidance.  === EDIT 2: Strategy.sqf — declare _enLdrs in the private[] block (line 18) ===   Append \",\"_enLdrs\"\" inside the existing private[...] array at line 18 (one new local; no behaviour change).  === EDIT 3: Strategy.sqf — build the enemy-leader-position … [truncated — full sketch regenerable]

Soak test: SOAK HOOK (greppable diag_log): each Strategy tick per side emits via WFBE_CO_FNC_AICOMLog -> "AI_Commander_Strategy.sqf: [WEST] SHIELD intel: N enemy maneuver leaders mapped (r=600 pen=25)." Grep the server RPT for SHIELD intel: to confirm the enemy-leader list is being built (N>0 once both sides have teams). OBSERVABLE BEHAVIOUR: in an AI-vs-AI soak, when one side stacks 3-4 teams on a front town, the other commander's wfbe_aicom_targets should shift to an ADJACENT lightly-held town at comparable distance (watch the spearhead primary flip away from the contested town) rather than grinding … [truncated — full sketch regenerable]

spearhead-human-contest-bias

Spearhead scorer: bias toward enemy/neutral towns where friendly HUMAN players are already fighting (concentrate WITH the human push)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_HUMAN_CONTEST_WEIGHT = 18 (Common/Init/Init_CommonConstants.sqf (AICOM spearhead block, right after WFBE_C_AICOM_VALUE_DIVISOR at line 221))
  • new const WFBE_C_AICOM_HUMAN_CONTEST_RANGE = 500 (Common/Init/Init_CommonConstants.sqf (AICOM spearhead block, right after WFBE_C_AICOM_HUMAN_CONTEST_WEIGHT))
GOAL: add a per-candidate bonus = (friendly live HUMAN players within RANGE of the town) * WEIGHT, so AI mass concentrates WITH the human push. Bonus is small vs FAR_PENALTY(1000) so front contiguity still dominates.  A2-OA SAFETY: uses only side / isPlayer / alive / distance / count / getVariable[name,default] / forEach — all 1.64-safe. NO nearEntities, NO isEqualType/params. Human snapshot computed ONCE per ~60s tick (cheap: O(players * candidates), players tiny), reused by BOTH scorer copies.  === EDIT 0: Init_CommonConstants.sqf — new constants (after line 221, WFBE_C_AICOM_VALUE_DIVISOR) ===   if (isNil "WFBE_C_AICOM_HUMAN_CONTEST_WEIGHT") then {WFBE_C_AICOM_HUMAN_CONTEST_WEIGHT = 18};  //--- score points ADDED per friendly live HUMAN player within HUMAN_CONTEST_RANGE of a candidate town, so AI concentrates WITH the human assault. Tuned to outrank an empty equal-distance town but stay under FAR_PENALTY(1000) so front contiguity holds. 0 = disable.   if (isNil "WFBE_C_AICOM_HUMAN_CONTEST_RANGE")  then {WFBE_C_AICOM_HUMAN_CONTEST_RANGE  = 500}; //--- m: ring around a candidate town centre inside which a friendly player counts as "contesting" it. Tight (500m) so a lone roamer 1km away does not pull the spearhead off-front.  === EDIT 1: AI_Commander_Strategy.sqf header (line 18) — declare new locals === Append to the private[...] list:  ,"_humW","_humRng","_humPlayers","_humCnt"  === EDIT 2: AI_Commander_Strategy.sqf — build the human snapshot ONCE, after line 143 (_enemyHQForRank), before the first scorer loop at line 149 === BEFORE (143-146):   _enemyHQForRank = (_enemySide) Call WFBE_CO_FNC_GetSideHQ;   //--- Concentrate force: split across FEW towns ...   _want = 1 max (missionNamespace getVariable ["WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX", 2]); AFTER (insert between 143 and the _want line):   _enemyHQForRank = (_enemySide) Call WFBE_CO_FNC_GetSideHQ;   //--- B68 HUMAN-CONTEST: snapshot our own live HUMAN players ONCE (reused by both scorer copies). Plain   //--- distance count per candidate is cheaper than per-town nearEntities and uses only A2-OA-safe commands.   _humW   = missionNamespace getVariable ["WFBE_C_AICOM_HUMAN_CONTEST_WEIGHT", 18];   _humRng = missionNamespace getVariable ["WFBE_C_AICOM_HUMAN_CONTEST_RANGE", 500];   _humPlayers = [];   if (_humW > 0 && {_humRng > 0}) then {       { if (isPlayer _x && {alive _x} && {side _x == _side}) then {_humPlayers set [count _humPlayers, _x]} } forEach playableUnits;   };   //--- Concentrate force: split across FEW towns ...   _want = 1 max (missionNamespace getVariable ["WFBE_C_AICOM_SPEARHEAD_TOWNS_MAX… [truncated — full sketch regenerable]

Soak test: Soak: grep the server RPT for AICOMSTAT|v1|HUMANCONTEST| (EDIT 4 telemetry). On a populated round, when humans cluster on an enemy/neutral town the chosen primary= should match that town and players= > 0 within a tick or two of the human assault. Behavioural confirmation: AI commander teams (AssignTowns spearhead) should converge on the same town humans are assaulting instead of a different equal-distance town. Negative control: on the AI-only overnight soak (no players) the line never appears and spearhead picks are byte-identical to B68 — confirms zero regression. Boot-smoke gate: MISS… [truncated — full sketch regenerable]

stall-telemetry-add-garrison-bodies

Instrument the STALL/POSTURE telemetry with held-town garrison body count to size the split correctly

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

TELEMETRY-ONLY change. One file: AI_Commander_Strategy.sqf. No new constants needed (the proposal text mentions no tunable; the count is pure observation). Insert a single compute block, then add one token to each of the two existing diag_log lines.  CONTEXT (verified against live B68 source): - _side (L20), _sideID (L21), _sideText (L22) already in scope. - towns iterated with the canonical idiom `(_x getVariable "sideID") == _sideID` (L38). - Garrison members live in `_town getVariable "wfbe_town_teams"` (default [], server_town_ai.sqf:42/243/250/262). Members are GROUPS, so `units _x` / `alive _x` is correct. - The established body-count idiom in THIS file is `{alive _x} count (units _x)` guarded by `if (!isNull _x)` (L52, L63). Mirror it exactly — do NOT introduce new commands. - POSTURE diag_log = L577; STALL diag_log = L590 (inside the `if` at L589).  === STEP 1: insert compute block immediately BEFORE the POSTURE diag_log (after the existing _posture computation at L571-576, before L577). ===  BEFORE (L576-577): 	}; 	diag_log ("AICOMSTAT|v1|POSTURE|" + _sideText + ... + "|strikeOn=" + str _strikeOn);  AFTER: 	}; 	//--- B69 garrison-body telemetry (stall-telemetry-add-garrison-bodies): how many bodies the leader has 	//--- tied up in town OCCUPATION vs its maneuver _myStr. Pure observation, no behaviour change. Mirrors 	//--- the existing _myStr idiom (L52): {alive _x} count (units _x), guarded by !isNull. One forEach over 	//--- towns (~30) per side per ~60s strategy tick - negligible, server-side, paced. wfbe_town_teams may 	//--- briefly hold null/empty groups mid-activation -> guarded by !isNull and the alive count (empty -> 0). 	private ["_garBodies","_garTeams"]; 	_garBodies = 0; 	{ 		if ((_x getVariable "sideID") == _sideID) then { 			_garTeams = _x getVariable ["wfbe_town_teams", []]; 			{ 				if (!isNull _x) then { _garBodies = _garBodies + ({alive _x} count (units _x)) }; 			} forEach _garTeams; 		}; 	} forEach towns; 	diag_log ("AICOMSTAT|v1|POSTURE|" + _sideText + "|" + str (round (time / 60)) + "|" + _posture + "|myTowns=" + str _myTowns + "|enTowns=" + str _enemyTowns + "|myStr=" + str _myStr + "|enStr=" + str _enStr + "|strikeOn=" + str _strikeOn + "|garBodies=" + str _garBodies);  (Note: only the trailing `+ "|garBodies=" + str _garBodies` is appended to the EXISTING L577 string; everything before it is unchanged.)  === STEP 2: append the same token to the STALL diag_log (L590). _garBodies is still in scope (same worker invocation, computed ~13 lines above). ===  BEFORE (L590): 	diag_log ("AICOMSTAT|v1|STALL|" + _sideText + "|" + st… [truncated — full sketch regenerable]

Soak test: Greppable: after deploy + a soak round, the per-side strategy tick must emit the new field. Grep the server RPT for AICOMSTAT\|v1\|POSTURE\|.*\|garBodies= and AICOMSTAT\|v1\|STALL\|.*\|garBodies=. PASS = every POSTURE line carries |garBodies=<int> and, in the dominant-but-passive freeze, the STALL line shows a high garBodies relative to a low myStr (i.e. garBodies >> myStr confirms the leader is tying bodies up in occupation — the exact magnitude the split was previously blind to). Sanity floor: a side holding only its HQ with no active towns should log garBodies=0. Boot-smoke gate fir… [truncated — full sketch regenerable]

stipend-and-income-out-of-canbuild-gate

Move the 0-town bootstrap stipend out from under the _canBuild build-grace so a brief human-commander blip can't starve a broke AI for 300s

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf

PROBLEM ANCHORED IN SOURCE File: .../Server/AI/Commander/AI_Commander.sqf - L170  `if (_canBuild) then {`  ... opens the full-economy block. - L227-267  the V0.7 BOOTSTRAP STIPEND block lives INSIDE that `_canBuild` gate. - L128  `_canBuild = (_noHumanSince >= 0) && {(time - _noHumanSince) >= WFBE_C_AI_COMMANDER_BUILD_GRACE(=300)};` - L123-127  `_noHumanSince` is RESET to -1 the instant a human occupies the commander slot, and re-armed to `time` when they leave -> `_canBuild` is then false for a fresh 300s. Net bug: any momentary human-commander blip on a 0-town side (vote, JIP, disconnect flap) re-arms the 300s grace, and the bootstrap stipend - the ONLY income a 0-town AI has - is suppressed for that whole window. A broke AI that just lost its commander seat is exactly the side that most needs the drip; instead it gets starved.  FIX: relocate the stipend block so it runs whenever the AI is the effective commander (not human-commanded), regardless of build-grace. The stipend's OWN conditions (0 towns AND time < MAXTIME) already bound it; the build-grace adds nothing but harm here. Funds route to the separate AI treasury (ChangeAICommanderFunds) so there is zero contention with a human's spend.  WHERE TO MOVE IT The block must run when the AI commands the side. The cleanest existing predicate is `!_humanCmd` (true = AI is the live commander; already accounts for AI_COMMANDER_LOCK at L108-110). Place the moved block immediately AFTER the executor/town-assign calls and BEFORE the `if (_canBuild)` block (i.e. just before L167), guarded by `if (!_humanCmd) then { ... }`. Keep it strictly inside the `if (_active)` scope (HQ alive) so a dead-HQ side gets nothing.  PSEUDO-DIFF (cut L227-267 out of the _canBuild block; paste before L167 wrapped in !_humanCmd)    (... L165 closes the HYBRID-REFILL block ...) +	//--- V0.7 BOOTSTRAP STIPEND (B68: lifted OUT of the _canBuild build-grace gate). +	//--- Runs whenever the AI is the effective commander of this side (!_humanCmd), so a brief +	//--- human blip that re-arms the 300s build-grace can no longer starve a 0-town AI of its +	//--- only income. Self-bounded by 0-town AND time < MAXTIME; funds hit the SEPARATE AI +	//--- treasury (no contention with a human's spend). _ltStipend/_prevStipendActive are +	//--- side-logic-scoped locals, unchanged by the move. +	if (!_humanCmd) then { +		_stipendMaxTime = missionNamespace getVariable ["WFBE_C_AICOM_BOOTSTRAP_MAXTIME", 7200]; +		_stipendTowns = 0; +		{ if ((_x getVariable "sideID") == _myID) then {_stipendTowns = _stipendTowns + 1} } forEach towns; +		_stipendActive = … [truncated — full sketch regenerable]

Soak test: Soak grep on the server RPT: AICOMSTAT|v1|EVENT|...|BOOTSTRAP_STIPEND|start. Reproduction/observation: on a fresh round (both sides 0 towns) confirm a BOOTSTRAP_STIPEND|start line per AI side within the first minute. Then force/await a brief human-commander blip on a still-0-town side and confirm the stipend RESUMES (next 60s grant) without waiting the 300s build-grace - i.e. you should NOT see a 300s funds-flat gap on a 0-town side in the AICOMSTAT|v2|EVENT|...|ECONOMY|...|netFunds= series (L342) right after the human leaves. Pre-fix that netFunds shows ~0 for up to 5 min post-blip; post-… [truncated — full sketch regenerable]

stranded-survivor-merge-into-nearest-team-before-cull

Stranded lone-survivor: merge into the nearest healthy same-side server-local team before culling it (preserve bodies, fewer+bigger groups, FPS-down)

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Produce.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_STRANDED_MERGE = 1 (Common/Init/Init_CommonConstants.sqf (AICOM block, next to WFBE_C_AICOM_RETREAT_MAX_ISSUES/WFBE_C_AICOM_RETREAT_MAX_DIST at lines 283-284))
  • new const WFBE_C_AICOM_STRANDED_MERGE_RANGE = 1200 (Common/Init/Init_CommonConstants.sqf (AICOM block, same B68 retreat-cull cluster, lines 283-284))
CONTEXT — AI_Commander_Produce.sqf, the retreat-thrash cull branch (currently lines 121-127). `_team` is the stranded lone survivor (alive<2, far from HQ, budget exhausted). The whole `then{}` block is server-local non-HC non-player-led (guaranteed by the line-69 gate + the line-89 alive<2 path). `_teams` is `_logik getVariable "wfbe_teams"` (line 32), `_side`/`_sideText` in scope, `_ldr` = leader _team (line 81). deleteVehicle on a unit at line 124 PROVES the team is server-local, so `join` is locality-safe.  ADD to the `private[...]` array at line 15 (so they don't leak global): "_mergeOn","_mergeRange","_mergeTeam","_mergeBest","_cand","_candLdr","_candAlive","_d2","_mergedInto".  --- BEFORE (lines 121-127) ---   if (_rTries >= _rBudget || {_rIssues >= _rMaxIssues} || {_curDist > _rMaxDist}) then {       //--- Budget exhausted with no progress: cull the stuck survivor. Non-player guard is       //--- belt-and-braces (this branch is already server-local non-HC, non-player-led).       { if (!(isPlayer _x)) then {deleteVehicle _x} } forEach (units _team);       ["INFORMATION", Format ["AI_Commander_Produce.sqf: [%1] team [%2] retreat-thrash CULLED (alive=%3, dist=%4, tries=%5, issues=%6) - recycled (no-progress OR issue-cap OR too-far).", _sideText, _team, _aliveNow, _curDist, _rTries, _rIssues]] Call WFBE_CO_FNC_AICOMLog;       deleteGroup _team;       _canProduce = false;   } else {  --- AFTER (insert the merge attempt at the TOP of the same `then{}`, before the deleteVehicle cull) ---   if (_rTries >= _rBudget || {_rIssues >= _rMaxIssues} || {_curDist > _rMaxDist}) then {       //--- B68 STRANDED-MERGE (item stranded-survivor-merge-into-nearest-team-before-cull):       //--- before throwing away a live trained body, try to JOIN the survivor into the nearest       //--- healthy same-side SERVER-LOCAL team. Net groups DROP by one and the body keeps fighting.       //--- A2-OA-safe: distance/count/select, GroupGetBool, `join` on a unit array, local on the       //--- LEADER object (never on the group). Fall through to the existing cull if no eligible target.       _mergeOn    = missionNamespace getVariable ["WFBE_C_AICOM_STRANDED_MERGE", 1];       _mergeRange = missionNamespace getVariable ["WFBE_C_AICOM_STRANDED_MERGE_RANGE", 1200];       _mergeTeam  = grpNull;       _mergeBest  = _mergeRange;  //--- only accept candidates strictly inside the range cap       if (_mergeOn > 0) then {           {               _cand = _x;               if (!isNull _cand && {_cand != _team}) then {                   _candLdr = leader _cand;                   //--- (b) serv… [truncated — full sketch regenerable]

Soak test: Greppable diag_log: a new AICOMLog line containing the literal substring "stranded survivor MERGED into" — grep the server RPT for retreat-thrash CULLED vs stranded survivor MERGED into; a healthy soak should show a non-trivial share of MERGED outcomes (proves a target was found) and the CULLED line should now only fire when no nearby team exists. Observable behaviour: dashboard "groups" count should tick DOWN by one at each merge (not just at culls), total AI count unchanged across the event; and the per-side allGroups count should trend lower over a long match vs a B68-baseline run with … [truncated — full sketch regenerable]

supervisor-spawn-phase-jitter

Phase-jitter the two per-side supervisors at spawn so both sides' economy workers never share a frame

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_SUPERVISOR_JITTER = 7 (Common/Init/Init_CommonConstants.sqf (AICOM cadence block, insert immediately AFTER line 142 WFBE_C_AI_COMMANDER_TICK = 15))
GOAL: de-correlate the two per-side AI-Commander supervisors so WEST and EAST never run the heavy economy/strategy workers on the same engine frame. Both are Spawned in one forEach (Init_Server.sqf:1015) and both block on the SAME release (AI_Commander.sqf:23 waitUntil {sleep 1; !(isNil "serverInitFull")}), so they resume on the same frame with identical _lt* throttles (all 0 at :72) -> first _canBuild tick stacks Strategy(:173) + Base(:183, the 10km nearestObjects scan) + Teams(:187) + AssignTypes(:190) + Upgrade(:193) + Produce(:197) for BOTH sides in one frame. Fix = a one-time per-side sleep of 0..N seconds right after the shared release, mirroring the existing Wildcard de-correlation idiom (AI_Commander_Wildcard.sqf:95-96: `_jitter = random 30; sleep _jitter`). Because each Spawn evaluates `random` independently, WEST and EAST get different offsets and their TICK loops stay out of phase.  === EDIT 1 — Init_CommonConstants.sqf : define the nil-guarded constant === Anchor (current, line 142, in the AICOM cadence block):    WFBE_C_AI_COMMANDER_TYPES_INTERVAL = 30;   WFBE_C_AI_COMMANDER_TICK = 15;             //--- Supervisor base tick (s); how often the order-executor runs (hybrid responsiveness).   WFBE_C_AI_COMMANDER_BASE_INTERVAL = 60;    ...  AFTER (insert one nil-guarded line directly under WFBE_C_AI_COMMANDER_TICK):    WFBE_C_AI_COMMANDER_TICK = 15;             //--- Supervisor base tick (s); how often the order-executor runs (hybrid responsiveness).   //--- B69 worker-stagger (deep-5vote): one-time per-side spawn phase jitter (0..N s) so WEST/EAST   //--- economy workers never share a frame. 0 disables. Well inside BUILD_GRACE(300) so unobservable.   if (isNil "WFBE_C_AICOM_SUPERVISOR_JITTER") then {WFBE_C_AICOM_SUPERVISOR_JITTER = 7};   WFBE_C_AI_COMMANDER_BASE_INTERVAL = 60;    ...  (Nil-guarded `if (isNil "NAME") then {NAME = v}` is the live idiom in this file from ~:207 onward; using it keeps an override-from-elsewhere safe and a missing value defaulting to 7. A2-OA-safe: isNil on a string name, plain assignment.)  === EDIT 2 — AI_Commander.sqf : apply the jitter once, after the shared release === Anchor (current, lines 22-26):    //--- Wait for full server init before commanding.   waitUntil {sleep 1; !(isNil "serverInitFull")};    //--- V0.2: pick a doctrine once - the primary factory path this AI builds around.   if (isNil {_logik getVariable "wfbe_aicom_doctrine"}) then {  AFTER (insert the jitter block between the waitUntil and the doctrine pick — runs exactly once, before _lt* init at :72 and the while loop at :93):    //--- Wait for f… [truncated — full sketch regenerable]

Soak test: Greppable: after MISSINIT, the server RPT/AICOM log should show TWO distinct one-time lines, one per AI side, with DIFFERENT jitter values, e.g. grep "SUPERVISOR_JITTER" <rpt> returns two AICOMSTAT|v1|EVENT|WEST|0|SUPERVISOR_JITTER|<x> and ...|EAST|0|SUPERVISOR_JITTER|<y> with x != y (both in [0,7)); plus the human-readable AI_Commander.sqf: [WEST] spawn phase-jitter Ns / [EAST] ... INFORMATION lines. Observable behaviour in a soak: the periodic server-FPS dip that previously coincided with both sides' first economy evaluation (and subsequent LCM realignments) should be visibly small… [truncated — full sketch regenerable]

svc-townpick-cache-and-fwd-factory-target

Target the self-service detour at the FORWARD owned town nearest the front and cache the pick, so resupply pulls teams the least far back

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Functions/Common_AICOMServiceTick.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_SVC_TOWNPICK_TTL = 30 (Init_CommonConstants.sqf (AICOM self-service block, immediately after line 343 WFBE_C_AICOM_SVC_TRIGGER_DIST))
CONTEXT (anchored, B68 live): - Common_AICOMServiceTick.sqf:122-135 = IDLE branch town pick. Currently chooses the OWNED town with the SMALLEST `_ldr distance _twn` (line 128/129) => on an advancing front this is usually a REAR town, dragging the team backward. Runs the full forEach-over-`towns` + per-candidate nearEntities[["Man"],_safeDist] safe-scan (line 130) on EVERY 8s IDLE tick, no cache (the heaviest part of the tick). - The most-forward metric we copy is the W17 idiom at AI_Commander_Wildcard.sqf:985-990: for each owned town, `_dd = min over enemy towns of (town distance enemyTown)`; smallest `_dd` = front-line town. - Enemy sideID: production uses `_enemyID = _enemySide Call WFBE_CO_FNC_GetSideID` (Wildcard:209). Service tick already has `_enemySide` (line 46) and `_sideID` (line 37). Town owner read = `_twn getVariable ["sideID", -1]` (bracket form is valid on TOWN logics; the file header only forbids it on GROUPS). - Group state vars in this file use plain `getVariable "x"` + `isNil` (A2-safe). The cache reuses that exact idiom.  ================ CHANGE 1: new constant (Init_CommonConstants.sqf) ================ AFTER line 343 (WFBE_C_AICOM_SVC_TRIGGER_DIST), ADD (nil-guarded, A2-safe, default no-op-safe):    if (isNil "WFBE_C_AICOM_SVC_TOWNPICK_TTL") then {WFBE_C_AICOM_SVC_TOWNPICK_TTL = 30}; //--- s: cache the chosen self-service town this long before re-running the full all-towns safe-scan (the heavy IDLE-tick cost). The en-route SAFE_DIST abort still runs every tick, so a stale pick can never send a team toward a freshly-hot town.  ================ CHANGE 2: forward pick + cache (Common_AICOMServiceTick.sqf) ================ Add the two cache vars to the `private` list at line 33 (append): "_pickTwn","_pickStamp","_ttl","_enemyID","_enemyTowns","_fwdD","_eTwn","_ed".  --- BEFORE (lines 122-136) ---     //--- nearest SAFE friendly town-centre within reach     _reach = missionNamespace getVariable ["WFBE_C_AICOM_SVC_REACH", 4000];     _best = objNull; _bestD = 1e9;     {         _twn = _x;         if ((_twn getVariable ["sideID", -1]) == _sideID) then {             _d = _ldr distance _twn;             if (_d < _bestD && {_d <= _reach}) then {                 if (({alive _x && {side _x == _enemySide}} count ((getPos _twn) nearEntities [["Man"], _safeDist])) == 0) then {                     _best = _twn; _bestD = _d;                 };             };         };     } forEach towns;     if (isNull _best) exitWith {}; //--- nothing safe in reach -> keep fighting  --- AFTER ---     //--- CACHE GATE: reuse a recent pick (cuts the O(towns*nearEnti… [truncated — full sketch regenerable]

Soak test: Soak greppable: new diag_log line AICOMSTAT|v1|EVENT|<side>|<min>|SERVICE_PICK_FWD|<townName>|fwdD=<m> fires on every CACHE-MISS pick. Grep the RPT for it: (1) the chosen should be a FORWARD town (near contested front), not the rear HQ town; (2) fwdD (min metres to nearest enemy town) should trend SMALL for advancing teams; (3) count of SERVICE_PICK_FWD lines per team per minute should be roughly <= 60/TTL (i.e. ~2/min at TTL=30) — far fewer than the prior ~1/8s scan rate, confirming the cache is suppressing the heavy scan. Cross-check existing SERVICE_ENROUTE/SERVICE_DONE l… [truncated — full sketch regenerable]

territory-credited-press-gate

Credit held territory in the PRESS/DEFEND posture gate so the dominant side keeps attacking

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_TOWN_STRENGTH = 2 (Common/Init/Init_CommonConstants.sqf — inside the B68 ATTACK-BIAS AICOM block, immediately after WFBE_C_AICOM_STR_LONE_FARHQ on line 280 (same isNil-guarded one-liner pattern as lines 277-280). Consumed ONLY in AI_Commander_Strategy.sqf POSTURE gate.)
GOAL: Credit garrisoned territory as effective strength in the POSTURE gate ONLY (lines 572-576 of AI_Commander_Strategy.sqf), so a side that holds many towns but has thin maneuver bodies (_myStr) does not falsely trip DEFEND. Raw _myStr stays unchanged everywhere else (LAST-STAND line 66; HQ-STRIKE lines 513/515) — those are genuinely about bodies committed to a strike.  === EDIT 1 — Init_CommonConstants.sqf, add new constant after line 280 === BEFORE (line 280, end of the LONE-remnant pair):     if (isNil "WFBE_C_AICOM_STR_LONE_FARHQ") then {WFBE_C_AICOM_STR_LONE_FARHQ = 1500};   //--- ...AND farther than this (m) from HQ...  AFTER (insert one new guarded line directly below line 280):     if (isNil "WFBE_C_AICOM_STR_LONE_FARHQ") then {WFBE_C_AICOM_STR_LONE_FARHQ = 1500};   //--- ...AND farther than this (m) from HQ...     //--- B69 territory-credited-press-gate (root of the dominant-but-passive stall, see Strategy.sqf:580-591 STALL block).     //--- Effective strength = maneuver bodies + (owned towns * this), used ONLY in the POSTURE/PRESS/DEFEND gate so a     //--- side winning on holdings keeps attacking instead of freezing when its few maneuver teams attrit below the     //--- enemy's concentrated stack. 5-town lead at default 2 = +10 eff bodies (~one team). 0 = exact current behaviour.     if (isNil "WFBE_C_AICOM_TOWN_STRENGTH") then {WFBE_C_AICOM_TOWN_STRENGTH = 2};         //--- per-town effective-strength credit in the posture gate only. Rollback to current: 0.  === EDIT 2 — AI_Commander_Strategy.sqf, compute _myEff/_enEff just before line 572 and rewire the gate === Note: _myTowns/_enemyTowns are already computed (lines 36-39), _myStr/_enStr already computed (lines 49-63). Pure scalar arithmetic, zero extra scan.  BEFORE (lines 571-577):     private ["_posture","_primT"];     _posture = if (_strikeOn) then {"HQ_STRIKE"} else {         if (_myTowns < _enemyTowns || {_myStr < _enStr}) then {"DEFEND"} else {             if (_myTowns >= (_enemyTowns * 1.2) && {_myStr >= _enStr}) then {"PRESS"} else {"HOLD"}         }     };     diag_log ("AICOMSTAT|v1|POSTURE|" + _sideText + "|" + str (round (time / 60)) + "|" + _posture + "|myTowns=" + str _myTowns + "|enTowns=" + str _enemyTowns + "|myStr=" + str _myStr + "|enStr=" + str _enStr + "|strikeOn=" + str _strikeOn);  AFTER:     private ["_posture","_primT","_townStr","_myEff","_enEff"];     //--- B69 territory-credited-press-gate: effective strength credits held towns (garrison bodies never counted in _myStr).     //--- POSTURE-gate ONLY; last-stand (l.66) + HQ-strike (l.513/515) keep reading raw _myS… [truncated — full sketch regenerable]

Soak test: PRIMARY (greppable A/B): the POSTURE diag_log now carries the new fields. Grep the server RPT for: AICOMSTAT|v1|POSTURE|...|myEff=...|enEff=...|townStr=2 Soak pass condition: on a board where one side holds >=2x the enemy's towns, that side's POSTURE line should now read PRESS (or HOLD), NOT DEFEND, even while myStr<enStr — i.e. a line like |PRESS|myTowns=6|enTowns=1|myStr=14|enStr=20|myEff=26|enEff=22|townStr=2. CORROBORATING (absence signal): the existing STALL line AICOMSTAT|v1|STALL||... should STOP appearing for the dominant side once it flips to PRESS (it only fires when a >… [truncated — full sketch regenerable]

wedge-watchdog-resync-stuckstrikes

Reset the AssignTowns stuck-strike ladder when the wedge-watchdog releases a team back to towns mode

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/AI/Commander/AI_Commander_Strategy.sqf

PROBLEM (anchored to live B68 source) The wedge-watchdog release block in AI_Commander_Strategy.sqf (lines 480-493) fires when a team wedges in "defense"/"move" mode with no movement and no contact. On release it resets relief/strike, flips mode to "towns" via SetTeamMoveMode, clears wfbe_aicom_townorder (line 486), clears the wedge breadcrumb (line 487), and bumps the HC order seq (488-490). But it never touches wfbe_aicom_stuckstrikes.  That counter is the AssignTowns unstuck/abandon ladder (AssignTowns.sqf:199-200 increments it; :208 blacklists the town once _strk > WFBE_C_AICOM_STUCK_ABANDON=4; :224 is the ONLY place it is zeroed, on genuine progress). A team that earlier ground a towns target up to e.g. strikes=4, then got pulled into relief/strike, then wedges and is released back to "towns", carries that STALE strikes=4 into its very first new towns order. On the next AssignTowns pass that fresh, perfectly-reachable target can immediately over-escalate the unstuck tier or get blacklisted on contact with the wedge logic at :208 — a clean target abandoned because the ladder was never re-synced to the mode transition.  FIX One unconditional reset, placed beside the sibling state-clears in the release block. Use the exact 2-arg (server-local, non-broadcast) setVariable form that AssignTowns.sqf:224 uses, so storage semantics match and nothing is broadcast.  PSEUDO-DIFF (AI_Commander_Strategy.sqf, inside the release block ~line 486-487)    ... (line 485) [_wTeam, "towns"] Call SetTeamMoveMode;       (line 486) _wTeam setVariable ["wfbe_aicom_townorder", []]; +                //--- RESYNC: zero the AssignTowns unstuck/abandon ladder on the mode transition, +                //--- so the released team starts its next towns order with a clean strike budget +                //--- (otherwise a stale stuckstrikes from a PRIOR towns assignment can instantly +                //--- over-escalate the unstuck tier or blacklist a fresh reachable target). +                //--- 2-arg form = server-local, non-broadcast, identical to AssignTowns.sqf:224. +                _wTeam setVariable ["wfbe_aicom_stuckstrikes", 0];       (line 487) _wTeam setVariable ["wfbe_aicom_wedge_bc", nil];   ... (line 488) if (_wTeam getVariable ["wfbe_aicom_hc", false]) then { ...seq bump... };  A2-OA-1.64 SAFETY - setVariable [name, value] with a 2-element array: core A2-OA, no broadcast flag. No A3 syntax. - No isEqualType/isEqualTo/typed-params/isNull-on-array. _wTeam already validated non-null + has alive units at line 462; setVariable on a valid group is safe. - No publicVariable, n… [truncated — full sketch regenerable]

Soak test: Soak observability (no new diag line strictly required — the existing two events bracket it): 1. Behavioural: after a WEDGE_RELEASE event, the released team should NOT immediately abandon its next town. Grep the RPT for a WEDGE_RELEASE followed within the same/next minute by a TARGET_ABANDON for the SAME team= token — that correlation should DISAPPEAR after the fix. grep -E "AICOMSTAT\|v2\|EVENT\|.*\|(WEDGE_RELEASE|TARGET_ABANDON)\|" .rpt (pre-fix: TARGET_ABANDON team= often appears 1-2 lines after WEDGE_RELEASE team=; post-fix: gone) 2. Strike ladder: STUCKSTAT lines … [truncated — full sketch regenerable]

wildcard-escalation-bidirectional-killratio

Make Wildcard losing-side escalation bidirectional: de-escalate combat cards when the AI is dominating humans

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Functions/AI_Commander_Wildcard.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AI_COMMANDER_WILDCARD_DEESCALATION_MULT = 1.0 (Common/Init/Init_CommonConstants.sqf (V0.6 Wildcard block, after line 208 WFBE_C_AI_COMMANDER_WILDCARD_INTERVAL). Ship 1.0 = behaviour-neutral no-op for soak-first; owner flips to 0.5 to enable. Read in AI_Commander_Wildcard.sqf with a nil-guarded getVariable default of 1.0 so the constant is optional.)
  • new const WFBE_C_AI_COMMANDER_WILDCARD_KILLRATIO_GATE = 1.5 (Common/Init/Init_CommonConstants.sqf (same V0.6 Wildcard block). De-escalate only when this side's cumulative KilledEnemy/Casualties exchange ratio >= this gate (AI is genuinely dominating, not just town-ahead). 1.5 = AI killing 50% more than it loses. Nil-guarded read in the Wildcard file.)
GOAL: add a symmetric _winning branch to the escalation block so a dominating AI draws FEWER free combat platoons (W4/W6/W7/W13/W18/W19/W21), biasing toward benign econ cards W1/W2. Gated on (a) a live human on the enemy side, (b) town-gap ahead, AND (c) a favourable kill-ratio from the supervisor counters. All commands A2-OA-1.64-safe (getVariable / count allUnits / isPlayer / side / alive / str / round). publicVariable is NOT involved (pure server-side read of WF_Logic counters set by the kill handlers).  --- A) NEW CONSTANTS  (Init_CommonConstants.sqf, after line 208) -------------   if (isNil "WFBE_C_AI_COMMANDER_WILDCARD_INTERVAL") then {WFBE_C_AI_COMMANDER_WILDCARD_INTERVAL = 900}; + //--- B68: bidirectional Wildcard. When the AI is DOMINATING a human lobby, scale combat-card + //--- weights DOWN (default 1.0 = no-op; owner sets 0.5 to enable). Gated on enemy-human presence, + //--- town-gap ahead, AND kill-ratio (KilledEnemy/Casualties) >= the gate. AI-vs-AI soaks untouched. + if (isNil "WFBE_C_AI_COMMANDER_WILDCARD_DEESCALATION_MULT") then {WFBE_C_AI_COMMANDER_WILDCARD_DEESCALATION_MULT = 1.0}; + if (isNil "WFBE_C_AI_COMMANDER_WILDCARD_KILLRATIO_GATE")   then {WFBE_C_AI_COMMANDER_WILDCARD_KILLRATIO_GATE   = 1.5};  --- B) PRIVATE DECLARATIONS  (AI_Commander_Wildcard.sqf, line 152) -----------   Add to the existing private list (line 152 currently: ..."_gapThresh","_eMult",): +   "_deMult","_krGate","_enemyHuman","_csKilledMe","_csCasMe","_killRatio","_winning",  --- C) STATE BLOCK  (AI_Commander_Wildcard.sqf, after line 218) --------------   _gapThresh = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_WILDCARD_ESCALATION_TOWNS", 5];   _losing    = ((_enemyTowns - _myTowns) >= _gapThresh);   _eMult     = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_WILDCARD_ESCALATION_MULT", 2.0]; + + //--- B68 BIDIRECTIONAL: de-escalate combat cards when this AI is crushing a HUMAN enemy. + _deMult = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_WILDCARD_DEESCALATION_MULT", 1.0]; + _krGate = missionNamespace getVariable ["WFBE_C_AI_COMMANDER_WILDCARD_KILLRATIO_GATE",   1.5]; + //--- (a) live human on the ENEMY side? (same count-allUnits idiom as W11 line 314) + _enemyHuman = ({alive _x && {isPlayer _x} && {_x isKindOf "Man"} && {side _x == _enemySide}} count allUnits) > 0; + //--- (b) kill-ratio from the supervisor's existing free counters (AI_Commander.sqf:436-439 reads + //---     the SAME str-side keys). Pure read, no new scan. Guard div-by-zero. + _csKilledMe = WF_Logic getVariable [Format ["%1KilledEnemy", str _side], 0]; + _csCasMe    = WF_Logi… [truncated — full sketch regenerable]

Soak test: SOAK GREP: tail the server RPT for the existing WILDCARD emit, now carrying the new fields: grep "AICOMSTAT|v2|EVENT|" rpt | grep "WILDCARD_W" | grep "winning=true" Expected on a human-vs-AI round where the AI pulls ahead: lines flip to winning=true enemyHuman=true killRatio>=1.5 deMult=0.5, and the draw distribution over the following ~10 ticks shifts toward WILDCARD_W1 / WILDCARD_W2 (econ) with fewer W6/W13/W19/W21 (free combat platoons/gunships/VBIEDs). Behaviour-neutral assertion for the default-on-1.0 ship: with WFBE_C_AI_COMMANDER_WILDCARD_DEESCALATION_MULT=1.0 there must be ZERO `winn… [truncated — full sketch regenerable]

wildcard-free-platoon-teamcap-gate

Gate Wildcard free air-platoon cards (W6 Air Cavalry / W19 QRF) on the side's live team count vs TOTAL_AI_MAX

Files: C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Server/Functions/AI_Commander_Wildcard.sqf, C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf

  • new const WFBE_C_AICOM_WILDCARD_TEAMCAP_FRAC = 0.9 (C:/Users/Game/a2waspwarfare/Missions/[55-2hc]warfarev2_073v48co.chernarus/Common/Init/Init_CommonConstants.sqf (AICOM block, right after WFBE_C_AI_COMMANDER_TOTAL_AI_MAX at line 136; use the same if (isNil ...) then {...}; idiom the neighbouring AICOM constants use so a live override survives))
GOAL: zero the two FREE unit-founding air-platoon wildcard cards (W6 Air Cavalry, W19 QRF) when the drawing side is already at/near its AI budget, so a losing side's x2 escalation can't hand it back-to-back free platoons and over-stack the server. All other (funds/supply/upgrade/strike) cards are untouched, so the existing re-draw loop naturally falls through to a non-unit card.  NAME FIX (load-bearing): the finding cites `WFBE_C_AICOM_TOTAL_AI_MAX` and `wfbe_units_west/_east` cache. The ACTUAL live constant is `WFBE_C_AI_COMMANDER_TOTAL_AI_MAX` (=190, Init_CommonConstants.sqf:136) — the AICOM_-prefixed name does NOT exist. Rather than read the `wfbe_units_*` cache (only written every audit cycle, needs isNil defence, and is what the finding worried about), MIRROR THE FOUNDING-PATH SIDE-GATE verbatim — exactly the gate the finding says these cards bypass — from AI_Commander_Produce.sqf:28-30:     _cap   = missionNamespace getVariable "WFBE_C_AI_COMMANDER_TOTAL_AI_MAX";     _sideAI = {(side _x == _side) && !(isPlayer _x)} count allUnits;     if (_sideAI >= _cap) exitWith {}; This is live, JIP-durable (no PV, no cache), A2-OA-safe (count/forEach/side/isPlayer only), and is the SAME budget the producer enforces — so the wildcard now respects the identical ceiling instead of an end-run around it.  SCOPE CHECK: `_side` and `_logik` are bound at AI_Commander_Wildcard.sqf:199-203 inside the same per-side block; the eligibility flags `_w6Eligible`/`_w19Eligible` are set at lines 258-288 / 404-410; the weight-zeroing runs at lines 445-463. The new gate slots in immediately BEFORE the existing zeroing lines for W6/W19 (so escalation at 435-443 has already been applied and we simply force the weight to 0).  --- INSERT A: AI_Commander_Wildcard.sqf, immediately after line 410 (end of the W19 eligibility block), BEFORE the `//--- BASE WEIGHTS + ESCALATION` comment at 412 --- BEFORE (410-412):     } forEach _owned;     }      //--- -----------------------------------------------------------------------     //--- BASE WEIGHTS + ESCALATION AFTER:     } forEach _owned;     }      //--- B68 (wildcard-free-platoon-teamcap-gate): the W6 Air Cavalry + W19 QRF cards FOUND a whole     //--- free elite air-assault platoon via Common_RunCommanderTeam with NO funds cost and (until now)     //--- NO budget check — so a losing side's x2 combat-card escalation could hand it back-to-back free     //--- platoons and over-stack AI exactly when the server is strained. Mirror the founding-path     //--- side-gate (AI_Commander_Produce.sqf:28-30) the cards bypass: if the side is already at… [truncated — full sketch regenerable]

Soak test: Soak grep: AICOMWC|v1|teamcap| in the server RPT/diag log. Each emit means a near-saturated side was denied a free air platoon, with side|card(W6/W19)|sideAI|cap|frac inline — so you can confirm it ONLY fires when sideAI >= 0.9*cap (≈171+ at default 190) and ONLY on a losing/at-budget side. Expected during a normal soak: zero or rare emits early game (sides below budget), occasional emits late game on a stacked side. Cross-check against AICOMSTAT|v1|TICK (AI_Commander.sqf:326, fields _fTeams/_eTeams/units=) to confirm the suppressed side's unit count plateaus AT the cap instead of overshooti… [truncated — full sketch regenerable]

Provenance — other agents' wiki findings folded in

The deep pass harvested 96 open AICOM items other fleet agents had already logged, and built on them. Source pages credited:

  • AI-Commander-Autonomy-Audit.md (14)
  • Pending-Owner-Decisions.md (12)
  • Commander-HQ-Lifecycle-Atlas.md (5)
  • Dead-Code-And-Stale-Code-Register.md (5)
  • Feature-Status-Register.md (4)
  • PR-Cleanup-And-Integration-Lab.md (2)
  • Commander-Positions-Branch-Audit.md (2)
  • agent-feature-status.jsonl#b68-aicom-live-branch-route-2026-06-21 (line 110); agent-events.jsonl (lines 1225-1226) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Upstream-Miksuu-Commit-Intel.md (lines 14,16); Upstream-Changelog-Feature-Leads.md line 54 (1)
  • C:/Users/Game/a2waspwarfare.wiki/Upstream-Miksuu-Commit-Intel.md (lines 16) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Upstream-Miksuu-Commit-Intel.md (lines 17,212) (1)
  • Commander-Reassignment-Call-Shape.md (1)
  • Commander-Vote-And-Reassignment-Playbook.md (1)
  • Deep-Review-Findings.md (DR-15 resolution note) (1)
  • Hardening-Implementation-Roadmap.md (P2 'HC delegation/failover' row); Headless-Delegation-And-Failover-Playbook (linked) (1)
  • Headless-Client-Scaling-And-Topology.md (Known gaps; Tier 3 #9) + AI-Runtime-HC-Loop-Map.md (Generic delegate-ai row) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Upstream-Miksuu-Commit-Intel.md (line 260); Developer-History-And-Upstream-Lessons.md line 260 (1)
  • Headless-Client-Scaling-And-Topology.md (roadmap #5, Specialisation) + Headless-Delegation-And-Failover-Playbook.md (1)
  • Headless-Client-Scaling-And-Topology.md (roadmap Tier 1 #1) + Headless-Delegation-And-Failover-Playbook.md (1)
  • Headless-Client-Scaling-And-Topology.md (Running a 2nd HC; What more HC capacity unlocks) (1)
  • Headless-Client-Scaling-And-Topology.md (Specialising HCs; What more HC capacity unlocks) (1)
  • Headless-Client-Scaling-And-Topology.md (Tier 3 #8; Observability) + AI-Headless-And-Performance.md (Performance Audit) (1)
  • Headless-Delegation-And-Failover-Playbook.md (DR-21, Phase 3 disconnect policy) + Headless-Client-Scaling-And-Topology.md (#3) + AI-Headless-And-Performance.md (1)
  • Headless-Delegation-And-Failover-Playbook.md (DR-42, Phase 1) + Headless-Client-Scaling-And-Topology.md (#2) + AI-Runtime-HC-Loop-Map.md (High severity) (1)
  • Headless-Delegation-And-Failover-Playbook.md (Risks + Phase 4) + Headless-Client-Scaling-And-Topology.md (#4) + AI-Headless-And-Performance.md (HC timing caveat) + AI-Runtime-HC-Loop-Map.md (1)
  • Player-AI-Caps-And-Role-Balance.md (Balance Suggestions + Dynamic Cap Proposal) + Headless-Client-Scaling-And-Topology.md (All factory-purchased units server-pinned) (1)
  • Quad-AI-Commander.md (1)
  • Headless-Client-Scaling-And-Topology.md (Known gaps; roadmap item #6) + AI-Runtime-HC-Loop-Map.md Findings (Server_OperateTownDefensesUnits.sqf:24); also Headless-And-Performance.md (1)
  • C:/Users/Game/a2waspwarfare.wiki/Upstream-Miksuu-Commit-Intel.md (line 260, 'AI, HC, Cleanup And Runtime'); also Developer-History-And-Upstream-Lessons.md line 260 (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 87,134-141,227); Developer-History line 259 (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 86,110,128-130,226) (1)
  • agent-feature-status.jsonl#hc-registration-clientfps-trust-edges (line 42) (1)
  • agent-feature-status.jsonl#hc-registration-clientfps-trust-edges (line 42); agent-events.jsonl#pr-board-aicom-closure-refresh-2026-06-21 (lines 1193-1194) (1)
  • agent-knowledge.jsonl#gameplay-systems-atlas-route-prune-2026-06-14 (lines 282,307); agent-feature-status.jsonl#mini-scout-wave-config-factory-ui-ai-cleanup (line 76); agent-events.jsonl#factory-purchase-atlas (Factory-And-Purchase-Systems-Atlas.md owns latent AIBuyUnit) (1)
  • AI-Headless-And-Performance.md (Commander Team Order Variables, Command tasks row) (1)
  • AI-Headless-And-Performance.md (Commander Team Order Variables) + AI-Runtime-HC-Loop-Map.md (Commander map-order executor row + Medium finding) (1)
  • AI-Runtime-HC-Loop-Map.md (AI town attack-path helper row + Medium finding) (1)
  • AI-Runtime-HC-Loop-Map.md (Medium finding) + Headless-Delegation-And-Failover-Playbook.md (Risks) + AI-Headless-And-Performance.md (1)
  • Attack-Wave-Authority-Playbook.md (1)
  • Audit-Findings-Queue-2026-06-03.md (AI10) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI11, Claude deep-dive) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI14) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI16) (1)
  • Source-Fix-Propagation-Queue.md ('AI commander upgrade debit/cost lookup' row); AI-Commander-Autonomy-Audit#ai-upgrade-debit-branch-matrix (linked) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI3) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI7) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI8) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI9) (1)
  • Audit-Findings-Queue-2026-06-03.md (SG15) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Developer-History-And-Upstream-Lessons.md (line 156) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Developer-History-And-Upstream-Lessons.md (lines 16,182,255-257); Upstream-Miksuu-Commit-Intel lines 255-257 (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 11,38,52,82,320,327); Developer-History-And-Upstream-Lessons line refs (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 127-128,249,292) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 14,110,212,235); Upstream-Changelog notes (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 40,84,112-116,184,213,248,304); Developer-History line 156 (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 55,65,75,216) (1)
  • C:/Users/Game/a2waspwarfare.wiki/Old-WarfareBE-Performance-Comparison.md (lines 64,140) (1)
  • Audit-Findings-Queue-2026-06-03.md (AI6) (1)
  • Source-Fix-Propagation-Queue.md ('Resource income payout/display drift' row); Economy-Towns-And-Supply#resource-income-branch-matrix (linked) (1)

Continue Reading

Previous: AI commander B69 improvement roadmap | Next: AI commander capture and fun plan

Main map: Home | AI route: AI runtime/HC loop map | Status: Progress dashboard

Sidebar

Clone this wiki locally