Skip to content

Deep Review Findings

rayswaynl edited this page Jul 13, 2026 · 66 revisions

Deep-Review Findings

Claude adversarial-review log (source-cited). This page records findings from independent verification passes against the Chernarus source mission, cross-checking the SQF code atlas, Gameplay systems atlas and other pages. Each finding is confirmed against path:line, given a severity, and paired with a remediation playbook. It complements (does not replace) Codex's atlas pages and the Feature status register.

All paths are relative to the source mission root Missions/[55-2hc]warfarev2_073v48co.chernarus/. Arma 2 OA 1.64 — not Arma 3.

Current Routing Index

Read this page as an evidence ledger. For day-to-day work, start from the owner page below and use the DR entry only when you need the original source proof or fix shape.

Finding family DRs Current canonical route Status
PVF, direct-PV and network-data compile trust boundaries DR-1, DR-27, DR-30, DR-38, DR-41, DR-44, DR-46 Networking/PV, Public variable channel index, PVF dispatch playbook, Server authority map, Hardening roadmap DR-1/DR-38 dispatcher lookup is partial source-present on current stable; registered allowlist/logging, sender auth, direct-PV and SEND_MESSAGE work remain code-owner lanes.
Economy, construction, purchases, services and upgrade authority DR-6, DR-14, DR-16, DR-22, DR-23, DR-28, DR-33 Feature status, Construction/CoIn, Factory/purchase, Economy authority first cut, Gear/loadout/EASA Source-backed; route fixes through owner playbooks and smoke packs.
Commander, HQ and victory correctness DR-11, DR-12, DR-13, DR-15, DR-20, DR-36, DR-47 Commander/HQ lifecycle, Commander vote/reassignment, Victory/endgame, Pending owner decisions Source-backed; DR-47 is source-unpatched and separate from DR-15.
Generated mission drift, modded stubs and release propagation DR-2, DR-4, DR-32, DR-43 Tools/build, Content/maps, Source fix propagation queue, Tooling release readiness audit Source Chernarus and maintained Vanilla Takistan are the release focus; modded missions are non-authoritative unless regenerated.
Respawn, MASH, markers, WASP and partial/dead features DR-3, DR-24, DR-25, DR-34, DR-35, DR-40 Feature status, Respawn/death, WASP overlay, Abandoned feature revival, UI IDD collision repair Keep source evidence; do not expand duplicate archaeology pages.
Headless, JIP, lifecycle and performance review cells DR-5, DR-18, DR-19, DR-21, DR-37, DR-39, DR-42, DR-45 AI/headless/performance, Headless delegation/failover, Lifecycle wait-chain, Performance opportunity sweep, Town AI vehicle despawn safety Mixed source-backed bugs, clean reviews and smoke-pending propagated fixes.
External integrations, AntiStack, DiscordBot, Extension and governance DR-7, DR-8, DR-9, DR-10, DR-26, DR-29, DR-31 External integrations, Integration trust boundary audit, AntiStack DB extension audit, Community & Dev Source-backed; secrets/config/runtime deployment choices remain owner decisions.
External report intake and clean-review records DR-17, DR-26, DR-35, DR-36, DR-37, DR-38, DR-39, DR-40, DR-43 External research reports, Codebase coverage ledger, Wiki pruning ledger Keep as provenance and false-positive guards; avoid copying them into new pages.

Round 2 — 2026-06-01 (Claude)

DR-1 — PV dispatch is a server-side Call Compile trust boundary (SECURITY / hardening) — High

Current status 2026-06-21: the raw Call Compile evidence below is historical for current stable origin/master@0139a346. Source Chernarus now uses missionNamespace getVariable _script plus typeName == "CODE" guards at Server_HandlePVF.sqf:14-15 and Client_HandlePVF.sqf:32-33 via 7d60b02b4, and maintained Vanilla Takistan has the same lines via 9b49883cb. The dispatcher still lacks an explicit registered-handler allowlist and rejection logs, and DR-55 sender authentication plus direct-PV channels remain open. See PVF dispatch playbook for current branch status.

What. The PVF dispatch executes a string taken directly from the value a remote machine broadcast, with no validation that it names a known command.

  • Server/Functions/Server_HandlePVF.sqf: _script = _publicVar select 0; _parameters Spawn (Call Compile _script);
  • Client/Functions/Client_HandlePVF.sqf: same pattern on _publicVar select 1.
  • The handlers are bound in Common/Init/Init_PublicVariables.sqf as WFBE_PVF_<Command> addPublicVariableEventHandler { (_this select 1) Spawn WFBE_SE_FNC_HandlePVF }. _this select 1 is the new value chosen by the sender.
  • Clients legitimately reach the server channel via Common/Functions/Common_SendToServerOptimized.sqf:15publicVariableServer 'WFBE_PVF_<cmd>'.

The legitimate flow always sets index 0 to the bare identifier "SRVFNC<Command>", so Call Compile resolves a function-variable lookup. But nothing constrains the value to that shape: a crafted broadcast on a registered WFBE_PVF_* channel whose select 0 is arbitrary SQF text would be compiled and run on the receiver. This is the well-known Arma 2 publicVariable trust-boundary problem.

Why the usual mitigations are absent here.

  • No server-side validation. Server_HandlePVF.sqf does not check that _script begins with SRVFNC or is in the registered command set (confirmed: no SRVFNC/CLTFNC guard in either handler).
  • No BattlEye filtering. BattlEyeFilter/publicvariable.txt contains a single rule, 5 "kickAFK". That rule is the AFK-kick feature (the mission cannot kick directly, so it broadcasts kickAFK and relies on BattlEye to kick the sender — see Networking and public variables). It is not a security filter: there is no restrictive default line and no whitelist of the WFBE_PVF_* channels with value constraints.

Severity rationale. This is a live-server hardening gap, not a mission-logic bug. It matters for any publicly-listed server (the README advertises one). It is recorded here as defensive guidance for the server owner.

Remediation playbook (cheapest first):

  1. Validate before compile (server-side, no BE knowledge needed). In Server_HandlePVF.sqf, reject any _script not in the known set, e.g. build a lookup of "SRVFNC"+cmd for every registered command at init and exitWith (with a WARNING log) if _script is not a member. Same idea for Client_HandlePVF.sqf against CLTFNC*. This neutralizes arbitrary-string compilation while preserving all legitimate traffic.
  2. Add a real BattlEye publicvariable.txt as defense-in-depth: a restrictive default plus an explicit allow-list of WFBE_PVF_*, the direct channels in the SQF code atlas "Direct Public Variable Channels" table, and the existing kickAFK rule. Do not add a blanket restrictive default without the allow-list, or you will break all PVF traffic.
  3. Prefer publicVariableClient/publicVariableServer (targeted) over broadcast for owner-directed messages to shrink the surface (already mostly done).

DR-2 — Paratrooper drop map markers were dead before source/Vanilla propagation — Superseded / smoke pending

Current status: this raw finding is historical for the maintained source path. Source Chernarus and maintained Vanilla Takistan now register HandleParatrooperMarkerCreation, compile the client handler and ship the sender/handler pair; see Paratrooper marker revival. Arma 2 OA smoke is still pending, and modded eden/Napf/lingor still register/send the callback without shipping Client/PVFunctions/HandleParatrooperMarkerCreation.sqf.

Server/Support/Support_Paratroopers.sqf:117 sends the marker:

[leader _playerTeam, "HandleParatrooperMarkerCreation", [_x, _sideID]] Call WFBE_CO_FNC_SendToClient;

Common/Functions/Common_SendToClient.sqf turns that into WFBE_PVF_HandleParatrooperMarkerCreation (dedicated path) / CLTFNCHandleParatrooperMarkerCreation (hosted path). But:

  • At the time of DR-2, HandleParatrooperMarkerCreation was not in the _clientCommandPV list in Common/Init/Init_PublicVariables.sqf, so WFBE_PVF_HandleParatrooperMarkerCreation had no addPublicVariableEventHandler, and CLTFNCHandleParatrooperMarkerCreation was never compiled.

Historical result: on a dedicated server the marker PV was broadcast but never handled; on a hosted/SP server Spawn (Call Compile "CLTFNCHandleParatrooperMarkerCreation") resolved to nil. The fix has now been applied in source Chernarus and maintained Vanilla Takistan by adding HandleParatrooperMarkerCreation to _clientCommandPV. Remaining gates are Arma smoke and modded mission maintenance policy.

DR-3 — MASH tent map markers are orphaned in source/Vanilla (abandoned/half-implemented) — Medium

The intended MASH marker design is a two-hop relay:

  1. Client deploys tent → WFBE_CL_MASH_MARKER_CREATED → server EH in Server/Module/MASH/MASHMarker.sqf:1-13 re-broadcasts WFBE_SE_MASH_MARKER_SENT.
  2. The client EH that should consume WFBE_SE_MASH_MARKER_SENT lives in Client/Module/MASH/receiverMASHmarker.sqf:1-29 — but its only source/Vanilla reference is the commented compile at Client/Init/Init_Client.sqf:132:
    //WFBE_CL_FNC_ReceiverMASHmarker = Call Compile preprocessFileLineNumbers "Client\Module\MASH\receiverMASHmarker.sqf";

2026-06-04 recheck: current source Chernarus and maintained Vanilla Takistan are even more inert than the original receive-side framing. Client/Module/Skill/Skill_Officer.sqf:25-27 creates and stores the MASH object and adds undeploy, but does not broadcast WFBE_CL_MASH_MARKER_CREATED; Client/Module/Skill/Actions/Officer_Undeploy_MASH.sqf:19-21 deletes the object and resets cooldown without marker cleanup. The server relay is active, but has no source/Vanilla sender. Modded eden and lingor still emit WFBE_CL_MASH_MARKER_CREATED, which is useful archaeology, but that is sender-only drift rather than proof the maintained missions have working shared MASH markers.

Result: on old-shape maintained roots, deployed MASH tents can still be local respawn objects, but shared MASH map markers are orphaned: no live sender, no compiled receiver, no server-held marker list and no explicit JIP resend/pull. Current stable/B69-shaped roots remove the maintained-root deploy/module path instead; use Respawn and death lifecycle for the current branch matrix. Fix: either restore the feature as a server-authoritative marker registry with explicit JIP resend and unique marker names, or remove/comment the relay and receiver as dead code.

DR-4 — Generated-mission drift: Takistan is in sync; the skip-list is a silent-divergence trap; modded maps are abandoned (generated-mission drift) — Medium

A full recursive diff of Missions/[55-2hc]…chernarus vs Missions_Vanilla/[61-2hc]…takistan shows every difference is exactly a LoadoutManager skip-listed file or blacklisted directory — there is no accidental drift between the two at this commit:

  • Differing files: mission.sqm, version.sqf (git-ignored), Client/GUI/GUI_Menu_Help.sqf, WASP/unsort/StartVeh.sqf, loadScreen.jpg, texHeaders.bin, Server/Init/Init_Server.sqf (the SET_MAP 1→2 patch), and the blacklisted Common/Config/Core_Artillery/* + Server/Config/* + Textures/*.

This is more reassuring but also more dangerous than the "generated missions can drift" framing elsewhere. The precise hazard is the skip-list, defined in Tools/LoadoutManager FileManagement/FileManager.cs (ShouldSkipFile + the co.takistan directory blacklist):

A gameplay edit made in Chernarus to any skip-listed file — mission.sqm, GUI_Menu_Help.sqf, StartVeh.sqf, anything under Core_Artillery/, Server/Config/, or Textures/will never propagate to Takistan via dotnet run. These must be hand-mirrored in both missions.

So the standard guidance "edit Chernarus + run LoadoutManager" is incomplete: it is correct for the ~95% of files that are copied, and silently wrong for the skip-listed set. See the expanded propagation guidance in Tools and build workflow.

Modded maps are abandoned/stale. Because modded propagation is commented out at Tools/LoadoutManager SqfFileGenerators/SqfFileGenerator.cs:132, the Modded_Missions/* trees are far behind Chernarus (787 files):

Modded mission File count State
…Napf 507 ~280 files behind
…eden 502 ~285 behind
…lingor 438 ~349 behind
…dingor 20 stub
…smd_sahrani_a2 4 stub
…tavi 3 stub
…isladuala 1 stub

None are deployable as-is; they would need full regeneration after the modded path is re-enabled. Treat Modded_Missions/* as non-authoritative.

DR-5 — Frozen metric counts drift; make them reproducible (docs too confident) — Low

The SQF code atlas states "659 preprocessFile references (452 preprocessFileLineNumbers + 207 plain)". An independent recount on the docs/developer-wiki-index base yields 678 total / 465 preprocessFileLineNumbers / 213 plain. The gap is small and likely reflects counting method (comment handling) or branch timing — not an error — but hardcoded counts rot as the mission changes. Recommendation: present such counts as point-in-time and ship the regeneration command so future agents verify rather than trust, e.g. (PowerShell, from the mission root):

(Get-ChildItem -Recurse -Filter *.sqf | Select-String -SimpleMatch 'preprocessFileLineNumbers').Count

Verified-accurate cross-checks (for trust calibration): the atlas's FSM inventory is correct — exactly three .fsm files exist (Client/FSM/updateactions.fsm, Client/FSM/updateavailableactions.fsm, Client/kb/hq.fsm). AttackWave and LogGameEnd remain outside the standard PVF list; HandleParatrooperMarkerCreation was later moved into the client PVF list as a small revived feature.


Open items handed to Codex / code owners

  • DR-2 is source/Vanilla propagated; smoke and modded drift remain. Decide fix-vs-remove for DR-3 (MASH markers), which is still dead on the receive side.
  • DR-1 server-side validation is a small, safe gameplay-code change (Chernarus Server_HandlePVF.sqf) — gate behind review since it touches the network hot path.
  • Re-confirm DR-4 modded-map decision: regenerate or formally retire Modded_Missions/*.

Round 3 — 2026-06-01 (Claude) — PVF hardening implementation playbook

Lane pvf-hardening-review. This turns DR-1 into a concrete, behavior-preserving change set a code owner can apply to the Chernarus source. Every claim below was re-verified against source on this pass. Paths relative to Missions/[55-2hc]warfarev2_073v48co.chernarus/.

Why the dispatch never actually needs Call Compile

The dispatch targets are already-compiled global variables, created at registration in Common/Init/Init_PublicVariables.sqf:44,49:

Call Compile Format["CLTFNC%1 = compile preprocessFileLineNumbers 'Client\PVFunctions\%1.sqf'", _x];   // :44
Call Compile Format["SRVFNC%1 = compile preprocessFileLineNumbers 'Server\PVFunctions\%1.sqf'", _x];   // :49

So SRVFNCRequestJoin, CLTFNCTownCaptured, … are missionNamespace variables holding code. The dispatch line Spawn (Call Compile _script) only compiles the string _script to perform a variable lookup. That means the same resolution can be done with getVariable, which treats _script purely as a name and cannot execute an arbitrary SQF string a client injected.

Fix 1 — Primary, behavior-preserving (closes arbitrary code execution)

Replace the compile-the-string dispatch with a name lookup that defaults to a no-op.

Server/Functions/Server_HandlePVF.sqf (current _parameters Spawn (Call Compile _script);):

private ["_fn"];
_fn = missionNamespace getVariable _script;   // resolve SRVFNC<cmd>; unknown -> nil
if (isNil "_fn") exitWith {
    ["WARNING", format ["Server_HandlePVF: rejected unknown PVF command '%1' from network", _script]] Call WFBE_CO_FNC_LogContent;
};
if (typeName _fn != "CODE") exitWith {
    ["WARNING", format ["Server_HandlePVF: rejected non-code PVF command '%1' from network", _script]] Call WFBE_CO_FNC_LogContent;
};
_parameters Spawn _fn;

Client/Functions/Client_HandlePVF.sqf (current _parameters Spawn (Call Compile _script);): identical pattern resolving CLTFNC<cmd>.

Why this is safe and behavior-identical:

  • Legitimate traffic sets _script to exactly "SRVFNC"+cmd / "CLTFNC"+cmd (see Common/Functions/Common_SendToServerOptimized.sqf:15, Common/Functions/Common_SendToClient.sqf), which resolves to the compiled function — unchanged.
  • A hostile value such as "hint 'x'; <server-side effect>" is not a defined variable, so getVariable leaves _fn nil and exits with a WARNING. No compile ever runs on attacker text.
  • Use OA-safe isNil and typeName guards. Do not use isEqualTo here; it is an Arma 3-era command and will mislead OA patch authors.

Fix 2 — Optional explicit allow-list (defense-in-depth + clarity)

At the end of Init_PublicVariables.sqf, snapshot the legal command set so the handler check is explicit rather than implicit:

WFBE_SE_PVF_ALLOWED = [];
WFBE_CL_PVF_ALLOWED = [];
{WFBE_SE_PVF_ALLOWED = WFBE_SE_PVF_ALLOWED + [Format ["SRVFNC%1", _x]]} forEach _serverCommandPV;
{WFBE_CL_PVF_ALLOWED = WFBE_CL_PVF_ALLOWED + [Format ["CLTFNC%1", _x]]} forEach _clientCommandPV;
publicVariable "WFBE_SE_PVF_ALLOWED"; // optional; or keep server-local

Then guard with if !(_script in WFBE_SE_PVF_ALLOWED) exitWith { ...log... }; before resolving. Fix 1 already covers the same cases; use Fix 2 only if you want a named, auditable whitelist.

Fix 3 — BattlEye publicvariable.txt (defense-in-depth at the network edge)

The shipped BattlEyeFilter/publicvariable.txt is a single line, 5 "kickAFK", which is the AFK-kick feature trigger, not a security filter (see Networking and public variables → Security). A hardened filter uses a restrictive default first line plus explicit allow exceptions:

5 ""                                  // default: kick on any unlisted PV broadcast
!="^WFBE_PVF_[A-Za-z]+$"              // allow the dispatch channels
!="^wfbe_supply_temp_(west|east)$"    // allow the supply request channel
!="^(ATTACK_WAVE_INIT|CLIENT_INIT_READY|REQUEST_SUPPLY_VALUE|WFBE_CL_MASH_MARKER_CREATED|AFKthresholdExceededName|WFBE_C_PLAYER_OBJECT|WFBE_CLIENT_HAS_CONNECTED_AT_LAUNCH)$"
5 "kickAFK"                           // keep the AFK feature kick

⚠️ Validate the exact client→server channel list against the "Direct Public Variable Channels" table in the SQF code atlas before deploying — a too-strict default with a missing exception will kick legitimate players. BattlEye regex/escaping differs from SQF; test on a private server first. This complements, and does not replace, Fix 1 (BE filters the variable name, not the payload shape).

Residual risk this playbook does NOT close (scope honesty)

Fix 1 stops arbitrary code execution, but the PVF model still trusts client-sent commands and parameters. A malicious client can broadcast a legitimate command with chosen arguments — e.g. RequestChangeScore (Server/PVFunctions/RequestChangeScore.sqf sets a player's score directly from the payload) or RequestStructure — because the server handlers largely don't authenticate the sender against the payload. Closing that is a larger, per-handler effort (validate that owner/sender matches the acting player, clamp economy/score deltas, range-check structure requests). Treat this as a separate follow-up lane, not part of the dispatch fix. Documented here so the dispatch fix isn't mistaken for full PVF authorization.

Verification notes (this pass)

  • No registered handler under Server/PVFunctions/ or Client/PVFunctions, nor Server_HandleSpecial.sqf/HandleSpecial.sqf/LocalizeMessage.sqf, calls compile on its parameters — so once the dispatch command name is resolved safely, there is no second-order injection on the PVF path. DR-46 correction: the direct SEND_MESSAGE publicVariable path is outside PVF and does compile network message text; hardening therefore has two network-data compile surfaces: PVF dispatch command strings (DR-1) and SEND_MESSAGE payload text (DR-46). Other non-network call compile sites still compile files (Init_Coin.sqf, EASA/CM init) or local engine key-strings (coin_interface.sqf:774-777).
  • Handoff: this is a Chernarus-source gameplay change → after applying, run Tools/LoadoutManager dotnet run to propagate (note Server_HandlePVF/Client_HandlePVF are not on the skip-list, so they propagate normally; BattlEyeFilter/ lives outside the mission and is deployed with the server, not via LoadoutManager).

Round 4 — 2026-06-01 (Claude) — construction PVF authority (DR-6)

Lane construction-authority-review (extends Construction and CoIn systems atlas, whose "Authority Boundary" section flags this risk generically; this finding adds per-handler forgery proof + a validation design). This is the concrete realization of the command-forgery residual scoped in Round 3 (Fix 1 closes code execution, not command forgery).

DR-6 — Construction request handlers perform no sender/authority/resource validation — High (gameplay integrity)

All three construction PVF handlers derive trust from the client-supplied payload and never verify the requester. Source (verified this pass):

Handler Validation actually performed Client-controlled inputs Forgery impact
Server/PVFunctions/RequestStructure.sqf Only: does _structureType exist in WFBE_<side>STRUCTURENAMES. _side (select 0), class, pos, dir — all from payload. Forge ["RequestStructure",[WEST,"US_WarfareBHeavyFactory_EP1",anyPos,dir]] → server ExecVMs the construction script and builds a free factory anywhere for any side. No commander check, no funds debit, no placement/base-area/hostile-town checks (those are client-only, atlas "Placement Rules").
Server/PVFunctions/RequestDefense.sqf Only: does _defenseType exist in WFBE_<side>DEFENSENAMES. _side, class, pos, dir, _manned — all from payload. Forge unlimited free defenses — incl. AI-manned static weapons and minefields (Sign_Danger) — anywhere for any side, bypassing the base-area avail budget (decremented only client-side in coin_interface.sqf:724-730).
Server/PVFunctions/RequestMHQRepair.sqf None. Body is [_this] Spawn MHQRepair;. entire payload; MHQRepair.sqf:3 uses _side. MHQRepair rebuilds the side HQ from server state (GetSideHQ/GetCommanderTeam/position) with no dead-HQ check, no commander check, and none of the repair count/price gating that lives only client-side in Action_RepairMHQ.sqf. Forge ["RequestMHQRepair",[side]] → free, unlimited HQ respawn/repair; can also fire while the HQ is alive.

Root cause (why "add a check" is non-trivial here)

  1. _side is taken from the payload, not derived from the sender. A client can act on any side's behalf.
  2. The payloads don't even include the requesting player object, so the handlers cannot identify or authorize the sender as written.
  3. Arma 2 OA addPublicVariableEventHandler provides no sender identity_this is [varName, value] only (unlike Arma 3 remoteExecutedOwner/RE ownership). So authority must be reconstructed: the payload must carry the player, and the server must validate that player's role/side/funds against server-side state. (This is also why DR-1's command-validation fix does not, by itself, stop forgery.)

Validation playbook (behavior-preserving for legit CoIn UX)

Add the requesting player to each request and validate server-side before creating objects. Example for RequestStructure (mirror for RequestDefense; for RequestMHQRepair validate dead-HQ + commander + repair-count/price server-side):

// client (coin_interface / Action_*): include the player in the payload
["RequestStructure", [player, sideJoined, _class, _pos, _dir]] Call WFBE_CO_FNC_SendToServer;

// server RequestStructure.sqf (new guards, then existing logic):
_player = _this select 0; _side = _this select 1; _structureType = _this select 2; ...
if (isNull _player) exitWith {};
if (side _player != _side) exitWith {};                                   // can't build for another side
if (_player != leader ((_side) call WFBE_CO_FNC_GetCommanderTeam)) exitWith {};  // commander-only
private _cost = /* look up WFBE_<side>STRUCTURECOSTS[index] */;
if ((_side call WFBE_CO_FNC_GetSideSupply) < _cost) exitWith {};          // server-authoritative funds
[_side, -_cost, "structure build", false] call WFBE_CO_FNC_ChangeSideSupply;  // debit on server
// ...then the existing class-exists lookup + ExecVM construction script

Notes:

  • Keep the client cost-deduction/preview for instant UX, but make the server the final authority on debit and creation (atlas "Cost deduction" risk row agrees). Avoid double-debit by having the client send a request and the server be the source of truth (or reconcile on the server-confirmed message).
  • Re-checking full placement/collision server-side is heavier; at minimum validate side+commander+funds+class (cheap, closes the worst abuse). Base-area avail should also be decremented/validated server-side to stop the unlimited-defense path (trace RequestBaseArea + Construction_StationaryDefense together, per the atlas, for JIP safety).
  • This is gameplay-code; gate behind review (touches the build hot path) and run LoadoutManager after. RequestStructure/Defense/MHQRepair.sqf are not on the skip-list, so they propagate normally.

Severity framing

Not a crash/RCE (that's DR-1). This is gameplay-integrity / anti-grief: on a public server, a modified client can mint free factories/defenses/HQs and bypass the economy. Pair with DR-1 (validate the command) — DR-1 stops arbitrary code, DR-6 stops forged legitimate commands; both are needed for a hardened public server.

Round 5 — 2026-06-02 (Claude) — AntiStack DB extension trust (DR-7..DR-10)

Lane antistack-db-trust. The AntiStack module persists per-player score/side and team skill to an external native extension for team-balancing. Source: Server/Module/AntiStack/callDatabase*.sqf (7 callExtension sites) + the A2WaspDatabase DLL, which is not in this repo (only the in-repo Extension/ GLOBALGAMESTATS DLL is — see External integrations). All claims verified this pass.

DR-7 — The server call compiles the extension's return value on every call — High (external trust boundary)

Every one of the seven handlers does, in effect:

_response = "A2WaspDatabase" callExtension format ["%1,%2", _procedureCode, _parameters];
_response = call compile _response;          // <-- executes the DLL's stdout as SQF
_responseCode = _response select 0;

(callDatabaseStore.sqf, callDatabaseRetrieve.sqf ×2 incl. the 505 poll, callDatabaseSendPlayerList.sqf, callDatabaseRequestSideTotalSkill.sqf ×2, callDatabaseSetMap.sqf, callDatabaseStoreSide.sqf, callDatabaseFlushPlayerList.sqf.)

callExtension returns a string from a native DLL, and the mission compiles+executes it. The server therefore fully trusts the A2WaspDatabase process's stdout as code. Why this matters:

  • Compromise/replacement/bug → server RCE. Any DLL that returns SQF other than the expected numeric array literal runs on the server. The DLL is third-party and absent from the repo, so its behaviour can't be audited here.
  • Malformed/empty return → error cascade. If the DB is down/slow/encoding-broken, callExtension returns ""call compile "" is nilnil select 0 throws. Several handlers only guard typeName _responseCode == "SCALAR" after the select 0, so the select can throw first.
  • Echo-to-code latent risk. Only UID/score/side/map round-trip today (all constrained, low risk). But the pattern means any future free-text field persisted and echoed back becomes executable.

Engine caveat (why the pattern exists): Arma 2 OA 1.64 has no parseSimpleArray (that is Arma 3), so call compile is the idiomatic string→array path for extensions. The A2-correct hardening is defensive validation, not a parser swap:

  1. Guard the raw string first with OA-safe checks: if (isNil "_response") exitWith { /* neutral fallback + WARNING */ }; if (_response == "") exitWith { /* neutral fallback + WARNING */ };
  2. Compile, then shape-check before use: _response = call compile _response; if (typeName _response != "ARRAY") exitWith {...}; if ({ typeName _x != "SCALAR" } count _response > 0) exitWith {...};
  3. Only then read _response select 0/1/2. This keeps the DLL contract (numeric arrays) but refuses to execute anything that isn't one.

DR-8 — Blocking DB poll on the join / skill-balance path — Medium (availability/JIP)

callDatabaseRetrieve.sqf polls the 505 procedure up to 120 × 0.10s ≈ 12s; callDatabaseRequestSideTotalSkill.sqf polls 707 up to 9 × 3s = 27s. These feed player-connect stat retrieval and team-skill balancing (called from mainLoop.sqf, getTeamScoreMonitor.sqf, Init_Server.sqf). They run in spawned scripts so the server tick survives, but a slow/down DB stalls join/balance up to those windows before falling back to neutral ([1,1] / 0). Recommend a shorter ceiling + a circuit-breaker that flips WFBE_C_ANTISTACK_ENABLED off after N consecutive timeouts.

DR-9 — callExtension length limits vs full-roster SEND_PLAYERLIST — Medium (scale)

callDatabaseSendPlayerList.sqf packs every player's guid,side pair into a single callExtension input string (g1,1,g2,2,…). Arma 2 OA callExtension has input/output length limits (output historically ~10 KB); a 55-slot roster can produce a long argument and an even longer response, risking truncation → call compile of a truncated array literal → parse error (compounding DR-7). Recommend chunking the player list across multiple calls and validating each response shape.

DR-10 — AntiStack defaults ON against a DLL that isn't in the repo — Medium (ops)

WFBE_C_ANTISTACK_ENABLED defaults to 1 (Init_CommonConstants.sqf:171), and the A2WaspDatabase DLL is an undocumented external runtime dependency absent from the repo. Any server/dev instance lacking the DLL gets callExtension""call compile→error per call unless the param is set to 0. Marty added per-call if (… == 0) exitWith disable guards (good), but the default is on. Recommend: document the external dependency in External integrations, and consider detecting DLL presence (a cheap PING procedure) to auto-disable rather than error.

Handoff

Code owners: apply DR-7 defensive validation (guard empty + shape-check before reading) to all seven callDatabase*.sqf; add a circuit-breaker (DR-8); chunk SEND_PLAYERLIST (DR-9); document/auto-detect the external DLL (DR-10). Codex: the A2WaspDatabase external dependency + call compile trust contract should be called out in the External integrations page (its lane). Ledger: Integrations Auth/PV cells advanced from ⬜ to 🟡 (AntiStack covered; Extension/Discord/BattlEye still pending).

Round 6 — 2026-06-02 (Claude) — victory / endgame (DR-11..DR-13)

Lane victory-endgame-review. Source: Server/FSM/server_victory_threeway.sqf (the only script that sets gameOver/WFBE_GameOver/failMission — verified by grep across Server/), Server/Functions/Server_LogGameEnd.sqf, Server/PVFunctions/LogGameEnd.sqf, Common/Init/Init_CommonConstants.sqf:401.

DR-11 — Endgame reports the winner inconsistently; persisted win-tally is wrong for the all-towns win — Superseded / fixed in master (B67, wiki-wins)

The trigger merges a lose test and a win test into one condition and then handles both identically:

if (!(alive _hq) && _factories == 0 || _towns == _total && !WFBE_GameOver) then {
    [nil,"HandleSpecial",["endgame",(_x) Call WFBE_CO_FNC_GetSideID]] Call WFBE_CO_FNC_SendToClients;
    WF_Logic setVariable ["WF_Winner", _x];
    gameOver = true; WFBE_GameOver = true;
    _side = west; if (_x == west) then {_side = east};
    [_side] call WFBE_CO_FNC_LogGameEnd;   // Server_LogGameEnd: _this select 0 == WINNER
}

SQF precedence (&& before ||) parses this as (!alive _hq && _factories==0) || (_towns==_total && !WFBE_GameOver):

  • Branch A_x HQ dead and no factories → _x is the loser. LogGameEnd(_side = opposite of _x) records the correct winner. ✓
  • Branch B_x holds all towns → _x is the winner. But LogGameEnd is still called with _side = opposite of _x → it records the loser as the winner in the persisted %1_WIN_CHERNARUS profileNamespace tally. ✗

Consequences:

  • The win/loss statistics saved via WFBE_CO_FNC_LogGameEnd (→ profileNamespace, saveProfileNamespace) are inverted for every all-towns victory.
  • WF_Logic setVariable ["WF_Winner", _x]WF_Winner is read by AI_Commander.sqf:593 for round-end telemetry logging. The inverted-tally bug still means the wrong side may be logged in profileNamespace on the all-towns win path.
  • The endgame client broadcast sends _x's sideID to WFBE_CL_FNC_EndGame (HandleSpecial.sqf:16) for both opposite scenarios, so the player-facing outro shows the same side regardless of who actually won — at least one path is wrong. (Follow-up: confirm whether EndGame treats the payload sideID as winner or loser.)
  • Guard/precedence bug: !WFBE_GameOver guards only the towns branch, and the forEach over sides has no break after setting gameOver. Branch A is unguarded, so if two sides both satisfy "HQ dead + no factories" in the same 80s tick, endgame fires twice (double endgame broadcast, double SET_MAP, double LogGameEnd). Fix: guard both branches with !WFBE_GameOver (or exitWith after the first winner) and split the win/lose logic so the winner is computed correctly per branch.

DR-12 — "Threeway" victory mode has no detection — Superseded / fixed in master (B66, 2026-06-21)

WFBE_C_VICTORY_THREEWAY defaults to 0 (Init_CommonConstants.sqf:401, comment "0: Side a vs Side b [supremacy] minus defender"), and the detection block is gated if (!gameOver && _victory == 0). Since server_victory_threeway.sqf is the only victory/failMission setter in Server/, selecting any non-zero WFBE_C_VICTORY_THREEWAY value disables victory detection entirely — matches never auto-end in the mode the file is named for. Either implement the threeway path or document the parameter as non-functional.

DR-13 — Two divergent LogGameEnd implementations, one buggy — Low (cleanup / latent)

  • Server/Functions/Server_LogGameEnd.sqf — clean; wired to WFBE_CO_FNC_LogGameEnd (compiled twice, Init_Server.sqf:64 and :89).
  • Server/PVFunctions/LogGameEnd.sqfremoved from master; was a buggy duplicate. Only Server/Functions/Server_LogGameEnd.sqf remains., and reads profileNamespace getVariable WEST_WIN_CHERNARUS (bare global, not the "WEST_WIN_CHERNARUS" string). If this variant is ever wired in, win-stat persistence silently corrupts. Recommend deleting the duplicate to prevent future mis-wiring.

Handoff

Code owners: fix DR-11 (split win/lose branches, compute winner per branch, guard both branches / break the loop) — this corrects permanently-skewed win stats; decide DR-12 (implement or document-as-disabled threeway); delete the buggy PVFunctions/LogGameEnd.sqf (DR-13). Follow-up review item: WFBE_CL_FNC_EndGame payload semantics (winner vs loser sideID). Ledger: Victory/endgame Map/Auth/PV/Perf cells advanced.

Round 7 — 2026-06-02 (Claude) — factory/purchase authority + commander assignment (DR-14, DR-15)

Lane factory-purchase-authority. Builds on Codex's Factory and purchase systems atlas (which noted player buy is client-local with no RequestBuyUnit PVF) and adversarially verifies Cicero's flagged Server_AssignNewCommander candidate. All claims verified at source.

DR-14 — Player unit purchasing has no server authority (the economy ceiling) — High (gameplay integrity), architectural

The player buy path never contacts the server:

  • Client/GUI/GUI_Menu_BuyUnits.sqf:102,108 check funds client-side; :155-156 do _params Spawn BuildUnit; -(_currentCost) Call ChangePlayerFunds;.
  • Client/Functions/Client_BuildUnit.sqf:217/249/… create the unit/vehicle directly on the buyer via WFBE_CO_FNC_CreateUnit / WFBE_CO_FNC_CreateVehicle (engine createUnit/createVehicle). There is no RequestBuyUnit PVF (confirmed: not in Init_PublicVariables.sqf, no Server/PVFunctions/RequestBuyUnit.sqf).
  • Funds live in wfbe_funds on the team group, written by Common_ChangeTeamFunds with setVariable [..., true] (broadcast, client-writable — see Round 1 / DR-6 root cause).

So a modified client can mint any factory unit for free (skip the deduction, or set wfbe_funds directly) — and the created vehicle is globally synced because client createVehicle in MP is global. This is the ceiling on the DR-1/DR-6 hardening thread: unlike construction (DR-6, which at least routes through a server PVF that could be validated), the player economy and unit production are architecturally client-authoritative in WFBE's locality model. Fully fixing it = a large redesign (route purchases through a validated server PVF like construction). The realistic live-server defense is a BattlEye script filter (scripts.txt) constraining client createVehicle/createUnit, not a publicVariable filter. Document this ceiling so future hardening targets the right layer.

Latent path note (confirms atlas): Server_BuyUnit.sqf / AIBuyUnit is compiled (Init_Server.sqf) but has no proven dynamic caller — the AI-commander production path that would use it is itself dormant (the AI commander FSM never starts; see Cicero's server atlas + DR-15 neighbourhood).

DR-15 — Server_AssignNewCommander call-shape bug (confirmed) — Medium (correctness)

Adversarial verification of Cicero's candidate — confirmed live by tracing compile + sole caller:

  • Init_Server.sqf:62: WFBE_SE_FNC_AssignForCommander = Compile … "Server\Functions\Server_AssignNewCommander.sqf".
  • Sole caller Server/PVFunctions/RequestNewCommander.sqf:13: [_side, _assigned_commander] Spawn WFBE_SE_FNC_AssignForCommander; (a 2-element array).
  • Server/Functions/Server_AssignNewCommander.sqf:3: _side = _this; — sets _side to the whole array [side, commander] (should be _this select 0), then _commander = _this select 1 (correct). _logic = (_side) Call WFBE_CO_FNC_GetSideLogic then receives an array, not a side → wrong/objNull logic → the block that stops the AI-commander FSM (_logic getVariable "wfbe_aicom_running") operates on a bad logic and fails.

Impact: when a human is assigned commander via RequestNewCommander, the AI-commander shutdown path mis-fires (mitigated in practice because the AI-commander FSM is itself dormant — DR-14 note). There's also a redundant new-commander-assigned broadcast (sent by both RequestNewCommander.sqf and Server_AssignNewCommander.sqf). Fix: _side = _this select 0;. One-line change in Chernarus source.

RESOLVED on current master (source-confirmed 2026-06-07, megapass). The primary call-shape bug is fixed: Server/Functions/Server_AssignNewCommander.sqf:4 now reads _side = _this select 0; (with a Marty comment: "the direct commander assignment receives [side, commanderTeam]") and :5 _commander = _this select 1;, so GetSideLogic gets a real side and the AI-com shutdown block operates on the correct logic. The minor secondary item is not fixed: the new-commander-assigned broadcast is still sent by both RequestNewCommander.sqf:14 and Server_AssignNewCommander.sqf:10 (duplicate). DR-15's core defect is closed on master; only the redundant-broadcast cleanup remains (cosmetic). This is the one DR the resolved-issue sweep found genuinely fixed; DR-1..DR-14, DR-16..DR-57 remain open on master.

Handoff

Code owners: (DR-14) decide whether to route player purchases through a validated server PVF (large) or accept client-authority + add a BattlEye scripts.txt filter; (DR-15) one-line fix _side = _this select 0 in Server_AssignNewCommander.sqf and drop the duplicate new-commander-assigned broadcast. Ledger: Factory/purchase Auth/PV advanced; AI-commander caveat cross-linked.

Round 8 — 2026-06-02 (Claude) — UI/HUD authority + dialog IDs (DR-16, DR-17)

Lane ui-hud-authority-review. Cross-checks Codex/Curie's Client UI systems atlas and reviews the economy-menu sale authority (the DR-6/DR-14 sibling). Verified at source.

DR-16 — Structure sale is fully client-authoritative — High (gameplay integrity)

Client/GUI/GUI_Menu_Economy.sqf:104-152 (MenuAction 105, "Sell Building"):

  • The commander check is client-side only (_isCommander via commanderTeam == group player, :107-109).
  • It picks the closest own-side structure (GetSideStructures, :110-112), then in a spawned thread credits the refund client-sideChangeSideSupply (broadcast, client-writable) or ChangePlayerFunds (:141) — and destroys the structure client-side with _closest setDammage 1 (:152), which propagates globally because the structure is a synced object.
  • No server PVF, no server validation (same pattern as DR-6 construction and DR-14 purchasing). A modified client bypasses the commander gate and the WFBE_SOLD re-sell guard, mints the refund, and demolishes structures. Same client-authority ceiling: the realistic defense is a BattlEye scripts.txt filter constraining client setDammage/funds writes, or routing sell through a validated server PVF (matches the DR-6 fix shape). This completes the economy picture: build (DR-6), buy (DR-14), and sell (DR-16) are all client-authoritative.

DR-17 — Duplicate dialog IDD 23000 (EASA vs Economy) — Low-Medium (UI correctness)confirms Curie candidate

Rsc/Dialogs.hpp: class RscMenu_EASA (:3209, idd = 23000 at :3211) and class RscMenu_Economy (:3287, idd = 23000 at :3289) share the same display id. findDisplay 23000 is therefore ambiguous, and any control-event/closeDialog/findDisplay 23000 logic can target the wrong dialog if both are reachable. Verified Curie's flagged candidate at source. Fix: give EASA and Economy distinct IDDs (and audit any findDisplay 23000 callers). Also re-confirmed Curie's note that other UI candidates (stale RscMenu_Upgrade → missing GUI_Menu_Upgrade.sqf; suspect RscClickableText.soundPush[]) remain open for a UI-focused follow-up.

Handoff

Code owners: (DR-16) move sell authority/refund/destruction server-side (mirror the DR-6 server-PVF validation) or add a BattlEye scripts.txt filter; (DR-17) assign distinct IDDs to EASA/Economy dialogs. Ledger: UI/HUD Auth/PV advanced; economy thread (build/buy/sell) now fully characterized.

Round 9 — 2026-06-02 (Claude) — server-loop candidates verified (DR-18, DR-19)

Lane server-loop-candidates-verify. Adversarial verification of two Cicero candidates from the Server gameplay runtime atlas; both confirmed at source with exact impact.

DR-18 — Supply-mission cooldown key casing mismatch → nil-throw on first check — Medium (correctness)confirms Cicero

setVariable/getVariable keys are case-sensitive in Arma 2 OA (unlike SQF identifiers). The seed and the readers disagree by one letter:

  • Common/Init/Init_Town.sqf:35: _town setVariable ["lastSupplyMissionRun", 0];lowercase l.
  • Server/Module/supplyMission/isSupplyMissionActiveInTown.sqf:8: getVariable "LastSupplyMissionRun"capital L. Same capital form is written by supplyMissionStarted.sqf:8 and supplyMissionActive.sqf:6.

So the 0 seed lands in a slot nothing reads, and "LastSupplyMissionRun" is nil until the first mission completes. The cooldown check then runs:

if (((_lastActivationTime + WFBE_CO_VAR_SupplyMissionRegenInterval) > time) && (_lastActivationTime != 0)) then {...}

On a never-run town _lastActivationTime is nil → nil + interval throws ("Type Nothing, expected Number"), aborting the handler before it publishes WFBE_Server_PV_IsSupplyMissionActiveInTown — so the client's cooldown query can get no response on first use. The mis-cased seed defeats exactly the != 0 guard it was meant to satisfy. Fix: make the seed key "LastSupplyMissionRun", or read with a default: getVariable ["LastSupplyMissionRun", 0].

DR-19 — Hosted/listen-server FPS publishers busy-loop — Medium (performance, non-dedicated)confirms Cicero

Both server FPS publishers put sleep 8 inside the isDedicated guard:

// Server/GUI/serverFpsGUI.sqf  AND  Server/Module/serverFPS/monitorServerFPS.sqf
while {true} do { if (isDedicated) then { …; publicVariable …; sleep 8; } };

On a dedicated server this is fine. On a hosted/listen server or singleplayer host (isServer true, isDedicated false — and both scripts are launched server-side from Init_Server), the if is false every iteration, so while {true} spins with no sleep → a tight CPU busy-loop per script (two of them), degrading the host. Fix: either if (!isDedicated) exitWith {} at the top (don't publish FPS when hosted), or move sleep 8 outside the if so the loop always yields. (Two scripts publishing the same round diag_fps under different PV names — SERVER_FPS_GUI / WFBE_VAR_SERVER_FPS — is also redundant; consolidating would remove one loop entirely.)

Handoff

Code owners: (DR-18) align the supply-cooldown key casing (or default the read) — one-line fix; (DR-19) hoist the FPS-loop sleep out of the isDedicated guard (or early-exit when not dedicated), and consider consolidating the two redundant FPS publishers. Ledger: Supply JIP/HC and server-runtime perf cells advanced.

Round 10 — 2026-06-02 (Claude) — JIP/headless cross-cut: non-idempotent HQ-killed (DR-20)

Lane jip-headless-crosscut. Traced HQ-death detection across server, existing clients and JIP clients.

DR-20 — HQ-killed is processed once per owning-side client (no idempotency) — High (multiplayer correctness / score exploit)

Death detection for the mobile HQ is deliberately redundant (a client-local vehicle's Killed EH must run on a client):

  • Server/Construction/Construction_HQSite.sqf:89 adds a server-side Killed EH, then :91 broadcasts ["set-hq-killed-eh", _mhq] to the whole owning side; Server_MHQRepair.sqf:37,43 do the same after a repair.
  • Client/PVFunctions/HandleSpecial.sqf:34 (set-hq-killed-eh) makes every owning-side client add a Killed EH that calls ["RequestSpecial",["process-killed-hq",_this]].
  • JIP clients additionally add it themselves at Client/Init/Init_Client.sqf:500-503 (guarded !isServer && !_isDeployed).

So N owning-side clients each hold the same Killed EH. When the HQ dies, the synced death fires every client's EH → the server receives N process-killed-hq messages → Server/Functions/Server_OnHQKilled.sqf runs N times, and it has no idempotency guard. Each run:

  • awards the killer score twice (_points = 30000/100*coef and _score = 900 via two RequestChangeScore), so total killer score ≈ 2N × the intended award;
  • broadcasts N× destruction / HeadHunterReceiveBounty messages;
  • re-publishes IS_<side>_HQ_ALIVE / marker infos N times.

(The dead-MHQ wreck spawn is inside if (GetSideHQDeployStatus) and self-limits after the first run flips wfbe_hq_deployed=false; the score/message duplication does not.) On a populated server (e.g. 20 owning-side players) an HQ kill inflates the killer's score ~40×. Fix: make Server_OnHQKilled.sqf idempotent — first line if (_structure getVariable ["wfbe_hq_killed_done", false]) exitWith {}; _structure setVariable ["wfbe_hq_killed_done", true];. Keep the redundant EH registration (it ensures death is never missed regardless of MHQ locality); guard the consumer, not the producers. Pattern: "detect redundantly, act once."

JIP coverage notes (verified, no change needed)

  • The JIP guard at Init_Client.sqf:500 (!_isDeployed) is correct: a JIP client adds the mobile-HQ EH only when the HQ is currently mobile; a deployed HQ (building) is covered by the server-side EH at Construction_HQSite.sqf:36 / Init_Server.sqf:319. JIP HQ-death detection is therefore covered — the defect is downstream duplication (DR-20), not a JIP miss.
  • set-hq-killed-eh is side-filtered (SendToClients [_side, …]), so only owning-side clients register it — correct.

Handoff

Code owners: add the one-line idempotency guard to Server_OnHQKilled.sqf (DR-20) — this also fixes the duplicate-score symptom on HQ kills in populated games. Ledger: JIP/HC cells advanced for victory/economy/construction (HQ-death path verified end-to-end).

Round 11 — 2026-06-02 (Claude) — headless disconnect (DR-21) + a self-correction

Lane headless-disconnect-review. Verifies the round-1 hypothesis about HC disconnect at Server/Functions/Server_OnPlayerDisconnected.sqf.

Correction to a round-1 hypothesis (honesty note)

Round 1 listed, as an unverified gotcha, "HC disconnect orphans units it created." Verified at source, that framing is wrong and is hereby downgraded: in Arma 2 OA, when any machine disconnects the engine migrates its local objects/groups to the server (ownership transfer, not deletion). HC-delegated AI is therefore not orphaned or lost on disconnect. The accurate effects are below (DR-21).

DR-21 — HC disconnect dumps delegated AI on the server with no re-delegation — Medium (performance/operational, non-data-loss)

Server_OnPlayerDisconnected.sqf HC handling:

  • If WFBE_C_AI_DELEGATION == 2 and WFBE_HEADLESS_<uid> exists, it removes the HC's group from WFBE_HEADLESSCLIENTS_ID and clears WFBE_HEADLESS_<uid>.
  • WFBE_JIP_USER<uid> is nil for an HC (HCs don't register as players via RequestJoin), so the handler exitWiths before the player-team/unit logic. (It does also delete any WFBE_CLIENT_<uid>_OBJECTS registered to that uid earlier in the handler.)

What actually happens to the AI the HC was simulating: the engine transfers those units/groups to the server, so the server's load spikes by exactly the amount the HC was offloading — the opposite of the delegation benefit, precisely when you least want it. There is no re-delegation: the disconnect handler does not hand the migrated AI to a surviving HC, and (per the round-1 init finding) WFBE_C_AI_DELEGATION is only evaluated/downgraded at boot, so a later HC reconnect does not resume offloading either. Net: HC delegation has no failover/rebalancing — a single HC drop silently re-loads the server for the rest of the match. Suggested handling: on HC disconnect, re-register migrated groups for cleanup/accounting, redirect future spawns to the server or a surviving HC, and make delegation re-evaluate when an HC (re)connects rather than only at boot. Arma 2 OA has no setGroupOwner / groupOwner live-transfer command, so full live re-delegation of already-created groups is not available; keep patch design inside this mission's remote-create-on-HC model. See AI, headless and performance and Headless delegation/failover.

Handoff

Code owners: treat HC delegation as best-effort with no failover today; if HC stability matters, add cleanup/accounting re-registration and future-spawn rebalancing on HC disconnect/connect. Ledger: AI/Headless JIP/HC cell advanced; round-1 "orphan" hypothesis corrected.

Round 12 — 2026-06-02 (Claude) — side-supply overspend windfall (DR-22)

Lane side-supply-delta-verify. Confirms + sharpens Faraday's "negative side-supply delta" candidate (and my round-1 "inverted guard" note) at source.

DR-22 — Overspending side supply grants a windfall instead of being floored — High (economy correctness/exploit)

The supply clamp (live in Server/Functions/Server_ChangeSideSupply.sqf, both the wfbe_supply_temp_west and …_east handlers; also present but dead in Common/Functions/Common_ChangeSideSupply.sqf) is:

_change = _currentSupply + _amount;
if (_change < 0) then {_change = _currentSupply - _amount};   // intended floor-at-0; actually a windfall
if (_change >= _maxSupplyLimit) then {_change = _maxSupplyLimit};

_amount is signed — deductions are negative. When a deduction would overdraw (_change < 0), the "floor" computes _currentSupply - _amount = _currentSupply + |amount|. Example: supply 100, spend 300 (_amount = -300) → _change = -200 → guard → 100 - (-300) = 400. Trying to spend more supply than you have increases your supply by the amount you tried to spend. Any over-budget supply deduction (e.g. an upgrade/structure costing more than the side holds) flips into a gain — directly exploitable by attempting over-large spends, and it corrupts the economy generally.

Fix: floor correctly — if (_change < 0) then {_change = 0};. Apply in Server_ChangeSideSupply.sqf (both handlers). Note the matching block in Common_ChangeSideSupply.sqf is dead code: it computes _change but the function sends only [_side, _amount, _reason] over wfbe_supply_temp_<side> and the server recomputes — so fix the server copy (and optionally delete the dead client computation). Related (round-1, still open): there is no resistance-side handler for wfbe_supply_temp_*, only west/east.

Handoff

Code owners: one-line floor fix in Server_ChangeSideSupply.sqf (×2 handlers) — closes the overspend windfall. Ledger: Economy/supply Auth/PV reinforced (confirmed exploit, not just "confusing").

Round 13 — 2026-06-02 (Claude) — upgrade authority (DR-23) + economy synthesis

Lane upgrade-authority-verify. Confirms Faraday's "upgrade authority gap" candidate and closes the economy-authority thread.

DR-23 — Upgrade purchasing is client-authoritative with no server validation — High (economy integrity)

Server/PVFunctions/RequestUpgrade.sqf is the whole handler: _this Spawn WFBE_SE_FNC_ProcessUpgrade; — the raw client payload [side, upgradeId, level, isPlayer] goes straight into Server/Functions/Server_ProcessUpgrade.sqf, which:

  • reads _side/_upgrade_id/_upgrade_level/_upgrade_isplayer from the client with no checks (no commander check, no side check, no upgrade-sequence/level check, no dependency/_LINKS check);
  • never deducts a cost — it only sleeps _upgrade_time then _upgrades set [_upgrade_id, current+1]. The upgrade cost is deducted client-side in the upgrade menu before the request, same as the rest of the economy.

So a modified client can forge ["RequestUpgrade",[side, id, level, false]] to grant any side a free upgrade, bypassing commander authority and cost. Secondary: _upgrade_time = (… select _upgrade_id) select _upgrade_level uses client-controlled indices → out-of-range error (minor DoS) if forged with bad ids. Fix: validate in RequestUpgrade/ProcessUpgrade — requester is the side's commander, indices in range, dependencies met and the level is the correct next step, and deduct cost server-side (mirror the DR-6 validation shape).

Economy-authority synthesis (DR-6, DR-14, DR-16, DR-22, DR-23)

This is the last economic action to review, and it confirms the pattern: the entire WFBE player economy is client-authoritative

  • build structures (DR-6), buy units (DR-14), sell structures (DR-16), change side supply (DR-22, plus the overspend-windfall bug), buy upgrades (DR-23) — each lets the client decide/deduct, with the server doing at most a class-exists check.

One owner decision covers all of it: either route economic mutations through validated server PVFs (server checks commander/side/funds and applies the debit), or accept client authority and rely on BattlEye scripts.txt/PV filters. Piecemeal fixes won't close the class; the decision is architectural.

Handoff

Code owners: add commander/funds/index/dependency validation + server-side cost to the upgrade path (DR-23); and make the economy-authority decision once for build/buy/sell/supply/upgrade rather than per-finding. Ledger: economy thread fully reviewed (Auth across the board characterized).

Round 14 — 2026-06-02 (Claude) — dead dialog reference (DR-24)

Lane missing-reference-inventory. Confirms Curie's RscMenu_Upgrade candidate at source (a representative dead/abandoned reference).

DR-24 — RscMenu_Upgrade dialog points at a missing onLoad script — Low (dead code / naming drift)

Rsc/Dialogs.hpp:2425 class RscMenu_Upgrade has onLoad = "_this ExecVM ""Client\GUI\GUI_Menu_Upgrade.sqf""" (:2428), but Client/GUI/GUI_Menu_Upgrade.sqf does not exist — only the differently-named Client/GUI/GUI_UpgradeMenu.sqf does. RscMenu_Upgrade is never opened (createDialog/cutRsc for it appears nowhere outside Dialogs.hpp); the live upgrade UI is GUI_UpgradeMenu.sqf (reached via GUI_Menu.sqf). So this is a stale dialog whose onLoad would ExecVM a missing file if it were ever opened — currently inert because nothing opens it. Fix: delete RscMenu_Upgrade (and its dangling onLoad), or repoint it at GUI_UpgradeMenu.sqf if it was meant to be the live one. Naming-drift class (GUI_Menu_Upgrade vs GUI_UpgradeMenu).

Method note: an automated "live reference → missing file" scan was attempted but its Windows-backslash path normalization was unreliable (false positives); this finding was confirmed by hand. A robust missing-reference inventory is a good future tooling task (resolve \-separated execVM/ExecFSM/preprocessFile string targets against the tree, excluding commented lines) — handed to Codex/tooling.

Handoff

Code owners: remove or repoint the dead RscMenu_Upgrade dialog (DR-24). Tooling (Codex/Meitner lane): build a reliable missing-reference scanner. Ledger: UI dead-reference candidate confirmed; abandoned-code inventory still has open candidates (TaskSystem, old blink loops, WASP OnArmor/KeyDown — see round-1 WASP-Overlay + Feature-Status).

Round 15 — 2026-06-02 (Claude) — remaining UI config defects (DR-25a/b)

Lane ui-followups-verify. Confirms Curie's last two UI candidates at source; closes the UI follow-up items.

DR-25a — Duplicate title IDD 10200 (RscOverlay vs OptionsAvailable) — Low (UI correctness)

Rsc/Titles.hpp: class RscOverlay (:46) and class OptionsAvailable (:165) both declare idd = 10200. Titles are shown via cutRsc/titleRsc (addressed by class name, so the collision is less damaging than the dialog dup in DR-17), but any code that does findDisplay 10200 / uiNamespace lookups on that id is ambiguous. Assign distinct IDDs. (Sibling of DR-17's idd=23000 dialog dup.)

DR-25b — Malformed soundPush[] in RscClickableTextLow (config defect)

Rsc/Ressources.hpp:556 class RscClickableText has soundPush[] = {, 0.2, 1}; — the first array element (the sound file) is empty/missing (a leading comma). The correct empty-sound form is {"", 0.2, 1} (as used at Ressources.hpp:92); the line-556 form is a malformed config array. RscClickableText is a base control class used widely, so the defect propagates to inheritors. Fix: soundPush[] = {"", 0.2, 1}; (or a real sound macro like the adjacent soundEscape[] = {WFBE_SoundEscape,0.2,1}).

Handoff

Code owners: assign distinct IDDs to RscOverlay/OptionsAvailable (DR-25a); fix the malformed RscClickableText.soundPush[] (DR-25b). Both Low. Ledger: UI follow-up candidates (title 10200, soundPush) now confirmed — UI cell's documented candidates are closed.

Round 16 — 2026-06-02 (Claude) — external deep-research integration (DR-26) + corroboration

Lane external-research-integration. Steff supplied three deep-research PDFs (also given to Codex). I read two in full (Diepgaande analyse, Analyse van); the third is the same genre. Provenance check: their citations are raw.githubusercontent.com/wiki/rayswaynl/... pages + Miksuu upstream blobs — i.e. they were generated from this wiki (plus upstream as a line-level proxy), so they are downstream corroboration, not independent source verification.

Corroboration (external validation of our findings)

The reports independently re-derive, and rate as top risks, exactly our spine: the Call Compile PVF trust boundary (DR-1) with the BattlEye kickAFK-only filter, construction client-authority (DR-6), callExtension/external-trust (DR-7), the UpdateSupplyTruck config-gated latent breakage (our Feature-Status sharpening), the town-AI despawn player-vehicle risk (our AI/headless note), the PR#1 Killed-EH leak, and MASH-marker-broken (DR-3). Their recommended fix order (static allow-list dispatch → server-side validation → reduce broadcast/centralize PV → harden callExtension) matches our DR-1/DR-6 playbooks. Our source-verified findings are a superset — the reports do not contain DR-11/15/18/19/20/22/23 (victory winner-inversion, commander-assign bug, FPS busy-loop, HQ-killed N-fold, overspend windfall, upgrade-authority), which required reading the actual .sqf rather than the wiki. Net: external review confirms the map holds up and surfaces nothing higher-severity that we missed in code.

DR-26 — License is custom/proprietary, not OSI (resolves both reports' "license unspecified") — Low (governance)

Both reports marked the license "unspecified" (they only had the wiki, not the repo root). Verified at source: LICENSE.md is a custom proprietary-style license — "Copyright (C) 2016 Spayker / (C) 2025 Miksuu", with contributions becoming the repository owner's property and reuse/distribution restricted to explicitly granted rights. Not MIT/GPL/OSI. Implication: third-party reuse or redistribution is not permitted by default; treat the repo as source-available, not open-source.

Governance/ops handoffs (the reports' additive value, source-confirmed)

  • Discord sample metadata: DiscordBot/preferences_sample.json ships a concrete GuildID (440257265941872660), AuthorizedUserIDs, and DataSourcePath C:\a2waspwarfare\Data; FileConfiguration.cs has the same hardcoded fallback path. No committed token (good), but neutralize the sample identifiers / move to env-based config. (Codex/owner lane — DiscordBot is outside the Chernarus mission.)
  • No CI/tests: confirmed earlier (only .github/FUNDING.yml). For a heavily preprocessFile-dynamic SQF codebase + generated targets, add at least SQF-syntax + generated-mission-drift + .NET build checks. (Tooling/Codex lane.)

Handoff

Owner: the campaign's code findings (DR-1→DR-25) are the actionable core; the external reports add governance items (license clarity now resolved as DR-26; Discord sample hygiene; CI). Codex: fold the governance asks into External-Integrations/Tools-And-Build-Workflow as desired (its lane).

Round 17 — 2026-06-02 (Claude) — weather / day-night: reviewed clean (no defect)

Lane weather-daynight-review. Reviewed Server/Functions/Server_DayNightCycle.sqf (Marty's hybrid accelerated cycle) + the client receiver/animation in initJIPCompatible.sqf:174-210 + Client/Functions/Client_DayNightCycle.sqf + the constants. No defect found — the system is well-designed. Recording the clean result so future passes don't re-review.

Verified:

  • No divide-by-zero in the cycle math. _twilight_hours_per_second = _day_weighted_hours / (_day_duration_real_seconds * _twilight_weight)WFBE_DAYNIGHT_TWILIGHT_WEIGHT is a non-zero hardcoded constant (= 3, Init_CommonConstants.sqf:88, not a param), and WFBE_DAY_DURATION's parameter values are {1,30,40,50,60,90,180} (min 1, never 0), so both divisors are always positive.
  • Authority model is coherent: the server runs an authoritative accelerated clock via small per-tick skipTime and publishes an absolute date (WFBE_DAYNIGHT_DATE) every WFBE_DAYNIGHT_SERVER_SYNC_INTERVAL (30 s) for drift correction; each non-dedicated machine animates locally (Client_DayNightCycle.sqf) — consistent with skipTime/setDate being local-effect in Arma 2 OA.
  • JIP is covered: WFBE_DAYNIGHT_DATE is engine-synced to joiners, and the init [] Spawn { waitUntil time>0; if (!isNil "WFBE_DAYNIGHT_DATE") setDate WFBE_DAYNIGHT_DATE … } applies the current absolute date on join; live drift resumes on the next 30 s broadcast. Minor, non-defect: a JIP client's WFBE_DAYNIGHT_DATE PVEH does not fire for the pre-join value (only the variable is synced), so the first drift-correction waits up to one sync interval — acceptable since the init setDate already seeds correct state.
  • The volumetric-clouds force-disable (perf) is documented in AI, headless and performance / Feature status register.

Outcome: weather/day-night cell → reviewed-clean. No handoff required.

Round 18 — 2026-06-02 (Claude) — modules: forgeable map-wide ICBM nuke (DR-27)

Lane modules-review. Reviewed the Client/Module/ set (AFKkick, AutoFlip, CM, CoIn, EASA, Engines, MASH, Nuke, Skill, UAV, Valhalla, ZetaCargo, supplyMission) and Server/Module/. Most are config-gated cosmetic/QoL features (WFBE_C_MODULE_* flags; UAV's _button == 007 branch is comment 'DISABLED' in both uav_interface.sqf:226 and uav_interface_oa.sqf:100 — confirms the Feature-Status "UAV partial" note). The Nuke/ICBM module is the high-stakes one and carries the most severe authority defect found in the campaign.

DR-27 — ICBM nuke is fully client-authoritative; one forged publicVariable = server-applied map-wide kill — Critical (network authority / forgery)

End-to-end chain (all path:line in the Chernarus source mission):

  1. Trigger is client-side and client-gated only. Client/GUI/GUI_Menu_Tactical.sqf MenuAction == 8 (the "ICBM Strike" branch, ~:463-505) deducts the fee locally (-_currentFee Call ChangePlayerFunds — itself client-authoritative, the DR-16/DR-23 economy class), spawns the strike-marker object locally ("HeliHEmpty" createVehicle _callPos), and Spawn NukeIncoming. The only ICBM gate is menu visibility (WFBE_C_MODULE_WFBE_ICBM > 0 && !IS_air_war_event, GUI_Menu_Tactical.sqf:253) — module-enable, not the per-side purchased WFBE_upgrade_…_ICBM, and not a commander check.
  2. Client asks the server to detonate. Client/Module/Nuke/nukeincoming.sqf:23: ["RequestSpecial", ["ICBM",sideJoined,_target,_cruise,clientTeam]] Call WFBE_CO_FNC_SendToServer;
  3. Server dispatches with no validation. RequestSpecial is a registered inbound PVF (Common/Init/Init_PublicVariables.sqf:18); its handler Server/PVFunctions/RequestSpecial.sqf is literally _this Spawn HandleSpecial;Server/Functions/Server_HandleSpecial.sqf "ICBM" case (:97-112):
    • _base = _args select 2 (client-chosen strike-position object), _target = _args select 3 (client-chosen object).
    • if (isNull _target || !alive _target) exitWith {}; waitUntil {!alive _target}; [_base] Spawn NukeDammage;
    • NukeDammage is server-side (which is why the kill propagates to everyone) and is applied centered on the client-supplied _base position with no check that _side/clientTeam owns the ICBM upgrade, that the sender is the commander, or that funds existed.

Why the one server-side guard is not a security check. waitUntil {!alive _target} only requires the forger to supply some live object and then end its life — spawn any vehicle, pass it as _target, delete/kill it; or pass any alive object and kill it. It gates timing, not authority.

Impact. Any connected client can hand-craft the publicVariable RequestSpecial = ["ICBM", <anySide>, <objAtChosenPos>, <liveObjThenKilled>, <anyTeam>] and the server applies a map-wide nuke at coordinates of the attacker's choosing — repeatable, no upgrade, no commander role, no real cost. This is the apex of the client-authoritative class (DR-6 build, DR-14 buy, DR-16 sell, DR-22 supply, DR-23 upgrade): same root cause (server PVF handlers trust payload fields without re-deriving authority server-side), but the blast radius is the entire match rather than one player's wallet.

Owner decision (same lever as the economy class, higher priority). Two non-exclusive fixes:

  • Server-side authority in the "ICBM" case: re-derive the requester from the PV sender, verify _remoteSender is the commander of _side, verify the side's WFBE_upgrade_…_ICBM level > 0 and a server-tracked cooldown/funds ledger, before Spawn NukeDammage. (The same _remoteSender-vs-payload pattern recommended in DR-1/DR-6.)
  • BattlEye scripts.txt/publicvariable.txt: restrict/snapshot the RequestSpecial PV so the "ICBM" selector can't be hand-injected. Defense-in-depth, not a substitute for server validation.

Handoff for Codex: this belongs in the Networking PVF-hazard table and a Feature-Status/atlas note on the Nuke module; the actionable fix is an owner decision shared with the economy-authority item already logged (DR-6/14/16/22/23).

Outcome: modules cell → Auth/PV flipped to the DR-27 finding; rest of Client/Module/ reviewed as config-gated cosmetic/QoL with the UAV-007 branch confirmed disabled.

Round 19 — 2026-06-02 (Claude) — gear / EASA / vehicle-service economy (DR-28) — class now complete

Lane gear-easa-review. Reviewed the aircraft/vehicle loadout system (Client/Module/EASA/ + Client/GUI/GUI_Menu_EASA.sqf) and the vehicle service point (Client/GUI/GUI_Menu_Service.sqf). Result: gear/rearm is the last untracked tier of the client-authoritative economy class, plus a minor logic inconsistency.

DR-28 — Gear/EASA loadouts and vehicle rearm/repair/refuel/heal are client-authoritative; rearm & refuel skip even the client-side affordability guard — High (economy authority), class-completing

Source-verified:

  • No server PVF for gear at all. EASA_Equip.sqf applies the chosen loadout directly to the local vehicle (addWeapon/addMagazine, or addWeaponTurret/addMagazineTurret for the AW159_Lynx_BAF) and broadcasts only the setup index (_vehicle setVariable ["WFBE_EASA_Setup", _index, true], :36). There is no SendToServer/RequestSpecial anywhere in the EASA or Service flow (grep-confirmed) — the spend and the effect are entirely client-local.
  • EASA cost is a client-side honor check. GUI_Menu_EASA.sqf:46-50: if (_funds > (_row select 0)) then { … Call EASA_Equip; -(_row select 0) Call ChangePlayerFunds; … }. The price lives in the loadout row ([[Price],[Desc],[Wpn,Ammo]…], EASA_Init.sqf:8), the affordability test runs on the client, and the debit is the client-authoritative ChangePlayerFunds (the DR-16/DR-23 primitive). A modified client equips any loadout without paying.
  • Service rearm/refuel deduct with NO affordability guard. GUI_Menu_Service.sqf: rearm (MenuAction==1, :196-200) and refuel (:217-219) do -_price Call ChangePlayerFunds; unconditionally, then Spawn SupportRearm/the refuel thread — whereas repair (:206-211, if (_repairPrice > 0)) and heal (:228-230) are guarded. So even a legit client can rearm/refuel into negative/clamped funds, and (as with all of the above) the effect threads run client-side with no server check.

Why it matters. This completes the economy-authority picture. Every WFBE spend path is now source-confirmed client-authoritative: build (DR-6) · buy (DR-14) · sell (DR-16) · supply transfer (DR-22) · upgrades (DR-23) · ICBM superweapon (DR-27) · gear/EASA + vehicle rearm/repair/refuel/heal (DR-28). There is no server-side ledger; ChangePlayerFunds and the can-afford tests are all on the honor system. The rearm/refuel missing-guard is a real but secondary inconsistency — moot against the root issue, since a cheat client bypasses the debit regardless.

Owner decision (same single lever). The one architectural decision already logged for the economy class covers DR-28 too: either (a) move spend authority server-side — a server-validated funds ledger that PVF handlers debit before applying effects — or (b) accept client-authoritative economy and lean on BattlEye scripts.txt to blunt the most trivial money/var edits. No new lever; gear simply joins the list. If (a) is ever scoped, also add the trivial if (_funds >= price) guards to Service rearm/refuel for parity with EASA/repair/heal.

Handoff for Codex: fold DR-28 into the Economy page's "all spend is client-authoritative" note and the gear/loadout atlas; it's the same owner decision, no separate workstream.

Outcome: new ledger row Gear / EASA / vehicle service → Map ✅, Auth ✅ (characterized as client-authoritative, DR-28), PV/JIP-HC 🟡, Drift ⬜; Economy row note extended to name gear as a class member.

Round 20 — 2026-06-02 (Claude) — in-repo GLOBALGAMESTATS extension (DR-29)

Lane extension-globalgamestats-review. Reviewed the in-repo .NET callExtension DLL (Extension/src/**) end-to-end plus its sole SQF caller (Server/CallExtensions/GlobalGameStats.sqf). This is the second extension trust boundary (distinct from the AntiStack A2WaspDatabase DLL reviewed in DR-7..DR-10, which is not in the repo). Net: this one is currently the safe direction, but carries a dormant RCE landmine and an async void reliability bug, and is a write-only/abandoned-refactor stub.

DR-29 — GLOBALGAMESTATS extension: safe today (output discarded), but a dormant deserialization-RCE landmine + async void write race + write-only stub — Medium (latent Critical)

What it is: a one-way telemetry exporter. GlobalGameStats.sqf loops every 60 s (while {true} … sleep 60, execVM'd once from Init_Server.sqf:298) and calls "a2waspwarfare_Extension" callExtension format ["%1,…,%6","GLOBALGAMESTATS",scoreWest,scoreEast,worldName,uptime,playerCount]. The DLL (ExtensionMethods.RvExtension, the legacy synchronous _RVExtension@12 ABI — correct for A2 OA 1.64; A3's RVExtensionArgs does not exist here) enum-validates the selector, reflection-instantiates GLOBALGAMESTATS (EnumExtensions.GetInstanceType.GetType("GLOBALGAMESTATS")), stores the args (GameData.Instance.exportedArgs = _args) and serializes GameData to C:\a2waspwarfare\Data\database.json.

Findings (source-cited):

  1. No RCE-into-SQF vector — the safe contrast to DR-7. RvExtension's _output StringBuilder is never written (grep-confirmed; only the parameter declaration exists at ExtensionMethods.cs:12), and the SQF caller invokes callExtension as a bare statement with no assignment (GlobalGameStats.sqf:22). So the DLL returns an empty string and SQF never call compiles anything from it — the exact opposite of the AntiStack DB path (DR-7), where _response is captured and compiled. Reflection is also constrained: Enum.TryParse (ExtensionMethods.cs:29) gates the selector to the GLOBALGAMESTATS enum before Type.GetType, so SQF cannot instantiate arbitrary CLR types.

  2. Dormant deserialization-RCE landmine (Low now → Critical if load is ever enabled). The commented-out load path (SerializationManager.cs:104-130) uses settings.TypeNameHandling = TypeNameHandling.Auto; with JsonConvert.DeserializeObject<Database>(_json, settings) — the textbook Newtonsoft $type gadget sink. It is inactive today, but the feature cannot actually persist across restarts without a load path (see #3), so a future dev is likely to re-enable it. If database.json is ever writable by an untrusted process (or replaced), re-enabling load = remote/local code execution on the server host. The active serializer is correctly hardened (TypeNameHandling.None, SerializationManager.cs:33) — the risk is strictly the commented load path. Recommend: delete the dead load code, or if reinstated use TypeNameHandling.None + a fixed expected type.

  3. Write-only / abandoned-refactor stub. The active code only ever serializes; the entire deserialize/load path is commented out and references a different type graph (Database, Leagues.StoredLeagues) than the live GameData singleton — evidence of a half-finished refactor. Consequence: there is no cross-restart persistence today (the DLL never reads the file back), and GameData's only field is [DataMember] public string[] exportedArgs = new string[2] (GameData.cs:29) — a stale initializer, since the caller now sends 5 data args (the // Todo: [3] Uptime [4] Player count comment in GLOBALGAMESTATS.cs:9-11 is also stale — both are already wired). Any wiki text implying GLOBALGAMESTATS provides durable stat persistence would be too confident; it currently produces a single overwritten JSON snapshot.

  4. async void + unawaited file-create race before File.Replace (Medium reliability). SerializationManager.SerializeDB is async void; it calls the also async void FileManager.CheckIfFileAndPathExistsAndCreateItIfNecessary(dbPath, dbFileName) without awaiting (:52), then immediately File.Replace(dbTempPathWithFileName, dbPathWithFileName, null) (:55). File.Replace requires the destination to exist — on first run (no database.json yet) the fire-and-forget create may not have completed, so File.Replace throws FileNotFoundException. Per BaseExtensionClass (:18-22) that is rethrown as InvalidOperationException out of the synchronous _RVExtension@12 call, which can destabilize the calling SQF/game thread; and unobserved async void exceptions surface as unhandled exceptions on the .NET threadpool, which can crash the host process. Recommend: make the I/O methods async Task and await them, or do the create synchronously before File.Replace.

  5. Minor telemetry data-quality bug (SQF side). GlobalGameStats.sqf:20 computes _playerCount = abs(_playerCount - 1) ("Exclude headless client"), which assumes exactly one HC is always connected. With 0 HCs it under-reports by 1 (1 real player → reports 0; empty server → abs(0-1)=1 reports a phantom player); with 2+ HCs it over-subtracts. Two WFBE_CO_FNC_LogContent INFORMATION lines per minute (:11 "Running with old vars …", :23) also add steady RPT noise. Cosmetic/telemetry-only.

Owner decisions / handoff for Codex. Document GLOBALGAMESTATS in External integrations as a one-way, output-discarded telemetry exporter (explicitly NOT an RCE-into-SQF path, unlike the AntiStack DB), with three concrete code asks for the owner: (a) delete or harden (TypeNameHandling.None) the dead deserialize path before anyone re-enables load; (b) fix the async void create/File.Replace race; (c) optional — correct the abs(playerCount-1) HC heuristic and the stale new string[2]/Todo comments. The Extension DLL is a code artifact, not a wiki page — these are upstream code-owner items, logged here for traceability.

Outcome: Integrations row — Extension sub-target reviewed (DR-29). AntiStack DB (DR-7..DR-10) + Extension (DR-29) now both done; Discord data path + BattlEye scripts.txt/publicvariable.txt posture remain ⬜ within the bundle.

Round 21 — 2026-06-02 (Claude) — BattlEye posture (DR-30) — the "rely on BattlEye" half of every economy/forgery owner-decision is not shipped

Lane battleye-posture-review. Source-verified the repo's entire BattlEye footprint to close a loop the campaign left open across eight prior findings (DR-1 RCE dispatch + DR-6/14/16/22/23/27/28 client-authoritative economy), each of which offered remediation option (b) "accept client authority and rely on BattlEye filters." Confirms — and sharpens — the high-level posture the Codex Gibbs scout reported (Progress-Dashboard.md:23,72), and corroborates the accurate, non-overclaiming wiki text already in place (External-Integrations.md:60, Feature-Status-Register.md:32, Networking-And-Public-Variables.md:122).

DR-30 — As shipped, the BattlEye mitigation is a 22-byte AFK-kick stub: no security PV filter, no scripts.txt at all — option (b) does not exist in the repo — High (live-server hardening gap, campaign-wide)

Source facts (full repo sweep):

  • The only BattlEye filter file in the repository is BattlEyeFilter/publicvariable.txt22 bytes, whose entire content is:
    //new
    5 "kickAFK"
    
    This is not a security control. The single rule is the AFK-kick feature plumbing itself: Client/.../updateclient.sqf intentionally broadcasts kickAFK and BattlEye acts on it because serverCommand kick paths are unavailable (correctly documented at External-Integrations.md:58 and Networking-And-Public-Variables.md:66). There is no default-deny catch-all line (e.g. 5 "" !="legitPV1" !="legitPV2" …) and therefore no restriction on any forgery-class PVRequestSpecial (the DR-27 ICBM vector), RequestStructure/RequestDefense (DR-6), RequestUpgrade (DR-23), RequestNewCommander (DR-15), or the raw Server_HandlePVF/Client_HandlePVF channels (DR-1). Every dangerous PV passes BattlEye unfiltered.
  • scripts.txt is absent (verified by name across the whole repo), as are the relevant Arma 2 OA command filters such as createvehicle.txt, setvariable.txt, setpos.txt, setdamage.txt, deletevehicle.txt, mpeventhandler.txt, cargo filters, teamswitch.txt, waypointcondition.txt, selectplayer.txt and attachto.txt. scripts.txt is the filter that would blunt the DR-1 call compile RCE and script-command injection (createVehicle/setDamage/call compile), so its absence is the more security-relevant gap of the two. Do not count remoteexec.txt here: it belongs to Arma 3 remoteExec / remoteExecCall, not Arma 2 OA.
  • The directory also contains a 716 KB READ ME FIRST - Using BattlEye filter to auto kick.docx. Per the project's untrusted-content rule it was not parsed (binary Office doc); regardless, the operative deployed artifact is the 22-byte stub, and admin documentation is not a control.

Campaign-wide implication (the point of this pass). The two-option framing in DR-1/6/14/16/22/23/27/28 is misleading as-shipped: option (b) "rely on BattlEye" is not a deployed reality — choosing it means authoring and maintaining a full BE filter set from scratch (a restrictive publicvariable.txt default-deny + whitelist of the legitimate WFBE_PVF_*/direct channels keeping kickAFK, plus a scripts.txt), which is a non-trivial, error-prone, separate workstream for a Warfare mission with hundreds of PVs and easy to break legitimate play. The realistic remediation for the entire forgery/economy class therefore collapses toward (a) server-side authority in SQF (re-derive the requester/role/funds in each PVF handler before applying effects, per DR-1/DR-6), with a real BE filter set as defense-in-depth only if someone owns it.

Honest caveat (do not overstate). BattlEye filter files are normally deployed in the server's BE working directory (the BEpath), outside the mission PBO — so their absence from this repo does not prove the production server lacks them. But the repository, as the campaign's source of truth, ships only the stub; whether ocd-clan.com/Miksuu's live server maintains a fuller filter set is an explicit owner question, not a safe assumption. The wiki should keep stating (as it already does) that PVF spoofing "must not be considered protected by BattlEye."

Owner decision / handoff for Codex. No wiki rewrite needed — the existing BattlEye text is accurate and in-lane for Codex. This finding's value is the cross-link: add a one-line note to the DR-1 remediation playbook and the External integrations BattlEye section that "option (b) requires building the filter set; it is not present in-repo (only the kickAFK stub)," and pose the production-BE-config question to the server owner. Bundle the scripts.txt/server.cfg/basic.cfg absences (also flagged by the Gibbs scout) into the same hosting-hardening owner item.

Outcome: Integrations row — BattlEye sub-target reviewed (DR-30). AntiStack DB (DR-7..DR-10), Extension (DR-29) and BattlEye (DR-30) now done; only the Discord data path remains ⬜ within the bundle. Every prior economy/forgery finding's option (b) is now annotated as "not shipped."

Round 22 — 2026-06-02 (Claude) — Discord data path (DR-31) — the DR-29 deserialization landmine is LIVE in the bot, with TypeNameHandling.All

Lane discord-datapath-review. Reviewed the in-repo DiscordBot/ (.NET / Discord.Net) end-to-end — the consumer side of the GLOBALGAMESTATS extension (DR-29), closing the last Integrations sub-target. The data path is: Arma server → GLOBALGAMESTATS extension writes C:\a2waspwarfare\Data\database.json (DR-29) → DiscordBot reads it on a 60 s timer → posts a game-status embed. Net: secret hygiene is good, the inbound command surface is properly auth-gated, but the deserialization sink I flagged as dormant in the extension (DR-29 #2) is active here, and worse.

DR-31 — DiscordBot deserializes database.json with TypeNameHandling.All on a 60 s timer — live insecure-deserialization gadget sink in the token-holding process — High (insecure deserialization; local-write-gated RCE)

Source-verified:

  • The active load path uses TypeNameHandling.All. GameData.LoadFromFile() (DiscordBot/src/ExtensionData/GameData/GameData.cs:49-56) builds new JsonSerializerSettings { … TypeNameHandling = TypeNameHandling.All … } and JsonConvert.DeserializeObject<GameData>(json, …) on the contents of database.json. TypeNameHandling.All honors $type directives for the root and every nested object/array — the canonical Newtonsoft gadget sink (e.g. ObjectDataProvider → arbitrary Process.Start).
  • It runs automatically every 60 s, no interaction. GameStatusUpdater (src/GameStatusUpdater.cs:9,19-22,84) arms a System.Timers.Timer at UPDATE_INTERVAL_SECONDS = 60 with AutoReset = true and calls LoadFromFile() each tick. Two more live callers: ProgramRuntime.cs:15 (startup) and CommandHandler.cs:211 (CreateGameStatusEmbed). So the sink is exercised continuously regardless of any auth.
  • The capability is gratuitous. GameData's only state is [DataMember] private string[] exportedArgs (GameData.cs:30) — a flat string-array DTO with no polymorphism. The writer (the extension, DR-29) serializes with TypeNameHandling.None and emits no $type. The reader therefore needs .None; requesting .All adds nothing but the gadget sink. (A second, dead copy GameDataDeSerialization.HandleGameDataCreationOrLoading uses TypeNameHandling.Auto — no callers, grep-confirmed; should be deleted too.)
  • Trigger & blast radius. Not remotely exploitable as-configured: database.json is normally written only by the trusted local extension. But any write-primitive to C:\a2waspwarfare\Data\database.json — a misconfigured ACL/share on DataSourcePath, a malicious mod or compromised Arma process writing there, or a future feature that ingests untrusted data into that file — yields arbitrary code execution in the DiscordBot process, which holds the Discord bot token (→ token theft + full bot/guild control). Classic local insecure-deserialization escalation.

Owner decision / fix (trivial). Change GameData.LoadFromFile() to TypeNameHandling.None (the data is a flat DTO; no behavior is lost) and delete the dead .Auto method. This also retro-closes DR-29 #2: keep the extension's deserialize path .None if it is ever reinstated. Defense-in-depth: lock down the ACL on C:\a2waspwarfare\Data so only the Arma service can write it.

Secondary observations (Low / informational):

  • Secret hygiene is good — resolves the external reports' "Discord sample hygiene" item. DiscordBot/.gitignore excludes token.txt and preferences.json; preferences_sample.json contains no token. Minor: the sample commits a real-looking GuildID (440257265941872660) and one AuthorizedUserIDs snowflake — these are Discord IDs, not credentials (knowing an admin's user ID grants nothing without being that user), but a sample is cleaner with placeholder zeros.
  • Inbound command surface is correctly gated. Slash-command handlers check Preferences.Instance.IsUserAuthorized(userId) (CommandHandler.cs:49,127) before privileged actions — no missing-authorization finding there.
  • Three-way exportedArgs shape drift (coupling smell). The array is new string[2] in the extension (DR-29), new string[4] in the bot (GameData.cs:30), while the SQF sender emits 5 data fields and the bot reads index [4]. Held together only by wholesale = _args replacement on deserialize + bounds-guards (Length > 4, added after the commented unguarded GetGameMapAndPlayerCount at GameData.cs:138-145). Benign today, but the three sides of the contract disagree on the shape — document the canonical 5-field layout ([0]bluforScore [1]opforScore [2]worldName [3]uptime [4]playerCount) in one place.

Handoff for Codex. Document the Discord data path in External integrations: one-way pull (extension writes JSON → bot reads on 60 s timer → status embed), secret hygiene OK, command surface auth-gated; flag the TypeNameHandling.All fix as the one actionable code-owner item and cross-link DR-29/DR-31. These are code artifacts, logged here for traceability.

Outcome: Integrations row — Discord sub-target reviewed (DR-31); all four sub-targets (AntiStack DB, Extension, BattlEye, Discord) now done. Map cell can move to ✅. The DR-29 deserialization concern is now closed end-to-end (dormant in writer, live in reader, one-token fix).

Round 23 — 2026-06-02 (Claude) — generated-mission drift (DR-32): vanilla faithful, modded forks divergent, 4 modded stubs abandoned

Lane generated-mission-drift-review. Cross-cutting Drift pass: file-set + byte-level comparison of the Chernarus source mission against every generated mission (1 vanilla + 7 modded), to establish whether the DR-1..DR-31 findings (all verified against Chernarus) propagate, and whether LoadoutManager generation introduces divergence. This is the single highest-leverage Drift result — it characterizes the Drift dimension for all subsystems at once.

DR-32 — Generated missions fall into three fidelity tiers; modded missions are divergent forks or abandoned stubs, so source fixes do not propagate to them — Medium (maintainability / drift) + abandoned-code inventory

Method: relative-path file-set comm + per-file cmp of all source .sqf against each generated mission. Results (differing/common .sqf):

Generated mission differ/common .sqf Tier
Missions_Vanilla/…takistan 15 / 671 Faithful
Modded_Missions/…Napf 123 / 466 Divergent fork
Modded_Missions/…eden 119 / 465 Divergent fork
Modded_Missions/…lingor 104 / 417 Divergent fork
Modded_Missions/…smd_sahrani_a2 4 / 4 (4 files total) Abandoned stub
Modded_Missions/…dingor 3 / 3 (20 files total) Abandoned stub
Modded_Missions/…tavi 2 / 2 (3 files total) Abandoned stub
Modded_Missions/…isladuala 1 / 1 (1 file total) Abandoned stub
  1. Vanilla Takistan is a faithful regeneration. Only 15 .sqf differ, and all are map-config, not logic: the per-faction Core_Artillery/Artillery_*.sqf, Config_GUE.sqf, GUI_Menu_Help.sqf, WASP/unsort/StartVeh.sqf, and Server/Init/Init_Server.sqf whose sole diff is one line["SET_MAP", 1]["SET_MAP", 2] (the AntiStack DB map identifier). Plus textures (US/CDF skins → desert skins) and 3 extra native Artillery_{TKA,TKGUE,US}.sqf. All other 656 logic files are byte-identical.Every DR-1..DR-31 finding propagates verbatim to vanilla Takistan; a fix to the Chernarus source + regen corrects both. The Drift dimension for the source→vanilla path is clean.

  2. Napf / eden / lingor are heavily divergent full forks. 104–123 of ~465 logic files differ from source — including security-critical files I reviewed: Server_HandlePVF.sqf (DR-1), Server_HandleSpecial.sqf (DR-27), server_victory_threeway.sqf (DR-11), Server_ProcessUpgrade.sqf (DR-23), Server_OnHQKilled.sqf (DR-20), Server_OnPlayerDisconnected.sqf (DR-21), Init_PublicVariables.sqf, initJIPCompatible.sqf. The divergence is hand-customized behavior, not just config: e.g. Napf's Server_HandleSpecial.sqf "ICBM" case additionally spawns three BO_GBU12_LGB laser-guided bombs around the target (absent in source). This is consistent with DR-4 (modded propagation is commented out at Tools/LoadoutManager/.../SqfFileGenerator.cs:132) — the modded missions are not regenerated from source; they are independent forks. Consequence: a fix to the Chernarus source does not reach Napf/eden/lingor; the DR vulnerability classes almost certainly persist there (same architecture) but at different lines/with different effects, so each fork needs its own review and manual fix propagation.

  3. smd_sahrani_a2 / dingor / tavi / isladuala are abandoned stubs. They have only a tiny fraction of the real mission tree; most are missing Server/, mission.sqm, the WASP/ overlay, description.ext, and essentially all logic. They cannot load as functional Warfare missions. These are incomplete scaffolds committed to the repo — an abandoned-code/inventory item.

Wave S 2026-06-02 refinement: Napf/eden/lingor should be treated as partial forks, not drop-in runnable missions from the checkout. eden lacks tracked version.sqf; Napf lacks tracked mission.sqm and version.sqf; lingor lacks tracked mission.sqm, description.ext, initJIPCompatible.sqf and version.sqf. The stub tier remains non-runnable; dingor has a description.ext that includes missing version.sqf.

Owner decisions / handoff. Three explicit choices for the code owner, all logged for Codex to fold into Tools and build workflow / a generated-mission status table (Codex's lane):

  • Stub missions (sahrani/dingor/tavi/isladuala): complete via regeneration or remove them — they are dead weight and misleading as "supported maps."
  • Divergent forks (Napf/eden/lingor): pick a maintenance model — (a) re-enable modded propagation (DR-4) and regenerate from the hardened source, accepting loss of the hand-customizations (e.g. Napf's GBU ICBM), or (b) formally treat them as independent forks and apply every DR-1..DR-31 fix to each by hand. Today they silently drift.
  • All security fixes: apply to the Chernarus source first (propagates to vanilla Takistan on regen), then deliberately propagate to the 3 forks.

Outcome: Drift dimension characterized across the whole codebase. Source→vanilla path is faithful (DR findings transfer verbatim); modded missions are out-of-scope forks/stubs flagged here. Ledger Drift cells updated to reference DR-32 (faithful-to-vanilla ✅; modded divergence is an owner decision, not a review gap).

Round 24 — 2026-06-02 (Claude) — factory/production Perf + JIP/HC (DR-33)

Lane factory-perf-jip-review. Filled the two ⬜ cells on the Factory/purchase row by source-reviewing the unit-production path: Client/GUI/GUI_Menu_BuyUnits.sqf (queue gate) → _params Spawn BuildUnitClient/Functions/Client_BuildUnit.sqf (the production loop), plus the WFBE_C_QUEUE_* counters seeded in Client/Init/Init_Client.sqf. Production runs entirely on the buyer's client (group player, local CreateUnit/CreateVehicle). Two real defects (one JIP/HC, one Perf) plus a network-churn note.

DR-33a — Empty-vehicle purchase leaks the buyer's WFBE_C_QUEUE counter → silent per-factory soft-lock — Medium (JIP/HC / client-state leak)

WFBE_C_QUEUE_<type> is a client-local counter (seeded in Init_Client.sqf:185+, e.g. BARRACKS_MAX=10, LIGHT_MAX/HEAVY_MAX=5). The buy gate increments it before producing and blocks at the cap:

  • GUI_Menu_BuyUnits.sqf:145-146: if (WFBE_C_QUEUE_<type> < WFBE_C_QUEUE_<type>_MAX) then { …+1; _params Spawn BuildUnit } else :158 "queue max" hint.
  • Client_BuildUnit.sqf:469 decrements it at the normal tail of the script.

But the vehicle branch has an early if (!_driver && !_gunner && !_commander) exitWith {} (Client_BuildUnit.sqf:365) — for a crewless vehicle purchase (all crew unchecked, a legitimate option) — which returns before the tail decrement at :469. So each empty-vehicle buy permanently increments the buyer's local queue counter without ever decrementing it. After _MAX such purchases (5 for Light/Heavy) the GUI gate at :145 silently refuses all further production from that factory type for the rest of the match — a slow soft-lock that presents as a mysterious "can't buy / queue full" with nothing actually queued. Reachable in normal play. Fix: move the WFBE_C_QUEUE (and unitQueu, :467) decrements before/around the empty-vehicle exitWith, or restructure so all exit paths decrement (e.g. a single cleanup block).

DR-33b — Per-unit sleep 4 queue poll re-broadcasts the building's queue on every mutation; non-unique queue token — Low/Medium (Perf / network churn + latent correctness)

  • Network churn. Each queued unit gets its own Spawn BuildUnit, which busy-waits while {_unique != _queu select 0 …} { sleep 4; … } (:180-199) and writes _building setVariable ["queu", _queu, true] — a global broadcast — on every enqueue (:172), timeout-advance (:191) and completion (:207). With several factories producing across a full server, every 4 s tick that advances or cleans a queue broadcasts that building's whole queu array to all machines. Bounded but avoidable; consider a server-owned queue or a non-broadcast local timer.
  • Non-unique token. The per-item identity is varQueu = random(10)+random(100)+random(1000) (:168) — a ~0–1110 value space, not unique. Two concurrently-queued items can collide on _unique, breaking the _unique != _queu select 0 front-of-queue test (an item may wait forever or two may think they're first). Low probability per pair but non-zero on a busy factory. Fix: use a monotonic counter or diag_tickTime-seeded id.
  • Orphan token on disconnect (minor). Because the loop + the front-token removal (:206) run on the buyer's client, a buyer disconnecting mid-production leaves their _unique token in the building's broadcast queu; it self-heals only if another buyer is queued behind to run the _ret > _longest timeout-cleanup (:187-192). The local WFBE_C_QUEUE counter is not leaked across clients (it dies with the buyer). Stale shared data, low impact.

Handoff for Codex. Document the production queue model in the Factory/purchase atlas: client-owned per-unit producer, broadcast queu token list, per-client WFBE_C_QUEUE caps. The two fixes (DR-33a decrement-on-all-paths; DR-33b unique token + reduce broadcast) are concrete code-owner items, not architectural decisions. Note DR-33a propagates to vanilla Takistan verbatim (DR-32) and likely exists in the 3 forks too.

Outcome: Factory/purchase row — Perf and JIP/HC cells filled (DR-33). The row's remaining 🟡 (Auth/PV) is the DR-14 client-authoritative-purchase architectural ceiling (economy class, owner decision).

Round 25 — 2026-06-02 (Claude) — respawn / MASH markers (DR-34): MASH map-marker feature is dead on both ends

Lane respawn-mash-review. Reviewed the respawn UI (Client/Functions/Client_UI_Respawn_Selector.sqf) and the MASH respawn-marker chain (Server/Module/MASH/MASHMarker.sqfClient/Module/MASH/receiverMASHmarker.sqf), with wiring confirmed in Init_Client.sqf / Init_Server.sqf. Extends the earlier DR-2 note ("MASH markers are dead receive-side") to a full both-ends diagnosis.

DR-34 — MASH map-marker feature is fully dead (send trigger never broadcast + client receiver commented out); the live server PVEH is orphaned — Low/Medium (broken/abandoned feature; UX)

MASH tents are a real deployable officer feature (Client/Module/Skill/Actions/Officer_Undeploy_MASH.sqf exists), but the map marker that should show a team its MASH locations does nothing, because all three links are broken or orphaned:

  1. Client receiver is commented out. Init_Client.sqf:132: //WFBE_CL_FNC_ReceiverMASHmarker = Call Compile preprocessFileLineNumbers "Client\Module\MASH\receiverMASHmarker.sqf"; — so no client ever registers the WFBE_SE_MASH_MARKER_SENT event handler; the receiver in receiverMASHmarker.sqf is never installed.
  2. The trigger PV is never broadcast. WFBE_CL_MASH_MARKER_CREATED appears in the repo only as the server's addPublicVariableEventHandler registration (MASHMarker.sqf:1). No client deploy path ever does WFBE_CL_MASH_MARKER_CREATED = […]; publicVariable …, so the server handler can never fire.
  3. The server handler is live but orphaned. Init_Server.sqf:70 actively compiles WFBE_SE_FNC_MASH_MARKER (= MASHMarker.sqf), registering a PVEH for a PV (WFBE_CL_MASH_MARKER_CREATED) that nothing emits — harmless dead weight that looks active in a grep but does nothing in composition. (Line 92 is a duplicate, commented.)

Net: deployed MASH tents produce no map markers for the owning side. Confirms and extends DR-2.

Latent JIP gap if revived (note for whoever fixes it). Even with both ends re-enabled, the marker is delivered by publicVariable "WFBE_SE_MASH_MARKER_SENT" — a single global overwritten on each deploy (not a list) and not replayed to join-in-progress clients. So a revived feature would: (a) show JIP joiners no markers for MASH deployed before they joined, and (b) only ever carry the most-recent MASH in the synced value. A correct revival needs a server-held list + a JIP re-send on join (the same pattern the construction/HQ-killed code uses via Server_HandleSpecial "set-…" re-sends).

Secondary (Low):

  • Respawn selector is a ~33 Hz local loop. Client_UI_Respawn_Selector.sqf:19-33 runs while {!isNil 'WFBE_MarkerTracking'} do { sleep 0.03; … }, animating a pulsing local marker (setMarkerDirLocal/SizeLocal/PosLocal) — network-free and bounded to while the respawn UI is open, but sleep 0.03 cannot be honored by the SQF scheduler so it effectively runs every frame. Acceptable for a transient UI; flagged for completeness.
  • Non-unique marker name (dead code, DR-33b class). receiverMASHmarker.sqf:12 builds the marker name with round random 50000 (collision-prone) and later deletes a createMarkerLocal marker with the global deleteMarker (local/global mismatch). Moot while the receiver is disabled; fix if revived.

Handoff for Codex. Mark the MASH map-marker feature as dead/abandoned in the Feature status register and the relevant marker/respawn docs (Codex's lane), with the revival recipe above (server-held list + JIP re-send + unique names + fix publicVariable JIP gap). Owner decision: revive the feature or remove the dead receiverMASHmarker.sqf + orphaned Init_Server.sqf:70 registration.

Outcome: Markers/respawn — MASH marker chain reviewed (DR-34): dead both ends + orphaned server PVEH; respawn selector Perf characterized. Markers row PV/JIP-HC cells reference DR-34.

Round 26 — 2026-06-02 (Claude) — parameters / localization integrity (DR-35): clean, with 2 dead-action confirmations

Lane params-localization-review. Reviewed the two never-covered cross-cutting areas: localization integrity (do localize/$STR_ references resolve?) and the mission parameters system. Result: localization is clean once case-folding and dead-code are accounted for; the params system is live and correctly wired.

DR-35 — Localization integrity is clean (no live broken strings); parameters system is live and correctly wired; 2 dead WASP actions confirmed — Informational (reviewed clean + abandoned-code)

Method matters (the trap that produces false findings). Arma 2 OA stringtable lookup is case-insensitive, but text-diff tools are not. A naïve case-sensitive set-difference of the 204 static localize "STR_…" keys against the 1289 stringtable.xml keys reports 4 "missing"; after lowercasing both sides it drops to 3, and after checking each reference site for liveness it drops to 0 live bugs:

  • STR_WF_UPGRADE_uav_Descfalse positive (casing): defined as STR_WF_UPGRADE_UAV_DESC; resolves at runtime.
  • STR_EP1_UAV_action_exit (Client/Module/UAV/uav_interface_oa.sqf:25, live) — engine-provided: the STR_EP1_* namespace is supplied by the Arma 2 OA base game's global stringtable, not the mission; resolves at runtime.
  • STR_WASP_actions_OnArmor and STR_WF_Gear — referenced only in commented-out lines (WASP/actions/AddActions.sqf:4,10-12, the dead "ride-on-armor" and "gear your unit" WASP actions). Dead code; the missing keys are moot.

Config-side $STR_ references in .hpp/.ext (excluding engine STR_EP1_/STR_DN_/STR_USRACT prefixes) all resolve. So no live missing-localization display bug exists. The stringtable carries ~1085 keys not hit by any static localize — a large legacy surface typical of a long-lived WFBE fork, not a defect (some are reached by config $STR_, engine, or removed features).

Parameters system — live and correct. Common/Init/Init_Parameters.sqf iterates missionConfigFile >> "Params" and sets each configName as a missionNamespace variable, taking paramsArray select _i in multiplayer and the param's default in single-player. Wiring confirmed: initJIPCompatible.sqf:121 runs it in MP; the parameter-display dialog is loaded via Rsc/Dialogs.hpp:3136 (onLoad ExecVM GUI_Display_Parameters.sqf), defined in Rsc/Parameters.hpp. This is the canonical A2 OA pattern. One fragility note (not a defect): the paramsArray select _iParams iteration is index-aligned, so inserting/removing a param without keeping class Params order in sync would silently shift every later parameter's value — worth a comment in the config for future editors.

Abandoned-code inventory (adds to DR-32/DR-34). WASP/actions/AddActions.sqf contains commented-out OnArmor (ride-on-tank: GetOnArmor.sqf/GetOnArmorBots.sqf/GetOutBots.sqf) and GearYourUnit actions — dead WASP features whose localization keys were never added. Confirms the earlier "WASP OnArmor/KeyDown abandoned" suspicion.

Handoff for Codex. Optionally note in the WASP overlay page that AddActions.sqf carries dead OnArmor/Gear actions, and add a one-line "keep class Params order stable (index-aligned to paramsArray)" caution to any parameters documentation (Codex's lane). No code defect to fix; the dead WASP actions are an owner cleanup decision (remove vs revive).

Outcome: parameters/localization reviewed — clean; localization integrity verified (no live broken keys), params system confirmed live/wired, 2 dead WASP actions logged. New ledger row Parameters / localization → reviewed-clean (DR-35).

Round 27 — 2026-06-02 (Claude) — victory/endgame Perf + JIP/HC (DR-36); source mechanism for DR-11/DR-13

Lane victory-perf-jip-review. Filled the Victory/endgame Perf + JIP/HC cells by reviewing the loop in Server/FSM/server_victory_threeway.sqf (the sole victory FSM, execVM'd unconditionally at Server/Init/Init_Server.sqf:528) and the end-of-match DB-flush tail, and traced the win-condition expression to a source-level root cause for the previously-observed DR-11/DR-13.

DR-36 — Victory loop Perf clean + JIP/HC server-authoritative; the win-condition guard/precedence is the source of DR-11/DR-13 double-fire — Low (Perf/JIP clean) + Medium (the confirmed correctness bug)

Dual-purpose finding — disambiguate when citing: DR-36 records two things: (1) a clean Perf/JIP review of the victory loop (no defect on those dimensions), and (2) the root-cause mechanism for the separate correctness bugs DR-11 (winner inversion, High) and DR-13 (duplicate game-end). Cite "DR-36" for the mechanism/fix; cite DR-11/DR-13 for the severity/impact.

Perf — clean. The detection loop runs every _loopTimer = 80 seconds (:6,46) with cheap per-side work (GetSideHQ/GetSideStructures/GetTownsHeld + 4× GetFactories, :14-21). No hot loop, no per-frame churn. Minor: _innerTimer is incremented (:47) but never read (dead variable); _miniSleep = 0.05 paces only the one-time end-of-match per-player DB STORE (:60-82). No perf trap.

JIP/HC — server-authoritative, one narrow gap. Detection runs server-only on server-authoritative state; headless clients don't participate (correct). Endgame is pushed to clients via [nil,"HandleSpecial",["endgame", sideID]] Call WFBE_CO_FNC_SendToClients (:24); gameOver/WFBE_GameOver are set server-side (:32-33) and WFBE_GameOver is not broadcast. The only gap: a player joining in the brief endgame window (between the broadcast and failMission "END1" at :88) won't receive the outro, because SendToClients is not replayed to JIP joiners — moot in practice since the mission is tearing down.

Confirmed source mechanism for DR-11 (winner inversion) + DR-13 (duplicate LogGameEnd). The win check (:23):

if (!(alive _hq) && _factories == 0 || _towns == _total && !WFBE_GameOver) then {

By SQF precedence (&& binds tighter than ||) this is ((!alive _hq) && _factories==0) || (_towns==_total && !WFBE_GameOver) — so the !WFBE_GameOver guard covers only the "holds-all-towns" clause, not the "HQ-destroyed elimination" clause. Combined with the enclosing forEach WFBE_PRESENTSIDES - [WFBE_DEFENDER] (:43) having no break/exit after a winner is declared, if two sides are eliminated within the same 80 s tick the elimination clause fires again for the second side: a second ["endgame",…] broadcast, a second WFBE_CO_FNC_LogGameEnd (:41), and WF_Logic setVariable ["WF_Winner", _x] (:31) overwritten with the opposite side (the _side = west; if (_x==west) _side=east swap at :35-39 then logs the inverted winner). That is the exact mechanism behind DR-11's inverted persisted winner and DR-13's duplicate game-end. Fix (one place): parenthesize and guard both clauses with !WFBE_GameOver, and exitWith/break the forEach (and the while) once gameOver is set, so only the first-detected winner is recorded.

Also re-confirms DR-12: the detection block is gated by if (_victory == 0) where _victory = WFBE_C_VICTORY_THREEWAY (default 0). When threeway is enabled (_victory != 0), the entire detection block is skipped and the loop just sleeps — i.e. threeway mode has no victory detection.

Handoff for Codex. This is a code-owner fix already tracked under DR-11/DR-13; this round adds the precise path:line mechanism + the two-part one-line fix. No new wiki page needed — cross-link from the victory rows of Feature status register to DR-36 for the root cause.

Outcome: Victory/endgame row — Perf and JIP/HC cells filled (DR-36): Perf clean, JIP server-authoritative (narrow endgame-join gap noted); DR-11/DR-13 now have a source-level mechanism + fix.

Round 28 — 2026-06-02 (Claude) — boot/lifecycle Perf + JIP/HC (DR-37): reviewed clean, one robustness note

Lane boot-lifecycle-perf-jip-review. Filled the Boot/lifecycle Perf + JIP/HC cells by reviewing the role router (initJIPCompatible.sqf) and the client boot chain (Client/Init/Init_Client.sqf), with the wait-chain cross-referenced against Lifecycle wait-chain. Result: boot is well-architected for JIP and Perf-clean; one robustness gap worth a defensive fix.

DR-37 — Boot Perf clean + JIP state-sync comprehensive; the post-join waitUntil chain has no timeouts (a never-set synced var hangs the JIP client) — Low (reviewed clean + robustness note)

Perf — clean. All boot blocking-waits are bare waitUntil {cond}, which the A2 OA scheduler evaluates once per frame and yields between (not a CPU busy-spin like a sleepless while), and every condition is cheap (!isNil, !isNull player, time>0, !isNil {logic getVariable …}). One wait uses the throttle idiom waitUntil {sleep 0.5; visibleMap} (Init_Client.sqf:248) — deliberately evaluates every 0.5 s instead of per-frame, a good pattern. The while {true} { sleep 0.1; … exitWith … } loops at Init_Client.sqf:419/444 are not perpetual 10 Hz loops — they are bounded join-handshake polls (see below) that exit on ACK. No boot perf trap. (The genuinely long-running client loops — RHUD/marker updaters at :522/:864 — belong to the UI/Markers rows already covered, each with its own internal sleep.)

JIP/HC — comprehensive and correct. initJIPCompatible.sqf routes roles cleanly: server (isHostedServer || isDedicated), client part II (isHostedServer || (!isHeadLessClient && !isDedicated)), headless (isHeadLessClient). A JIP client:

  • syncs time/date via the engine-synced WFBE_DAYNIGHT_DATE (or skipTime (time/3600) catch-up on the disabled path) — :189-205, reviewed clean in Round 17;
  • syncs teams by waiting on the synced WFBE_PRESENTSIDES then per-side wfbe_teams (:225-234);
  • pulls all remaining client state from broadcast logic-object variables via a serial waitUntil {!isNil {WFBE_Client_Logic getVariable "wfbe_…"}} chain (Init_Client.sqf:367-502: structures, commander, radio_hq(+id), startpos, hq, hq_deployed, votetime).
  • Robust join handshake: the RequestJoin→ACK poll (:416-429) polls at 10 Hz, re-sends after a 30 s timeout, and fails the client back to the lobby on team-stack/swap — a well-defended one-time handshake.

The one robustness gap. Unlike the join handshake, the post-join state-sync waitUntil chain has no timeouts. Each step blocks on a synced wfbe_* logic variable; in normal operation all are reliably setVariable [...,true] server-side so the chain completes, but if a server-side regression ever fails to set one (e.g. wfbe_radio_hq_id, :397), the JIP client hangs forever at that step with no fallback or log past it — presenting as a "stuck on black screen at join" with no diagnostic. Not a live bug (the variables are set today), but a fragility: consider a waitUntil {!isNil … || (_t = _t + …; _t > N)} timeout with a logged warning, mirroring the handshake's own retry discipline.

Handoff for Codex. No code defect to fix and no wiki rewrite needed; optionally note in Lifecycle wait-chain that the post-join wfbe_* waits are timeout-less (a single missed server broadcast = permanent JIP hang) as a known robustness characteristic. Owner decision: add defensive timeouts or accept the current fail-silent behavior.

Outcome: Boot/lifecycle row — Perf and JIP/HC cells reviewed clean (DR-37); role routing + JIP state-sync confirmed correct; timeout-less post-join wait-chain logged as a robustness note.

Round 29 — 2026-06-02 (Claude) — PV/networking dispatch Perf + JIP/HC (DR-38): the per-message recompile = the DR-1 fix

Lane pv-dispatch-perf-jip-review. Filled the PV-dispatch Perf + JIP/HC cells by reviewing the dispatch hot path (Server/Functions/Server_HandlePVF.sqf, Client/Functions/Client_HandlePVF.sqf) and the registration/precompile in Common/Init/Init_PublicVariables.sqf. (Auth/PV/RCE already covered by DR-1.)

DR-38 — PVF dispatch recompiles the command string per inbound message; the lookup that fixes DR-1's RCE also removes the recompile. JIP/HC clean — Low/Medium (Perf, converges with DR-1) + JIP reviewed clean

Current status 2026-06-21: this recompile finding is source-present/fixed on current stable origin/master@0139a346 for source Chernarus and maintained Vanilla Takistan. Both roots now use missionNamespace getVariable _script plus typeName == "CODE" guards in the server and client dispatchers. Miksuu b8389e74 and origin/perf/quick-wins@0076040f still use Spawn (Call Compile _script) in both maintained roots, and current stable still needs a registered-handler allowlist/rejection log before dispatcher hardening is fully closed.

Perf. Both dispatchers end with _parameters Spawn (Call Compile _script) (Server_HandlePVF.sqf:14, Client_HandlePVF.sqf:22), so every inbound PVF message runtime-compiles the sender-provided command string. This is per-action (build/buy/construct/upgrade/join/server-pushes), not per-frame, so bounded — but it is avoidable and redundant: Init_PublicVariables.sqf already pre-compiles every PVFunction once at init into SRVFNC<name>/CLTFNC<name> globals (:44 CLTFNC%1 = compile preprocessFileLineNumbers …, :49 SRVFNC%1 = …), and binds the per-command WFBE_PVF_<name> channels to the dispatchers (:45,50). The dispatcher discards that precompiled work and recompiles the string each message. Resolving the handler by lookup — call (missionNamespace getVariable _validatedName) against the known SRVFNC*/CLTFNC* set — eliminates the per-message compile entirely. This is the same change as the DR-1 remediation: validating/looking up a name instead of compile-ing a sender-chosen string closes the RCE and the recompile in one edit. (Secondary: Spawn per message creates a scheduled script per message — justified because handlers use sleep/waitUntil, but it adds scheduler pressure under PVF floods; lower priority.)

JIP/HC — clean. The dispatchers are registered via addPublicVariableEventHandler on the WFBE_PVF_<name> channels in Init_PublicVariables.sqf (:45 client if (!isServer || local player), :50 server if (isServer)), which runs inside Init_Common on every machine including JIP clients during their own init — so a joiner installs its PVF handlers on join and processes all subsequent messages. PVFs are transient events, not state, so nothing needs replaying to joiners (the only replay burden is the separate broadcast-variable state-sync layer, DR-37). The client destination routing (Client_HandlePVF.sqf:12-15: nil = broadcast, SIDE = sideJoined match, STRING = getPlayerUID player match) resolves correctly for joiners. No JIP gap in the dispatch layer; no headless-specific dispatch issue beyond the AI-delegation locality already covered in DR-21.

Handoff for Codex. No new wiki page; fold the Perf observation into the Networking DR-1 remediation note as "the validated-lookup fix also removes a per-message compile," so the owner sees the security fix is free on Perf. JIP-clean result can be stated in the same section.

Outcome: PV/networking dispatch row — Perf and JIP/HC cells filled (DR-38): Perf = avoidable per-message recompile (fixed by the DR-1 lookup change); JIP/HC reviewed clean. The row's remaining work is the DR-1 owner decision (validate-before-execute), which now also carries a Perf benefit.

Round 30 — 2026-06-02 (Claude) — supply missions Perf + JIP/HC (DR-39): dead twin loop + pull-based JIP done right

Lane supply-missions-perf-jip-review. Filled the Supply-missions Perf + JIP/HC cells by reviewing the server module (Server/Module/supplyMission/*) and its client consumers (Client/Module/supplyMission/*). (Auth covered by DR-18 cooldown-casing + the PR#1 helicopter review.)

DR-39 — Dead duplicate supplyMissionActive.sqf; origin/master live loop scans all objects every 3 s; JIP cooldown status is correctly pull-based — Low/Medium (abandoned-code + Perf; JIP reviewed clean)

Abandoned-code: a dead twin. Two near-identical supply-mission tracking loops exist. The live one is supplyMissionStarted.sqf — it self-registers "WFBE_Client_PV_SupplyMissionStarted" addPublicVariableEventHandler { … } (:1), so compiling it (Init_Server :68WFBE_SE_FNC_SupplyMissionStarted) installs the handler. The dead one is supplyMissionActive.sqf — a plain function body (no PVEH; takes _this select 0/1/2, runs while {alive _truck && !_completed} {sleep 2}), compiled to WFBE_SE_FNC_SupplyMissionActive (Init_Server :81) but never called anywhere (grep-confirmed: no caller, no self-registration). It is the superseded older twin of supplyMissionStarted's spawn body (same LastSupplyMissionRun set, same SupplyMissionTimerForTown spawn, same truck-alive poll). Owner cleanup: delete supplyMissionActive.sqf + its Init_Server:81 compile, or wire it if a second path was intended. Adds to the abandoned-code inventory (DR-32/DR-34/DR-35 class).

Perf (live path). Each active supply mission spawns one server-side while {alive _associatedSupplyTruck} { sleep 3; ... } loop (supplyMissionStarted.sqf:20-69). On current source/stable/upstream/perf, the per-tick cost is still nearestObjects [(getPos _truck), [], 80] at supplyMissionStarted.sqf:24-28 — an all-object-types scan in an 80 m radius every 3 s, then a Base_WarfareBUAVterminal post-filter. Release 7ff18c49 carries a PR #1-compatible typed scan at :52,58 in both maintained release roots, including heli 400 m / truck 80 m radius selection and the heli 2D distance gate. The 8 m nearby-player/object scan at current branch :44 intentionally remains broad because it runs only once at delivery and is looking for player/vehicle occupants, not command-center structures.

JIP/HC — handled well (the positive counterexample to DR-34). Supply-mission cooldown status uses an on-demand request/response, not a fire-and-forget push: a client broadcasts WFBE_Client_PV_IsSupplyMissionActiveInTown; the server PVEH (isSupplyMissionActiveInTown.sqf) computes the cooldown from _sourceTown getVariable "LastSupplyMissionRun" vs WFBE_CO_VAR_SupplyMissionRegenInterval and answers via WFBE_Server_PV_IsSupplyMissionActiveInTown; the client (townSupplyStatus.sqf) stores it per-town. So a JIP joiner gets correct state simply by asking — no replay logic needed, unlike the MASH marker (DR-34) which pushed once and missed joiners. The per-mission tracking loop is server-side and keyed on the truck object, so it correctly survives the starting player's disconnect (truck ownership migrates to the server, DR-21). Minor: the cooldown answer is broadcast to all clients (publicVariable, :18) rather than targeted to the requester — every client re-stores the town's cooldown on each query (small redundant network; could target the asker). The DR-18 LastSupplyMissionRun casing concern lives in this subsystem and is already filed.

Handoff for Codex. Note the dead supplyMissionActive.sqf/WFBE_SE_FNC_SupplyMissionActive in the Supply mission architecture page (Codex's lane) as an abandoned duplicate; record the pull-based cooldown query as the JIP-correct pattern. Code-owner items: remove the dead twin; adopt the narrowed nearestObjects filter on origin/master; optionally target the cooldown response.

Outcome: Supply missions row — Perf and JIP/HC cells filled (DR-39): Perf = origin/master all-object nearestObjects poll (branch/release narrowed; master adoption + smoke pending) + dead twin loop; JIP reviewed clean (pull-based status, server-side tracking). The row's Auth 🟡 remains the DR-18 + PR#1 items (owner).

Round 31 — 2026-06-02 (Claude) — WASP overlay Perf + JIP/HC (DR-40): the last Perf/JIP-HC cell

Lane wasp-overlay-perf-jip-review. Filled the final 🟡 Perf cell and ⬜ JIP/HC cell — the WASP overlay (WASP/*, the project-specific subtree). Reviewed against the WASP overlay page; live wiring confirmed in Client/Init/Init_Client.sqf.

DR-40 — WASP Perf clean except one sleepless display-wait busy-spin; JIP/HC correct (per-client init); old WASP init path is dead — Low (Perf nit + reviewed clean)

Live wiring (JIP-correct). The active WASP components are all execVM/call'd from Client/Init/Init_Client.sqf, which runs on every machine including JIP clients: DropRPG.sqf (:15, player call), global_marking_monitor.sqf (:267), WASP/baserep/init.sqf (:574), WASP/actions/AddActions.sqf (:575). Each client therefore builds its own local overlay on join — JIP-correct by construction (same principle as DR-37). The old monolithic init if (local player) then {ExecVM "WASP\Init_Client.sqf"} in initJIPCompatible.sqf:243-244 is inside the commented /* Marty : old wasp script … */ block — dead, superseded by the per-component wiring above (abandoned-code, already noted on the WASP page).

Perf. Mostly clean and bounded:

  • The one nit: global_marking_monitor.sqf:62 while {time < _this} do { _display = findDisplay 54; if (!isNull _display) exitWith {…add keyUp/keyDown EHs…} } is a sleepless busy-spin — it polls findDisplay 54 every frame for up to a 2 s window (_this = time + 2) with disableUserInput true during. Bounded and one-time at init, so impact is a brief hitch, but it is a genuine CPU spin. Its own sibling at :80 does it correctly: waitUntil {sleep 0.1; !isNull (findDisplay 12)}. Fix: convert :62 to the same throttled waitUntil {sleep 0.05; !isNull findDisplay 54} form.
  • The rest are bounded: baserep/repair.sqf polls at sleep 1 only while a repair is active; baserep/viem.sqf sleep 3; DropRPG.sqf sleep 30 is a post-drop cooldown, not a loop; AddActions.sqf:2 While {!(Alive Player)} do {sleep 2} is a one-shot wait that exits when the player spawns. No sustained per-frame marker/blink loop in the live WASP code.

JIP/HC — clean. Per-client init (above) means joiners initialize WASP locally; if (local player) guards (global_marking_monitor.sqf:78, DropRPG player call) correctly scope client-local features so a headless client (no real player) skips them. No PV-replay dependency in the live WASP path (the map-marking handlers are local input EHs, not networked state).

Auth/PV note (scoped out, for a future Auth pass). WASP action authority (Action_RepairMHQDepot.sqf, DropRPG.sqf createVehicle, baserep/repair.sqf) is the WASP row's remaining 🟡 Auth/PV — several are client-local self-effect, others touch shared objects; they belong to the same client-authority question as the economy class (DR-6/14/…) and are left as an explicit owner-review item, not closed here.

Handoff for Codex. Note on the WASP overlay page: global_marking_monitor.sqf:62 should use the throttled waitUntil idiom (as :80 does); the initJIPCompatible.sqf:243-244 WASP init is dead/commented (remove). Code-owner items are the one-line :62 throttle + dead-code removal.

Outcome: WASP overlay row — Perf and JIP/HC cells reviewed (DR-40): Perf clean except the :62 sleepless display-wait (one-line fix); JIP/HC correct (per-client init). This was the last outstanding Perf/JIP-HC cell in the matrix — every subsystem's Perf and JIP/HC dimension is now source-reviewed. The residual 🟡 cells are exclusively Auth/PV owner decisions (the client-authoritative economy/forgery class DR-1/6/14/16/22/23/27/28, the victory fixes DR-11/12/13, supply DR-18/PR#1, and the WASP/modules Auth follow-ups).

Round 32 — 2026-06-02 (Claude) — ATTACK_WAVE_INIT forgeable direct-PV (DR-41); confirms Codex's pv-scout candidate

Lane attack-wave-authority-verify (collaboration-follow: source-verifying Codex's attack-wave-authority backlog candidate, status new-from-2026-06-02-pv-scout). Confirmed at source and upgraded to a finding.

DR-41 — ATTACK_WAVE_INIT is a forgeable direct publicVariable: server trusts client _supply/_side, no re-derivation/authority/cost → side-wide free units — High (economy authority / forgery; new direct-PV channel)

Chain (source-cited):

  • Client gate is advisory only. Client/FSM/updateclient.sqf:240 adds the "HEAVY ATTACK MODE" action with params [(sideJoined) call GetSideSupply, sideJoined] and condition ((sideJoined) Call GetSideSupply) >= 25000 — a client-side visibility/eligibility check.
  • Client sends its own numbers. Common/Functions/Common_AttackWaveActivate.sqf:6-8: ATTACK_WAVE_INIT = [_supply, _side]; publicVariableServer "ATTACK_WAVE_INIT"; — a direct publicVariable to the server (not via the WFBE_PVF dispatcher).
  • Server trusts the payload wholesale. Server/Functions/Server_AttackWave.sqf:1-27 ("ATTACK_WAVE_INIT" addPublicVariableEventHandler): _supply = _this select 1 select 0; _side = _this select 1 select 1; then _discountPercentage = 0.7 * (0.4 + ((WFBE_C_ECONOMY_SUPPLY_MAX_TEAM_LIMIT - _supply) * (1/50000))) → sets ATTACK_WAVE_PRICE_MODIFIER (a side-wide unit-price multiplier, read by GUI_Menu_BuyUnits.sqf:90/261 and Client_UIFillListBuyUnits.sqf:60) and _attackWaveLength = (1 - _discountPercentage) * 1500 (a server-side sleep). No re-derivation of the side's real supply (never calls GetSideSupply), no check that _side matches the PV sender, and no server-side supply deduction.

Impact. WFBE_C_ECONOMY_SUPPLY_MAX_TEAM_LIMIT = 50000 (Init_CommonConstants.sqf:166), so legitimately _supply ∈ [0,50000] → modifier ∈ [0.28, 0.98] (a discount). But _supply is attacker-controlled and unvalidated: forging _supply = 50000 + 0.4·50000 = 70000 drives _discountPercentage → 0ATTACK_WAVE_PRICE_MODIFIER → 0every unit costs price × 0 = free for the chosen _side; larger forged values make the modifier negative (negative-priced units / broken pricing). The 25 000-supply cost is never deducted server-side (client gate only), so the forger pays nothing, and _side is attacker-chosen. ATTACK_WAVE_INIT is not in BattlEyeFilter/publicvariable.txt (only kickAFK, DR-30), so the channel is unfiltered. _attackWaveLength is also attacker-influenced (a sleep of forged length).

Architectural significance — the forgery class has two surfaces. This is the first confirmed exploit on a direct publicVariable channel rather than the registered PVF dispatcher. The DR-1 remediation (validate the PVF command string before compile) does not protect direct channels like ATTACK_WAVE_INIT. So the economy/forgery owner decision must cover both: (1) the PVF dispatcher (DR-1, validated lookup) and (2) each direct publicVariableServer PVEH must re-derive trusted values server-side — here: take _side from the PV sender/owner, compute _supply from GetSideSupply _side on the server, and deduct the cost there — ignoring the payload's economic fields. Other direct channels (side-supply, supply-mission, MASH) share this surface and warrant the same treatment.

Handoff for Codex. Confirms backlog item attack-wave-authority (was new-from-2026-06-02-pv-scout) → confirmed, High; flip its status and cross-link DR-41 from Networking (direct-PV hazard table) and the economy-authority roadmap entry. Fold into the same owner decision as the economy class, with the explicit "two surfaces (PVF + direct PV)" note so the server-authority redesign covers direct channels too.

Outcome: Economy/forgery class extended to a new direct-PV channel (DR-41). Collaboration-follow pass: a Codex pv-scout candidate verified at source and promoted to a confirmed finding.

Round 33 — 2026-06-02 (Claude) — HC static-defence update-back commented out (DR-42); + DR-19 dedup

Lane hc-static-defense-verify (collaboration-follow: adjudicating two raw backlog candidates).

DR-42 — Static-defence HC delegation never reports created units back to the server (update-back commented out) — Low/Medium (headless/JIP; confirms hc-static-defense-sync)

The server delegates static-defence AI to a random headless client (Server/Functions/Server_DelegateAIStaticDefenceHeadless.sqf:26, ["…","HandleSpecial",['delegate-ai-static-defence',…]]). The HC receiver Client/Functions/Client_DelegateAIStaticDefence.sqf creates the units (:25 _retVal = … call WFBE_CO_FNC_CreateUnitForStaticDefence) — but the update-back to the server is commented out (:28 //["RequestSpecial", ["update-delegation-static_defence", _teams]] Call WFBE_CO_FNC_SendToServer;). By contrast, the town-AI delegation does report back: Client/Functions/Client_DelegateTownAI.sqf:35 sends ["RequestSpecial", ["update-town-delegation", _town, _town_vehicles]], which Server_HandleSpecial.sqf ("update-town-delegation") folds into the town's wfbe_active_vehicles. So static-defence units created on an HC are invisible to the server — there is no server-side record for cleanup, accounting, or re-delegation. The HC manages only its own group lifecycle locally (:30-38, a per-team while {count units > 0} {sleep 1}; deleteGroup). This compounds DR-21 (HC disconnect → server load-migrates the units but cannot re-delegate): for static defence the server doesn't even know the units exist. Owner decision: either un-comment/restore the update-back (define the server update-delegation-static_defence handler) or document that static-defence HC units are deliberately fire-and-forget. Confirms backlog hc-static-defense-sync.

DR-19 dedup — server-fps-hosted-loop-sleep is already DR-19

The support-scout backlog candidate server-fps-hosted-loop-sleep ("move sleep outside the isDedicated branch in the server FPS monitor") is the same defect already confirmed as DR-19 (Round 9). Source re-verified: Server/Module/serverFPS/monitorServerFPS.sqf:1-7 is while {true} { if(isDedicated) { …; sleep 8; } } — on a hosted/listen server the if is false every iteration so the loop busy-spins with no yield (and Server/GUI/serverFpsGUI.sqf is the second, redundant publisher). Not a new finding — fold the backlog item into DR-19.

Handoff for Codex. Mark backlog hc-static-defense-sync → confirmed (DR-42); mark server-fps-hosted-loop-sleep → duplicate of DR-19. Cross-link DR-42 from the AI, headless and performance page near the DR-21 HC notes.

Outcome: two raw scout candidates adjudicated — one promoted (DR-42), one deduped (DR-19). AI/headless authority deepened.

Round 34 — 2026-06-02 (Claude) — external deep-research intake #2 (9 reports) → DR-43 (2 new source-confirmed leads)

Lane external-research-intake-2. Ray supplied 9 new deep-research reports (deep-research-report (1..9).md). Triaged all 9 (treating their content as untrusted leads, not authority — cross-checked at source). Same posture as the 3 PDFs in DR-26: these are downstream syntheses that corroborate the campaign, not independent source verification (they cite their own research-tool fetches, turnNNview…).

Mapping: (1) Runtime Architecture → boot/lifecycle DR-37 + the version.sqf lead below; (2) Trust-Boundary Audit → DR-1 (PVF Call Compile = privileged exec surface); (3) Feature Archaeology → DR-4/32/34/39 + the duplicate-binds lead below; (4) Modernization Strategy → roadmap (Codex lane); (5) Locality & JIP → DR-37/38; (6) Testing/Release Workflow → already shipped by Codex (Testing-Debugging-And-Release-Workflow.md); (7) Gameplay State Ownership → DR-32/38; (8) Server Authority Refactor → an independent restatement of the campaign's central thesis — "not one exploit but the architecture; funds/supply are mutated client-side then merely announced; the ledger is a replicated client mutation, not a server source of truth" = the economy-authority class (DR-6/14/16/22/23/27/28/41) + DR-1 + the two-surfaces point; (9) AI-onboarding playbook → agent-docs (Codex lane). Net: strong third-party corroboration; our source-cited DRs remain the verified core (superset-confirming-subset, as with DR-26).

DR-43 — two new leads extracted from the reports, both source-confirmed — Low (source-completeness + init redundancy)

(a) description.ext:39 #include "version.sqf" but version.sqf is absent from tracked source (original external-research note; refined below after a local ignored-file recheck). Since the live mission runs, the file must be present in generated/local/deploy context (consistent with the LoadoutManager/7za build per AGENTS.md). So this is a source-completeness/drift note, not a runtime bug: a clean checkout is not buildable/loadable directly from tracked source without the generation step that supplies version.sqf — anyone preprocessing description.ext from the raw tracked tree hits a missing include. Ties to the generated-mission story (DR-4/DR-32). Owner: commit a source version.sqf (or document and validate that packaging generates it).

2026-06-04 correction, refined: a later init/compile scout found Missions/[55-2hc]warfarev2_073v48co.chernarus/version.sqf:1 present on disk, but a stronger target-root recheck showed it is an ignored generated file, not tracked source: .gitignore:1 ignores it, git --literal-pathspecs ls-files -- Missions/[55-2hc]warfarev2_073v48co.chernarus/version.sqf returns no rows, and git status --ignored --short -- .../version.sqf reports !!. Vanilla Takistan has the same ignored-local-file shape via .gitignore:23. Keep the original clean-checkout release lesson: run/generate/check version.sqf for every target root before boot, pack or test claims.

(b) Server/Init/Init_Server.sqf has redundant duplicate compile/bind rows. Codex re-checked the current Chernarus source before promotion: three functions are live duplicate binds (WFBE_CO_FNC_LogGameEnd at :64 and :89, WFBE_SE_FNC_PlayerObjectsList at :69 and :91, WFBE_SE_FNC_AwardScorePlayer at :83 and :93), while three other apparent duplicates are commented remnants (WFBE_CO_FNC_InitAFKkickHandler, WFBE_CO_FNC_monitorServerFPS, WFBE_SE_FNC_MASH_MARKER). The live duplicates are perf-trivial because they happen at init and bind the same files, but they are a maintenance trap: if a pair diverges later, the second bind silently wins. The LogGameEnd duplicate also sits near the DR-13 game-end cleanup area. Owner: de-duplicate live binds and either remove or clearly annotate the commented remnants.

Handoff for Codex. Add the 9 reports to external-research-report-manifest.json (Codex's lane); mark them corroborating (no contradictions found). The two confirmed leads (version.sqf source gap; duplicate Init_Server binds) are small code-owner cleanups — cross-link DR-43(b) from the victory/DR-13 area and DR-43(a) from the tooling/generated-mission docs.

Outcome: external-research intake #2 complete — 9 reports corroborate DR-1..DR-42 (esp. report 8 ↔ the economy-authority thesis); two new source-confirmed leads recorded as DR-43.

Round 35 — 2026-06-02 (Claude) — side-supply ledger is directly client-writable (DR-44); 2nd direct-PV forgery

Lane direct-pv-supply-authority (research autonomy: walking the direct publicVariableServer channels enumerated in Public variable channel index for the forgery class DR-41 opened).

DR-44 — wfbe_supply_temp_<side> lets any client write a side's supply balance via a forged direct PV — High (economy authority / forgery)

Chain (source-cited):

  • Client sender Common/Functions/Common_ChangeSideSupply.sqf:28-30: missionNamespace setVariable [format ["wfbe_supply_temp_%1", _side], [_side, _amount, _reason]]; publicVariableServer format ["wfbe_supply_temp_%1", _side]; — a direct channel (not via the PVF dispatcher).
  • Server handler Server/Functions/Server_ChangeSideSupply.sqf (addPublicVariableEventHandler on wfbe_supply_temp_west :1 and wfbe_supply_temp_east :25): takes _side = _this select 1 select 0 and _amount = _this select 1 select 1 straight from the payload (:4-5/:28-29), computes _change = _currentSupply + _amount (:11/:35), caps at WFBE_C_MAX_ECONOMY_SUPPLY_LIMIT, then missionNamespace setVariable ["wfbe_supply_<side>", _change] + publicVariable (:19-21/:43-45).
  • No authority: the handler never checks the PV sender, never verifies _side belongs to the sender, and never re-derives _amount server-side. So a forged wfbe_supply_temp_west = [west, 999999, "x"] makes the server set west's supply to (current + 999999), capped at the max — arbitrary side-supply inflation from any client.

Impact. Supply gates attack-wave eligibility (≥25000, DR-41), funds/income, and production — so writing the supply balance is a high-leverage economy exploit. The author was aware: the _reason fallback string is literally "This might indicate a malicious supply update request. Check stuff if you see this message." (:6/:30) — i.e. a log breadcrumb was added instead of authority validation.

Relationship to other findings. Same files as DR-22 but a different axis: DR-22 is the broken overspend floor (if (_change<0) then {_change=_currentSupply-_amount} should be {_change=0}, a correctness bug present here too at :12/:36); DR-44 is the authority/forgery gap (payload-trusted _amount, no sender/side check). And it is the second confirmed direct-PV forgery after DR-41 — establishing that the direct-channel surface is a class, not a one-off. Sharpens the economy thesis: not only is spending client-authoritative (DR-6/14/16/22/23/27/28), the supply ledger itself is directly client-writable.

Owner decision. Folds into the economy-authority decision (server-side authority vs BattlEye) with the two-surfaces note (PVF dispatcher DR-1 + direct channels DR-41/DR-44). The direct fix: the wfbe_supply_temp_<side> handler must derive the authorized delta server-side (or validate the sender is the side's commander/server) and ignore the payload's _amount as an authority. BattlEye publicvariable.txt should also restrict wfbe_supply_temp_* (not shipped, DR-30).

Handoff for Codex. Update the Public variable channel index wfbe_supply_temp_* row to cite DR-44; add DR-44 to the economy-authority class wherever the class is listed (it's a new member); the Pending owner decisions economy table gains a row.

Outcome: direct-PV forgery surface now has two confirmed members (DR-41 attack-wave, DR-44 side-supply); economy thesis extended to "the supply balance is client-writable".

Round 36 — 2026-06-02 (Claude) — full wiki audit follow-through: DR-45 (town-AI vehicle despawn) + direct-PV surface closed + coverage-gap assessment

Lane wiki-audit-followthrough. Three parallel audits of all 60 wiki pages (duplication already resolved by Codex; this pass = accuracy/consistency/coverage). The wiki is healthy (no broken links, no orphans, DR severities consistent everywhere). The Public-Variable-Channel-Index PVF line ranges (:8-20/:23-37) were a verified audit false positive and should not be "fixed." Correction 2026-06-03: this paragraph formerly grouped DR-15's _side = _this at Server_AssignNewCommander.sqf:3 into that false-positive bucket; source recheck confirms that was stale. RequestNewCommander.sqf:13 still passes [_side, _assigned_commander], while Server_AssignNewCommander.sqf:3-5 treats the full payload as _side and indexes element 1 as commander, so DR-15 remains patch-ready/source-unpatched. Real outcomes below. Update 2026-06-07: DR-15's call-shape bug is now fixed on master (Server_AssignNewCommander.sqf:4 reads _side = _this select 0;) — see the RESOLVED note on the DR-15 record; only the redundant new-commander-assigned broadcast remains.

DR-45 — Town-AI inactivity despawn deletes vehicles with player passengers — Medium (gameplay; player vehicle loss)

Server/FSM/server_town_ai.sqf:213-216 cleans up a captured/inactive town's vehicles:

{ if (alive _x) then { if (!(isPlayer leader group _x)) then {deleteVehicle _x} } } forEach (_town getVariable 'wfbe_active_vehicles');

The guard !(isPlayer leader group _x) only spares a vehicle whose group leader is a player. It does not inspect crew/cargo/turret occupants, so an AI-led (or empty-group) vehicle that a player is riding as a passenger/cargo is deleted under them during the despawn sweep. Promotes the existing (un-numbered) Town-AI-Vehicle-Despawn-Safety playbook — confirmed by Codex's Einstein verifier and now re-confirmed at source — to a formal DR. Fix: before delete, also check crew _x / assignedCargo _x for any isPlayer, or skip vehicles with any player occupant.

Direct-PV forgery surface — closed (coverage-clean)

Following DR-41/DR-44, the remaining client-touchable direct publicVariable channels were source-checked for the same forgery class:

  • REQUEST_SUPPLY_VALUE (Server/Functions/Server_PV_RequestSupplyValue.sqf) — clean: a read-only query (SUPPLY_VALUE_REQUESTED = (side _player) call GetSideSupply; (owner _player) publicVariableClient …); no mutation, no authority surface (worst case: a client reads another side's public supply — negligible).
  • MARKER_CREATION (Client/Functions/Client_onEventHandler_MARKER_CREATION.sqf) — clean/cosmetic: creates a side-visible local map marker from the payload; worst case is cosmetic marker spam, no gameplay/authority impact.

Conclusion: the direct-PV forgery surface is fully enumerated and bounded to the two mutation channels (DR-41 ATTACK_WAVE_INIT, DR-44 wfbe_supply_temp_<side>); the read/cosmetic direct channels are safe. No further direct-PV forgery findings.

Coverage-gap & code-depth assessment (what remains unreviewed)

CLEARED 2026-06-07 (Round 41, agent team). Every item below has now been source-reviewed to finding depth; the residual list is empty. See Round 41 for DR-51..DR-54 and the dead-code/correction routing.

Original accounting (kept for provenance), now annotated with its clearance:

  • Server/AI respawn + orders — ✅ CLEARED (Round 41): DR-51 (both respawn paths orphaned/uncalled), AI_TLWPHandler dead, live water-loop traced to Common_WaypointPatrolTown.sqf (not the dead Orders/AI_Patrol.sqf).
  • Cleaners/restorers Perf (Server/FSM/cleaners/*, buildings_restorer.sqf) — already documented in Marker cleanup/restoration atlas. Live RPT samples remain a code/test-owner perf-tuning gate (not a review gap).
  • Config data model (Common/Config/Core*/, Gear/, Loadout/, Defenses/) — ✅ CLEARED (Round 41): full load-order/data-model map produced and folded into Assets/config atlas; DR-54 (Core_US/USMC dedup guard + AH64D faction).
  • Server/FSM/basearea.sqf, groupsMonitor.sqf, Server/Support/Support_* — ✅ CLEARED (Round 41): DR-52 (support-special authority), groupsMonitor dead-code, basearea _onAreaRemoved accumulation (nuanced perf).
  • PR#1 supply-helicopter delta — ✅ CLEARED (Round 41): line-by-line on feat/supply-helicopter → DR-53 (forgeable SupplyAmount + non-cash double-reward); the suspected stacked Killed EH was refuted.
  • Code depth note (updated 2026-06-07): the AI/respawn, config, support and PR #1 subsystems are now at finding depth alongside the economy/forgery and PVF classes. The only remaining non-review gaps are owner decisions and live RPT/Arma smoke evidence.

Handoff for Codex. Audit punch-list (Codex-lane accuracy fixes) recorded in Wiki quality audit "Round 2"; DR-45 should be cross-linked from the Town-AI playbook + AI/headless atlas; the coverage-gap list seeds the next review queue.

Outcome: DR-45 filed (town-AI passenger-vehicle deletion); direct-PV forgery surface closed as bounded; coverage gaps enumerated for the next phase.

Round 37 — SEND_MESSAGE second RCE (Claude, 2026-06-02)

Lane send-message-rce-review. While producing a standalone "20 improvements" review for the owner (a shareable PDF), a fresh source pass over the message/notification path surfaced a second network-data call compile site that DR-1's analysis explicitly assumed did not exist.

DR-46 — SEND_MESSAGE broadcast call compiles network text: a second client-side RCE, independent of the PVF dispatcher — High (security / RCE; extends and corrects DR-1)

Client/Functions/Client_onEventHandler_SEND_MESSAGE.sqf:25-31 handles the SEND_MESSAGE direct publicVariable broadcast (_SEND_MESSAGE_infos = _this select 1; index 0 = text, index 2 = receiving side, index 3 = multi-language flag). When the flag is true it runs:

if _is_multi_language_message then { _messageText = call compile _messageText };
systemChat _messageText;

_messageText is network data. Any connected client can broadcast SEND_MESSAGE with arbitrary SQF in index 0 and true in index 3; every receiver whose playerSide matches index 2 compiles and executes the payload — a full client-side remote-code-execution primitive. It does not pass through WFBE_PVF_* / Server_HandlePVF / Client_HandlePVF, so even a perfect DR-1 dispatcher fix leaves this surface open. Common/Functions/Common_SendMessage.sqf:26 has the identical sender-local call compile, and the callers (artillery / ICBM friendly+enemy messages) pass compilable strings.

Correction to DR-1. DR-1's "why the dispatch never needs Call Compile" note (≈ line 159) states the other call compile sites "compile files… or local engine key-strings…, not network data" and that there is "no second-order injection on the PVF path." That is accurate for the PVF path, but SEND_MESSAGE is a direct addPublicVariableEventHandler outside that path, and it does compile network data. The repo therefore has two independent network-data call compile RCE surfaces, not one — DR-1's single-site assumption should be amended to name SEND_MESSAGE explicitly.

Fix. Treat the multi-language payload as a stringtable key plus arguments — ["STR_KEY", arg1, …] — and resolve with localize + format; treat any non-array payload as a literal string and never compile it. Patch the identical line in Common_SendMessage.sqf and update the artillery/ICBM callers to send arrays. Optionally allowlist the SEND_MESSAGE channel (with a value-shape filter) in the BattlEye publicvariable.txt from DR-30. Source-verified; fix shape in agent-hardening-backlog.jsonl#send-message-call-compile-rce.

Maintainability leads (for Codex to verify against existing pages, not yet filed as DRs). The same pass source-confirmed these, but they may already be owned by Variable and naming conventions / SQF atlas: (1) Common/Init/Init_Common.sqf compiles ~15 functions twice — once as a short global (GetSideID) and again as WFBE_CO_FNC_GetSideID, both pointing at the same file (plus ~22 un-prefixed globals in Init_Server.sqf and four GetClosestEntity{,2,3,4} variants); (2) the four Client_Support{Repair,Refuel,Rearm,Heal}.sqf (~90% identical), Construction_{Small,Medium}Site.sqf (identical heads) and the ten Common/Config/Loadout/*.sqf (one structure, data-only difference) are large copy-paste families; (3) ~12 hardcoded English hint/systemChat strings bypass stringtable.xml.

Codex closure. Cross-linking is complete: DR-46 is routed from Networking and public variables, Public variable channel index, SQF atlas, Feature status, Pending owner decisions and Hardening roadmap. The DR-1 single-site note above now explicitly scopes "no second-order injection" to the PVF path and names SEND_MESSAGE as the second network-data compile surface. The maintainability leads remain verify-then-document, not new DRs unless confirmed undocumented.

Outcome: DR-46 filed and Codex cross-link closure completed — a second, source-verified network-data call compile RCE (SEND_MESSAGE), correcting DR-1's single-site assumption. The source patch remains P0 patch-ready/source-unpatched in agent-hardening-backlog.jsonl. (Context: produced alongside a standalone 20-improvement PDF for the owner.)

DR-30 correction — remoteexec.txt is Arma 3-only; BattlEye filters are local + contingent (defense-in-depth, not the primary fix)

Two refinements to DR-30 (BattlEye posture), surfaced during the improvements pass and confirmed at source:

  • remoteexec.txt does not apply to Arma 2 OA. It is an Arma 3 BattlEye filter (it gates the A3-only remoteExec / remoteExecCall commands); Arma 2 OA has no such command, so there is nothing for it to filter. DR-30 / External-Integrations and any "missing filters" list should drop remoteexec.txt and name the A2 OA set instead: scripts.txt, publicvariable.txt, createvehicle.txt, setpos.txt, setdamage.txt, setvariable.txt, deletevehicle.txt, mpeventhandler.txt, addmagazinecargo.txt / addweaponcargo.txt / addbackpackcargo.txt, teamswitch.txt, waypointcondition.txt, selectplayer.txt, attachto.txt.
  • BattlEye is defense-in-depth, contingent on the server still loading BE. BE's global/online anti-cheat for the Arma 2 series is effectively defunct, but the filter files are processed locally by the server-side BE component (reads BEpath next to server.cfg), independent of BE's online services. Direct evidence the deployment model assumes working BE filters: Client/FSM/updateclient.sqf:153-162 broadcasts kickAFK with the comment "detected by BattlEye (customized filter)… Must be kicked using BattlEye Filter." So if AFK-kick works on the production server, BE filters are live (and a scripts.txt would too); if it does not, BE isn't loaded and the BattlEye remediation is moot. Either way, the durable, BE-independent fix for the RCE/economy classes is server-side authority (DR-1, DR-46, DR-6, DR-27, DR-41/44); BattlEye is a layered backstop only.

Codex closure. External-Integrations and this DR-30 list now drop remoteexec.txt from the missing Arma 2 OA filter framing, name the OA-era filter set and reframe BattlEye as contingent local-filter defense in depth using updateclient.sqf:153-162. DR-30's substance — no security filters are shipped in the repo — stands; only the filter-name list and the "primary mitigation" framing changed.

Round 38 — Commander vote AI/no-commander outcome mismatch (Codex, 2026-06-04)

Lane commander-vote-ai-no-commander-outcome-mismatch. A source pass over the commander election worker found a server/client disagreement around AI/no-commander votes.

DR-47 — Commander vote selects any non-tied player candidate even when AI/no-commander votes should win — Medium (gameplay correctness / commander control)

Server/Functions/Server_VoteForCommander.sqf:24-29 counts player-team votes and treats wfbe_vote == -1 as _aiVotes. The final selection then uses:

if ((!_tie && _highest >= _aiVotes && _highestTeam != -1) || (!_tie && _highest <= _aiVotes && _highestTeam != -1)) then {_commander = _teams select _highestTeam};

For numeric vote counts, _highest >= _aiVotes or _highest <= _aiVotes is always true. Therefore any non-tied player candidate with _highestTeam != -1 becomes commander, including equal-vote and AI/no-commander-majority cases. The AI/no-commander fallback only remains reachable for ties, no player candidate, or the later "selected team is not player-led" guard at :45-46.

The client vote dialog presents different semantics: Client/GUI/GUI_VoteMenu.sqf:87-89 previews AI/no commander when the highest row is row 0 or when the leading option is not above half of counted player voters. That means clients can see "AI"/"No Commander" as the apparent outcome while the server assigns a player commander after the countdown.

Fix shape. Decide the intended rule first: plurality, strict majority, AI/no-commander as a real candidate, or no-commander only on row-0 win. Patch the server condition to match that rule, then smoke player-majority, no-commander-majority, equal-vote, player tie and late-join/revote restart cases. Keep this separate from DR-15 manual reassignment call shape and commander-authority hardening, though they share the same UX surface.

Codex closure. Cross-linked from Feature status and Commander/HQ lifecycle atlas. Source remains unpatched.

Round 39 — Town-defense overhaul capture-persistence cleanup re-introduces the occupancy-deletion class (Claude, 2026-06-07)

Lane town-defense-overhaul-capture-persistence-occupancy-review. The Marty_town_defense_overhaul merge landed on stable/local master 89ae9dad after the 2026-06-02 coverage milestone, adding three new files (Common/Functions/Common_MarkTownDefenseAsset.sqf, Server/Functions/Server_CleanupExpiredTownDefenseAssets.sqf, Server/Functions/Server_SendTownDebugChat.sqf) plus 17 modified files in Chernarus. This is freshly-landed, previously-unreviewed code, so it was source-reviewed for the DR-45 hazard class and new authority/perf surfaces.

DR-48 — Town-defense capture-persistence cleanup deletes occupied captured vehicles; OBJECT guard is occupancy-blind and the GROUP branch is unguarded — Medium (gameplay; player vehicle loss, extends DR-45)

When a town is captured, its surviving defender groups, units and vehicles are registered for temporary persistence and given an expiry:

// Server/FSM/server_town.sqf:238-258
_persistDelay = missionNamespace getVariable ["WFBE_C_TOWN_DEFENSE_CAPTURE_PERSIST_TIME", 600];
if (WF_Debug) then {_persistDelay = 60};
_persistUntil = time + _persistDelay;
...
{ ... [_location, _x, _sideID, "captured_mobile_vehicle", _persistUntil] Call WFBE_CO_FNC_MarkTownDefenseAsset; ... } forEach _captureVehicles;

WFBE_SE_FNC_CleanupExpiredTownDefenseAssets runs per town, every town-AI loop iteration, before the activity checks (Server/FSM/server_town_ai.sqf:61), so expired assets are reaped even in an actively-contested just-captured town — exactly when a capturing player is most likely riding a captured vehicle. The OBJECT delete branch is:

// Server/Functions/Server_CleanupExpiredTownDefenseAssets.sqf:61-64
if (_assetType == "OBJECT") then {
    if !(isPlayer _asset) then {
        if !(isPlayer leader group _asset) then {deleteVehicle _asset};
    };
};

Two distinct gaps, both source-confirmed:

  1. The OBJECT guard is occupancy-blind for vehicles. captured_mobile_vehicle and static_weapon assets are vehicle objects, and the guard never inspects crew/cargo/turret occupants — it is the same class as DR-45. Worse, for a vehicle object group _asset is grpNull (group resolves a person, not a vehicle), so isPlayer leader group _asset evaluates isPlayer objNull = false and the delete always fires. Under that engine semantics the guard spares no occupant — not even a player driver — so a player who boarded a captured town vehicle has it deleted under them when the 10-minute persistence (60s under WF_Debug) expires. The repo's own occupancy primitive corroborates this: Server/Functions/Server_HandleEmptyVehicle.sqf keeps a vehicle alive while {alive _x} count crew _vehicle > 0, i.e. it uses crew, never group, to detect occupants. Open verification: the BIKI page for group could not be fetched live this pass (HTTP 403); confirm in-engine that group <vehicle> returns grpNull in Arma 2 OA 1.64. The recommended fix below is correct under either interpretation.

  2. The GROUP branch has no player guard at all. static_group / captured_static_group / mobile_group assets hit the GROUP branch, which deletes every member unconditionally:

    // Server/Functions/Server_CleanupExpiredTownDefenseAssets.sqf:57-60
    if (_assetType == "GROUP") then {
        {deleteVehicle _x} forEach units _asset;
        deleteGroup _asset;
    };
    

    Real-world player risk here is lower (town-defender groups are AI-created), but the asymmetry — OBJECT branch attempts a (broken) player guard while GROUP branch attempts none — is a latent gap if a player ever ends up in a marked group (e.g. team-switch/join into a defender group).

Relationship to DR-45. DR-45 is the same occupancy-deletion class in the inactivity despawn path (server_town_ai.sqf:278); DR-48 is the capture-persistence path in the new overhaul, with distinct files and asset types, so it is filed separately. The group <vehicle> = grpNull observation, if confirmed, also sharpens DR-45: its :278 guard !(isPlayer leader group _x) likewise iterates vehicle objects from wfbe_active_vehicles, so it too would spare no occupant rather than only "missing cargo/passengers." Both fixes converge on the same crew-based primitive.

Fix shape. Before deleting an OBJECT asset, skip it if any occupant is a player: if (({isPlayer _x} count crew _asset) == 0) then {deleteVehicle _asset}; (optionally retain the existing leader exception as a behavior-preserving addition, matching the DR-45 patch shape on Town-AI vehicle despawn safety). For the GROUP branch, skip member deletion when any unit is a player: if (({isPlayer _x} count units _asset) == 0) then { {deleteVehicle _x} forEach units _asset; deleteGroup _asset; };. Patch Chernarus source, propagate to maintained Vanilla via LoadoutManager, then smoke: capture a town, board a captured_mobile_vehicle as driver and as cargo, and confirm it is not deleted under the player at expiry; verify empty captured vehicles are still reaped. Fix shape in agent-hardening-backlog.jsonl #town-defense-overhaul-capture-persistence-occupancy.

Branch scope. Source-verified on current source/Vanilla and stable/Miksuu 89ae9dad. The overhaul is not present on historical 2cdf5fb8; perf/quick-wins 0076040f and release 7ff18c49 predate this merge for these new files — branch presence of the overhaul on those heads should be confirmed by the next current-head refresh pass before cross-branch status wording.

Outcome: DR-48 filed — the new town-defense overhaul re-introduces the DR-45 occupancy-deletion class in the capture-persistence cleanup, with an occupancy-blind OBJECT guard (illusory for vehicles, pending one in-engine confirmation) and an unguarded GROUP branch. Source unpatched; routed to the Town-AI playbook, coverage ledger and hardening backlog.

Round 40 — Audit Findings Queue verification sweep → DR-49, DR-50 (Claude, 2026-06-07)

Lane audit-findings-queue-verification-sweep. The Audit Findings Queue (2026-06-03) held ~48 previously-UNVERIFIED () audit claims flagged as "claims to check." This sweep source-checked every remaining row against current master (Chernarus) via read-only scouts, then adversarially re-verified the high-severity promotions at source. The queue page now carries the per-row verdicts; this round records the two genuinely-new confirmed exploits as numbered findings plus one correction.

DR-49 — Side-supply underflow guard adds supply instead of clamping (economy exploit) — High (economy integrity)

Common/Functions/Common_ChangeSideSupply.sqf:24-25 and the server-side handlers Server/Functions/Server_ChangeSideSupply.sqf:12 (west) / :36 (east):

_change = _currentSupply + _amount;
if (_change < 0) then {_change = _currentSupply - _amount};

The second line is intended as an underflow guard, but for a negative _amount (a deduction), _currentSupply - _amount = _currentSupply + |_amount|. So any deduction large enough to drive supply below zero instead increases side supply by the deduction's magnitude. The correct clamp is _change = 0. This is the arithmetic logic of the supply mutation itself — distinct from DR-44, which covered the forgeability of the wfbe_supply_temp_<side> channel; DR-49 is wrong even for legitimate server-issued deductions. Fix: if (_change < 0) then {_change = 0}; in all three sites (Common + both server handlers); propagate to maintained Vanilla. Smoke: drive a side's supply near zero and issue a deduction larger than the balance; confirm it clamps to 0, not jumps up. Fix shape in agent-hardening-backlog.jsonl #side-supply-underflow-guard-inverts. (Was audit row SG3.)

DR-50 — HQ kill awards score twice on a clean kill and once on a teamkill — Medium (scoring integrity)

Server/Functions/Server_OnHQKilled.sqf awards HQ-kill score in two places:

  • :23,46-47unconditional _points = 30000 / 100 * WFBE_C_BUILDINGS_SCORE_COEF (900 at the default coef 3), awarded to leader _killer_group on every HQ kill, including teamkills;
  • :74-81 — a second award _score = 900 to leader _killerGroup, this one correctly guarded by if (_side != side _killer) (non-teamkill only).

Net effect: a legitimate enemy HQ kill awards 1800 (900 + 900); a teamkill still awards 900 from the unconditional block, despite the :62-71 message branch treating teamkills as punishable. Fix: move the :46-47 award inside the non-teamkill guard (or delete one of the two awards so a clean kill pays once), and ensure teamkills award nothing. Smoke: kill an enemy HQ and confirm a single bounty; teamkill a friendly HQ and confirm zero score. Fix shape in agent-hardening-backlog.jsonl #hq-kill-double-score. (Was audit row SG2.)

Correction — NJ10 (JIP HQ killed-EH) is not a victory-blocker

The audit row NJ10 (Client/Init/Init_Client.sqf:512, "JIP HQ killed-EH not added when deployed → victory may not fire") was flagged "high." Source re-verification shows the authoritative HQ-killed handler is server-local — Server/Init/Init_Server.sqf:323, Server/Construction/Construction_HQSite.sqf:89 (comment: "Killed EH fires localy, this is the server") and Server/Functions/Server_MHQRepair.sqf:37 all add killed → WFBE_SE_FNC_OnHQKilled directly on the server. The client EH at Init_Client.sqf:515 only relays process-killed-hq to the server (redundant with the server's own EH). The !_isDeployed gate means a post-deploy JIP client skips the redundant relay, but the server still processes the kill and fires victory. NJ10 is therefore a minor robustness/cleanup item (⚠️ NUANCED), not a victory failure. Recorded so future agents don't re-open it as critical.

Outcome: The Audit Findings Queue is fully verified — no rows remain. Two new source-confirmed exploits filed (DR-49 supply underflow, DR-50 HQ double-score); SG1 re-confirms DR-11/DR-13 and SG14 maps to DR-30; NJ10's "victory-blocker" framing corrected. Per-row verdicts, false positives (V8, V9, V12, NJ6), GONE/fixed (AI13) and nuanced/design items live on the Audit Findings Queue.

Round 41 — Agent-team clearance of the coverage-gap backlog → DR-51..DR-54 (Claude, 2026-06-07)

Lane clear-wiki-coverage-gap-backlog. A five-agent team source-reviewed the weakly-understood subsystems listed in Round 36's coverage-gap assessment (AI respawn, AI orders/waypoints, the config data model, server support + misc FSM, and the PR #1 supply-helicopter branch); every candidate finding was then adversarially verified at source by a second agent. This round records the confirmed findings and two important corrections; the gap list above is now marked cleared.

DR-51 — AI respawn handlers are orphaned: the active respawn path never attaches, so AI leaders silently never respawn — Medium (gameplay; AI never regenerates)

Server/AI/AI_AddMultiplayerRespawnEH.sqf is the only file that attaches the MPRespawn handler driving AIAdvancedRespawn (the non-vanilla/OA path), but it has no call site — a whole-tree grep for AI_AddMultiplayerRespawnEH, AddMultiplayerRespawnEH and addMPEventHandler returns zero hits outside the file itself. AIAdvancedRespawn is compiled at Server/Init/Init_Server.sqf:12 but only ever referenced inside that orphaned EH body, so it is dead. The vanilla path AISquadRespawn (Init_Server.sqf:11) is gated by WF_A2_Vanilla, which initJIPCompatible.sqf:91 sets false unconditionally (the only true path is #ifdef VANILLA, and VANILLA is never defined), and a grep for spawn AISquadRespawn returns nothing — so it never runs either. Net: both server AI-respawn paths are compiled-but-uncalled; AI group leaders are never re-spawned by this subsystem on a dedicated server. Fix: compile AI_AddMultiplayerRespawnEH.sqf and call it for each AI group leader at group creation (matching the WF_A2_Vanilla guard), or delete the dead path if AI leader respawn is intentionally disabled. Source-verified on current master; Arma smoke (kill an AI leader, confirm no respawn) recommended before patch. (From the AI-respawn review.)

DR-52 — Support-special spawn requests have no server-side authorization, and accept a client-supplied group handle — High (authority; free support assets + cross-team injection)

The support-special request chain is Client GUI → WFBE_PVF_RequestSpecial → Server_HandlePVF → RequestSpecial.sqf:1 (\_this Spawn HandleSpecial`) → Server_HandleSpecial.sqfcasesParatroops/ParaVehi/ParaAmmo/uav (:43-65), each of which does only args spawn KAT*with **no commander check, no side verification, and no server-side funds deduction** (funds are deducted client-side inGUI_Menu_Tactical.sqf:372,514,525, and the GUI only gates the commander check for ICBM, not these four). The PVEH registration (Init_PublicVariables.sqf:50) extracts no sender identity. Worse, the support scripts read the **owning group from the client payload** — Support_Paratroopers.sqf:5 (_playerTeam = _this select 3), Support_ParaAmmo.sqf:7, Support_ParaVehicles.sqf:7, Support_UAV.sqf:9— with no check thatside _playerTeam == _sideor that the group belongs to the sender, so a forged request cancreateUnit-join paratroopers into an arbitrary (enemy) group and route server→client marker messages to the enemy leader. The PVF dispatcher's Call Compileonly resolves theSRVFNC-prefixed name, so this is a **data-authority** hole, not RCE — it shares the client-authoritative class with DR-1/DR-6/DR-27/DR-28/DR-41/DR-44. **Fix:** in HandleSpecial, derive the sender's side/group/commander-status from the PVEH sender identity (_this select 0), validate against the payload, and deduct funds server-side. Source-verified on current master`. (From the server-support review.)

DR-53 — PR #1 supply-helicopter economy is client-forgeable and double-pays the pilot on non-cash heli runs — High (economy; branch feat/supply-helicopter)

On origin/feat/supply-helicopter: the client sets SupplyAmount / SupplyByHeli as broadcast object variables on the supply vehicle (Client/Module/supplyMission/supplyMissionStart.sqf:53-55, setVariable [...,true]), and the server reads them back without re-derivation — supplyMissionStarted.sqf:6 (_byHeli) and supplyMissionCompleted.sqf:9 (_supplyAmount), which is then paid via ChangeSideSupply (:38). Any client can setVariable ["SupplyAmount", <arbitrary>, true] on the vehicle and the server pays it out (capped only by the global WFBE_C_MAX_ECONOMY_SUPPLY_LIMIT, not per-mission). Separately, for non-cash heli runs (Air level 3): the server deposits the full _supplyAmount into the team pool via ChangeSideSupply and the client pays the pilot _supplyAmount * WFBE_C_SUPPLY_HELI_REWARD_MULT personal cash via ChangePlayerFunds (supplyMissionCompletedMessage.sqf:19) with no _cashRun guard — value is minted from both paths. (Air-4 cash runs skip ChangeSideSupply and are single-source/clean.) Fix: re-derive _supplyAmount server-side from the source town's supplyValue and re-check Air upgrade; gate the client pilot reward on _cashRun, or pay all rewards server-side. Branch-scoped to feat/supply-helicopter; must be resolved before PR #1 merge + Arma smoke. (From the PR #1 review.)

DR-54 — Core_US.sqf / Core_USMC.sqf unit-registration dedup guard is broken, and AH64D_EP1 is mislabeled to the wrong faction — Low (config correctness / buy-menu filter)

In Common/Config/Core/Core_US.sqf:289 and Core_USMC.sqf:239 the duplicate-detection read drops the _get = assignment (missionNamespace getVariable (_c select _z); with the result discarded), so _get stays nil every iteration and the isNil '_get' guard is always true — the duplicate-registration log branch is unreachable and every entry unconditionally overwrites any prior registration. The other 19 Core/*.sqf files use the correct _get = … form. Compounding this, Core_US.sqf:205-206 registers AH64D_EP1 with faction field (index 8, QUERYUNITFACTION) 'USMC' instead of 'US', so the buy-menu filter (Client_UIFillListBuyUnits.sqf:39) shows it only under the USMC tab, not US. Fix: restore _get = on both lines; correct the AH64D_EP1 faction string (or move the entry to Core_USMC.sqf). Source-verified on current master. (From the config-data-model review.)

Confirmed minor / dead code (routed, not separately numbered)

  • Server/AI/AI_TLWPHandler.sqs (legacy straggler-sync SQS) and Server/FSM/groupsMonitor.sqf (debug poll, launch commented at Init_Server.sqf:571) are dead code — no call site anywhere. Routed to Dead/stale code register.
  • AI autonomous supply-truck is dead: Init_Server.sqf:387 does [_side] Spawn UpdateSupplyTruck but the only compile of UpdateSupplyTruck is commented at :36, so it spawns nil; and AI_UpdateSupplyTruck.sqf:17 ExecFSM "Server\FSM\supplytruck.fsm" references a file that does not exist anywhere in the tree. This confirms and sharpens the existing AI supply-truck branch matrix (nil-Spawn site :387 is the precise current-source symptom).
  • Live unbounded water-avoidance loop: the audit's AI11/AI_Patrol water-loop lead is real but the dead files are Orders/AI_Patrol.sqf / AI_TownPatrol.sqf (compiled-but-uncalled); the live instance is Common/Functions/Common_WaypointPatrolTown.sqf:48-52 (and Common_WaypointPatrol.sqf), invoked from server_town_patrol.sqf:43. A surfaceIsWater spin with no iteration cap can starve its scheduler thread near water-adjacent towns. Perf-owner lane.
  • Config init file-read cost: each Core/*.sqf vehicle entry with crew-slot -2 does Call Compile preprocessFile of the crew-slot helper (which itself recurses into two more preprocessFile helpers) — ~281 such entries across 21 Core files → ~850-1000+ synchronous file reads at startup. One-shot init cost; hoist the compile out of the loop if startup time matters.
  • PR #1 branch carry-over: server_town_patrol.sqf:18 on feat/supply-helicopter flips the loop exit to || (while {!WFBE_GameOver || _aliveTeam}), leaking one zombie patrol thread per town-team after game-over (30 s sleep, so not a spin). Fix to && before merge.

Corrections (refuted / downgraded long-standing assumptions — preserve)

  • PR #1 "stacked Killed EH" is REFUTED. The long-suspected duplicate-interdiction-handler-on-reload issue does not occur: supplyMissionStarted.sqf:13-16 guards the add with isNil {getVariable "wfbe_supply_killed_eh_set"} and broadcasts the flag, so a second load on the same vehicle never re-adds the EH. The real residual issue is a double cooldown timer (supplyMissionStart.sqf:6-9 reads the cooldown before the server PV round-trip returns) plus SupplyAmount/SupplyFromTown not being zeroed on abort — Medium, not the suspected High EH-stack.
  • PR #1 interdiction Killed EH is server-safe. A candidate "EH runs ChangeSideSupply on a client" was refuted: ChangeSideSupplyCommon_ChangeSideSupply.sqf:30 only publicVariableServers wfbe_supply_temp_<side>; the economy write happens exclusively in the server-only Server_ChangeSideSupply PVEH. (The separate forgeability of wfbe_supply_temp_* is the already-filed DR-44, not this path.)
  • AI loadout empty-array crash is a non-issue (audit AI14 follow-up): every WFBE_%1_AI_Loadout_* across all 10 faction roots is non-empty and Common_EquipUnit.sqf:18-19 guards the optional 4th/5th args with count _this > 4/5, so neither the random count [] nor the EquipUnit else-branch is reachable. The literal 13 index equals WFBE_UP_GEAR (correct).

Outcome: the coverage-gap backlog is cleared — all five weakly-understood subsystems are now source-reviewed to finding depth. DR-51..DR-54 filed; dead code and config bugs routed; two PR #1 assumptions corrected (Killed-EH no-stack; interdiction EH server-safe). Full per-subsystem maps, source anchors and open questions were produced by the agent team and are summarized on the owner pages (AI/headless, Support specials, Assets/config atlas, Current supply heli PR).

Round 42 — Deeper hazard-class sweep (68-agent team) → DR-55..DR-57 + systemic authority result (Claude, 2026-06-07)

Lane deep-hazard-class-sweep. An orthogonal pass: 7 agents swept the whole mission tree by hazard class (exhaustive PV/PVF authority enumeration, direct-channel forgery, call-compile/EH-stacking, JIP/HC missing-broadcast, perf busy-waits, logic inversions, whole-tree dead-code), each feeding adversarial verifiers, deduped against DR-1..DR-54 and the audit-queue confirmations. The headline is systemic.

DR-55 — The PVF / PVEH / direct-publicVariable handler surface systemically lacks server-side sender authentication — Critical (authority; the economy/forgery class is the whole network surface, not a few channels)

Root cause: the dispatcher Server/Functions/Server_HandlePVF.sqf:14 runs _parameters Spawn (Call Compile _script) and Common/Init/Init_PublicVariables.sqf:50 registers … addPublicVariableEventHandler {(_this select 1) Spawn WFBE_SE_FNC_HandlePVF}the sender's machine/identity (_this select 0/select 2) is never extracted or forwarded. So every server PVF handler and direct-PV EH that does not independently re-derive the sender's side/role from its own payload is forgeable by any connected client (including the enemy side). This generalizes DR-1 (RCE), DR-27, DR-41, DR-44, DR-52, DR-53 from "specific channels" to "the entire surface." Newly enumerated forgeable handlers (all source-verified, none previously catalogued):

Handler / channel Source Severity Forged effect
RequestVehicleLock Server/PVFunctions/RequestVehicleLock.sqf:1-7 Critical lock/unlock any vehicle (incl. enemy MHQ) — _vehicle lock _locked with no side/owner check
RequestChangeScore Server/PVFunctions/RequestChangeScore.sqf:3-8 Critical set any player's score to an arbitrary value (addScore -_old; addScore _new)
ATTACK_WAVE_DETAILS (direct PV) Server/PVFunctions/AttackWave.sqf:19-42 Critical drain a side's entire supply to 0 + set arbitrary price modifier (e.g. 0.01) for arbitrary duration; distinct from DR-41 (ATTACK_WAVE_INIT) which is server-computed
WFBE_Server_PV_SupplyMissionCompleted (direct PV) + SupplyAmount truck var supplyMissionCompleted.sqf:2,9,26; supplyMissionStart.sqf:34 Critical client broadcasts SupplyAmount on the truck and fires the completion PV directly → unlimited instant supply, bypassing the proximity loop; truck path, distinct from DR-53 (heli)
RequestMHQRepair Server/PVFunctions/RequestMHQRepair.sqf:1, Server_MHQRepair.sqf:3 High rebuild any side's HQ (cross-side); no alive/side/count/in-progress guard
RequestNewCommander (direct-assign path) Server/PVFunctions/RequestNewCommander.sqf:8-13 High force-assign commander to any team outside a vote window (no commander/admin/side check); distinct from DR-15/DR-47 vote paths
RequestTeamUpdate Server/PVFunctions/RequestTeamUpdate.sqf:1-26 High reprogram behaviour/combat/formation/speed for all teams of any side (enemy AI sabotage)
RequestSpecial "RespawnST" Server_HandleSpecial.sqf:55-60 High kill all supply trucks of any side
RequestSpecial "repair-camp" Server_HandleSpecial.sqf:225-247 High flip any camp to any sideID + global CampCaptured (territory forgery)
RequestSpecial "upgrade-sync" Server_HandleSpecial.sqf:67-73 + Server_ProcessUpgrade.sqf:29 High instantly complete any in-progress upgrade for any side (timer bypass)
RequestSpecial "connected-hc" Server_HandleSpecial.sqf:195-209 High inject own group into WFBE_HEADLESSCLIENTS_ID (HC-pool poisoning; only guard is owner != 0)
RequestSpecial "update-clientfps" Server_HandleSpecial.sqf:75-84 High zero another player's WFBE_AI_DELEGATION_<uid> FPS slot via foreign UID (targeted delegation-DoS)
RequestSpecial "update-town-delegation" Server_HandleSpecial.sqf:86-95 High append arbitrary vehicles to any town's wfbe_active_vehicles (deletion/grief)
RequestSpecial "update-teamleader" Server_HandleSpecial.sqf:6-12 Medium poison wfbe_teamleaderServer_OnPlayerDisconnected.sqf:57,102 deletes a live enemy unit on disconnect
Action_RepairMHQDepot (client direct setVariable) WASP/actions/Action_RepairMHQDepot.sqf:23-28 High client broadcasts cashrepaired=true and {_x setVariable ["supplyValue"…]} draining all own-side towns; no server validation
WFBE_Client_PV_SupplyMissionStarted supplyMissionStarted.sqf:8 Medium stamp LastSupplyMissionRun cooldown on any town (supply-mission DoS)
WFBE_C_PLAYER_OBJECT playerObjectsList.sqf:1-34 Medium spoof [ownObject, victimUID] into WFBE_SE_PLAYERLIST (displaces victim's entry; reward-redirect blocked by live getPlayerUID recheck)
MARKER_CREATION (direct PV) Common_CreateMarker.sqf:82-83 Low inject fake same-side map markers; global createMarker name-collision can overwrite real event markers
AFKthresholdExceededName initAFKkickHandler.sqf:9-12 Low client log-injection (no enforcement sink)

Fix (one architectural change): make the PVF dispatcher capture the sender (the addPublicVariableEventHandler callback's _this select 1 is the value; the owner is available via the channel — or re-architect SendToServer to embed getPlayerUID player/owner player and have the server cross-check it against _this origin). Then each handler must re-derive side/commander/funds server-side from the authenticated sender, not from payload fields. This is the same server-authority redesign already named for the economy class (DR-1/6/14/16/22/23/27/28/41/44/49/50/52/53) — DR-55 establishes that the redesign must cover the entire handler list above, not a subset. Read/cosmetic channels confirmed clean (server-originated, no client mutation): PLAYER_RADIATED, REQUEST_SUPPLY_VALUE/SUPPLY_VALUE_REQUESTED, WFBE_DAYNIGHT_DATE, SERVER_FPS_GUI, HQ-alive/marker-infos, stagnation/compensation displays, wfbe_supply_<side> (server→all). Fix shapes per-handler in agent-hardening-backlog.jsonl #pvf-handler-sender-authentication.

DR-56 — ARTY_HandleSADARM.sqf: infinite while {true} thread leak per air-kill + per-frame setVelocity busy-wait — High (perf / scheduler exhaustion)

Common/Module/Arty/ARTY_HandleSADARM.sqf (spawned once per SADARM round via Common_HandleArtillery.sqf:33): :137-141 is if !(isNull _impactAreaSimulation) then { while {true} do { sleep 1; deleteVehicle _impactAreaSimulation } } — the object is deleted on iteration 1 but the loop has no exit, leaking one permanently-sleeping scheduled thread per SADARM air-kill for the rest of the mission. Separately, :38-44 is a waitUntil with no sleep that calls getPos/velocity/setVelocity every frame for the 10-20s parachute descent (the sibling loop at :50-72 correctly uses sleep 0.2, confirming the omission). Fix: replace :137-141 with a single deleteVehicle; add sleep 0.1 inside the :38 waitUntil. Fix shape in agent-hardening-backlog.jsonl#arty-sadarm-thread-leak.

DR-57 — Town patrols can never spawn: wfbe_patrol_active_last is reset to time every town-AI cycle — High (gameplay; an entire AI feature is dead at runtime)

Server/FSM/server_town_ai.sqf:67-68 unconditionally sets wfbe_patrol_active = false and wfbe_patrol_active_last = time on every pass of the per-town loop (~5s). The spawn gate at :295-296 then requires time - (_town getVariable "wfbe_patrol_active_last") > WFBE_C_PATROLS_DELAY_SPAWN (360s), but the timestamp was just refreshed milliseconds earlier in the same iteration, so the delta is always ~0 and never reaches 360. Town patrol groups never spawn for the entire match. (Distinct from the known AI1 ||-vs-&& loop bug in server_patrols.sqf:26, which is also confirmed still-unpatched on current master — see below.) Fix: remove the unconditional :68 timestamp reset; stamp wfbe_patrol_active_last only on the active→inactive transition (the completion path server_patrols.sqf:71-72 already does). Fix shape in agent-hardening-backlog.jsonl#town-patrols-never-spawn.

Confirmed-new minor (routed, not separately numbered)

  • PLAYER_RADIATED broadcast (Client/Module/Nuke/radzone.sqf:102-104): server publicVariables the full player object to all clients per radiated player per 5s tick; should be (owner _x) publicVariableClient. Perf, Medium.
  • playerObjectsList PVEH registered twice (Server/Init/Init_Server.sqf:73 and :95 both Call Compile the installer): every client connect races two spawns on WFBE_SE_PLAYERLIST → duplicate entries. Remove :95. (Compounds known SG6.)
  • skillDiffCompensation.sqf:31,89 inner while loops have no WFBE_GameOver exit → continue running (incl. ChangeSideSupply/DB calls) for minutes after game-over. Add && !WFBE_GameOver.
  • monitorServerFPS.sqf broadcasts WFBE_VAR_SERVER_FPS every 8s but nothing reads it (the HUD uses SERVER_FPS_GUI from serverFpsGUI.sqf) — dead orphan broadcast, remove Init_Server.sqf:599.
  • Action_RepairMHQDepot.sqf:28 writes the lowercase key "supplyvalue" (engine is case-sensitive; all readers use "supplyValue") → the intended all-town SV reset is a silent no-op. Correct the case or delete the debug residue.
  • Client_BuildUnit.sqf:332,334 duplicate Fired→HandleReload EH (current-master confirmation of audit V2, still unpatched here; PR #8 fixed it on the release branch only).
  • AI_SquadRespawn.sqf:1 Private ["_rcm'"] apostrophe typo (no-op while spawned; refactor hazard).

Corrections / dedup notes (preserve)

  • Several audit-queue items confirmed still unpatched on current master (they were PR #8 / release-branch fixes, not merged to master): SG5 (server_town_camp.sqf:135 camp flag uses old _side), AI1 (server_patrols.sqf:26 || zombie loop), AI2 (town mortar chain dead — WFBE_SE_FNC_ManageTownMortars never compiled + Server_SpawnTownMortars.sqf:20 undefined _positions), SG4 (RequestOnUnitKilled.sqf:92 undefined _objectType on kill-assist bounty), SG9 (Common_ChangeSideSupply.sqf:12 _reason guard count>3 should be >2, log-only, sole 3-arg caller is AttackWave.sqf:40).
  • AntiStack DB call compile on raw callExtension output (callDatabaseRetrieve.sqf:30 etc., all 6 DB scripts) is a real server-side RCE-if-DLL-compromised surface — folded as a sharper citation under DR-7..DR-10 (use parseSimpleArray, not call compile), not a new DR.
  • SEND_MESSAGE receiver Client_onEventHandler_SEND_MESSAGE.sqf:27 call compile — sharper citation under DR-46 (the receiver side of the same channel; confirms the broadcast executes on every client, not just the sender).
  • JIP/HC: LocalizeMessage.sqf:30 Teamstack waitUntil and the Init_Client.sqf :406/:409/:502/:802 main-thread waitUntil chain have no timeouts → permanent client freeze if the relevant server broadcast is dropped or the server aborts mid-init. Owner hardening (bounded waits), extends DR-37's note.

Outcome: the deeper pass established the systemic result (DR-55: the whole PVF/PVEH/direct-PV surface is client-forgeable, ~18 newly-enumerated handlers) plus DR-56 (ARTY perf) and DR-57 (town patrols dead). Minor perf/dead/logic findings routed; six known audit items confirmed still-unpatched on master; DR-7..10 and DR-46 given sharper citations. No gameplay source changed.

Continue Reading

Previous: Testing workflow | Next: Implementation plan

Main map: Home | Fast path: Quickstart | Agent file: agent-context.json

Sidebar

Clone this wiki locally