dayz-mcp-verify
Skill20 starsChanged 42 days agoMITView on GitHub
Auto-test a DayZ mod in-game through dayz-mcp without keyboard or OCR: spawn a classname, orbit/capture, raycast collision, inspect placement/attachments/telemetry and emit PNG+JSON evidence. Compose with dayz-test-ingame; dayz_test_run/dayz_test_stop own the managed lifecycle, then this skill drives the bridge. Use for "auto-probar el mod", "verificar in-game con MCP", visual smoke/re-test, "comprobar que el .p3d carga/se ve/colisiona", automated spawn+capture+raycast, or the vehicle acceptance ladder (spawn→render→get-in→drive→wheel direction). Covers static objects, items/weapons, buildings without doors and vehicle placement/drivability. Player UI, door interaction, inventory use and firing remain manual. Also: ScriptConsoleTabRegistry, IsHeadless roboclient, UA_AM_INIT action inject.
In this repository47 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- 3d-generation-harnessSkill
- ai-3d-to-dayzSkill
- ardy-motion-generationSkill
- blender-animationSkill
- blender-assemblySkill
- blender-visual-reviewSkill
- dayz-3d-viewerSkill
- dayz-ai-patternsSkill
- dayz-animation-pipelineSkill
- dayz-aviationSkill
- dayz-basebuildingSkill
- dayz-charactersSkill
- dayz-clothingSkill
- dayz-doorsSkill
- dayz-environment-hazardsSkill
- dayz-feature-specSkill
- dayz-model-pipelineSkill
- dayz-mod-workflowSkill
- dayz-motorbikesSkill
- dayz-p3d-auditSkill
- dayz-p3d-inspectorSkill
- dayz-particlesSkill
- dayz-pbo-buildSkill
- dayz-pbo-reverse-engineeringSkill
- dayz-persistenceSkill
- dayz-physics-engineSkill
- dayz-preflightSkill
- dayz-proxy-alignSkill
- dayz-realistic-animation-directorSkill
- dayz-sound-systemSkill
- dayz-test-ingameSkill
- dayz-texture-pipelineSkill
- dayz-ui-developmentSkill
- dayz-undergroundSkill
- dayz-vehiclesSkill
- dayz-weaponsSkill
- enforce-script-referenceSkill
- mixamo-retargetSkill
- ovh-cloudSkill
- rigorous-data-auditSkill
- rip-vehicle-importSkill
- systematic-debuggingSkill
- task-observerSkill
- uv-clean-atlasSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
26 rules. About 0 are common, 5 are distinctive, and 21 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
21
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 67
Before operational flow, read canonical protocol: <runbooks>\dayz-mcp-agent-session-protocol.md.
- Very specific: line 159
bridge_statusgreen: ALWAYS first call: bridge_status. - Very specific: line 259
Workaround: read entire log at end of sequence and assign responses in order, verifying alignment with some field from event itself (a …
- Very specific: line 317
All start from spawn. world_spawn(type=<classname>, pos=[x,y,z]) → PASS if ok and without unknown_type/spawn_failed.
- Distinctive: line 407
Read the fixture again a few seconds later.
- Very specific: line 518
Keep OverrideRaise(ENABLED) server-side (init.c) as well so server agrees.
- Very specific: line 542
exec_enforce: does not execute on the headless diag server (GATE4B-LIM, engine limitation like MakeScreenshot) — do not rely on it to … - Distinctive: line 562
If the result comes out "did not persist", that is the harness, NOT the mod: do not report it as a bug.
- Distinctive: line 664
User's eye > capture: when render is ambiguous: in MercedesAMGLF grab was flaky and user reading was reliable diagnosis of winding and …
- Very specific: line 771
NOT work outside the Mercedes (MERCEDES_AMGLF hardcoded in MCPBridge.c:835 and loopback.py:113.
- Very specific: line 802
If you add a verb to the bridge (SERVER_COMMANDS / CLIENT_COMMANDS in loopback.py) and the tool responds not_whitelisted, do not look for …
- Very specific: line 859
Before any mutation on "the first human": bridge_status.coordination.active, server_peer.last_poll_age_s and Get-CimInstance Win32_Process …
- Very specific: line 976
If the lane's workspace contains only one layer, say so in the brief and require that everything relative to the others goes to …
- Very specific: line 995
When it was finally used: wait_for_box_s=600 -> active_run_exists with hint: "stop it with dayz_test_stop(run_id=...)".
- Distinctive: line 1002
If a tool tells you how to unblock yourself, do it before waiting for anyone.
- Very specific: line 1056
Before spawn, read query_player_state.pos and place the object next to the player.
- Very specific: line 1059
After several camera_set, treat as orphan camera any series of identical frames from different positions.
- Very specific: line 1067
When the test needs mod APIs that have no MCP verb, use an Enforce driver in the init.c of the workspace mission …
- Very specific: line 1114
Do not blindly duplicate spawn.
- Distinctive: line 1149
Before writing a negative in HANDOFF from this path:
- Very specific: line 1163
Before considering the box free, read -mod= of living processes:
- Very specific: line 1196
After that, get-in returned not_seated for any car, also on a clean site and after player_respawn.
- Very specific: line 1212
Create the fixture with flags=8389668.
- Very specific: line 1213
Before the first episode, entities_query(pos, radius=30) shows no foreign vehicle.
- Very specific: line 1214
After vehicle_get_in_client, the returned type is the class you created.
- Very specific: line 1216
Before analysing an episode, cross-check the trace's car_type against the class you asked for.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
DayZ MCP verify — auto-test in-game vía tools MCP
GATE 0 — ask WHO is driving, before touching anything (added 2026-08-07)
(until 1.29: managed dayz_test_run / dayz_test_stop and Mode=all capture still hold.) (since 1.30 Exp: Diag Script Console tabs register via ScriptConsoleTabRegistry; IsHeadless() names roboclients; SP PerformActionStart no-ops while pending. Details: dayz-1-30-mcp-verify.md and ## DayZ 1.30 Exp.)
Before the first dayz-mcp tool in a verification run, ask the user whether
they are driving or the MCP is. Neither is assumed. A single question per run, not per
capture.
Why it is a gate and not a preference: with the user present, their manual cycle is faster
and yields better results — spawns with VPP in seconds and evaluates the whole scene at a glance,
while MCP loop needs to position camera, capture, and chain tools. MCP wins when
the user is NOT there: early morning, unattended run, or while attending another front. Furthermore,
changing mode is not free midway (clean manual test requires relaunching without
@DayZ_MCP), so the decision is made at the start of the cycle or paid twice.
| Situation | Who drives |
| User present and available | Them (VPP + direct judgment) — MCP only if they ask |
| User absent / unattended run | MCP, and report is left for them with evidence |
| Doubt | Ask. Never assume |
What does NOT change: MCP remains mandatory for what a human cannot measure by eye (telemetry, numerical raycast, exact placement, repeatable series). There you do not ask, you use it.
Measurement that originated gate (2026-08-07, corpus of 276 sessions): 63% of wall-clock time in a full cycle falls on user side, and 71% of their actual verifications are automatable with today's tools. That it is automatable does not imply it should be automated: with them present, their cycle rules.
WHAT THIS DOES
Drives an already launched DayZDiag client, via dayz-mcp MCP server tools, to
verify a mod without human intervention: spawns object, views it from multiple angles
(camera + window-grab), checks collision via raycast, reads placement/telemetry, and
produces a report with pass/fail criteria and evidence. It is the automated observation
loop bridging the gap between "PBO compiles/deploys" and "looks and behaves properly
in-game", for the class of properties that DO NOT require keyboard input.
Server-authoritative + passive pixel capture. Control and data are engine-native
(spawn, raycast, telemetry, camera); sole non-native piece is visual capture
(window-grab of rendered client — MakeScreenshot is broken in diag, T165276).
COMPOSITION — public lifecycle + MCP verification
Build/deploy/launch belongs to dayz-test-ingame (its environment preflight runs on its own:
P:\ mounted, junction P:\Mods, AddonBuilder, allowFilePatching). This skill invokes it
with MCP mod added and then drives. Bridge verbs do not start or terminate
processes; public test tools do orchestrate managed lifecycle.
Before operational flow, read canonical protocol:
<runbooks>\dayz-mcp-agent-session-protocol.md.
[EXACT][CLAIM-R21-MCP-ORCHESTRATED-TEST] Execute lifecycle with
dayz_test_run and stop it with dayz_test_stop on exact run_id. Both
tools possess FIFO queue, lease, heartbeat, and release; do not wrap them in
a second session_acquire. Reserve session_* primitives for
low-level mutations not already encapsulated. Every lifecycle is
identified by run_id: sharing mod does not grant ownership. Under retail
quarantine only reads are allowed; if whoever opened retail cannot close it via
UI, declare manual_cleanup_required.
Bridge startup sequence (rev. 2026-09-06): dayz_test_run → session_acquire_wait →
verbs. Never probe before adopting. "Do not wrap launch in another lease" does not mean run
remains yours: upon completion, dayz_test_run RELEASES its owner lease (daemon audit:
session_release_finished reason=owner_release right after launch) and run transitions to RUNNING_IDLE
without owner (release_owner, process_lifecycle.py:958-976). From that instant the idle run fence
rejects ALL /enqueue on that run, reads included: _enqueue_run_rejection discards flag
mutation (loopback.py:1293-1296) and for RUNNING_IDLE returns run_not_owned (:1317-1318) with
hint "This run has no owner (RUNNING_IDLE). Adopt the existing run before dispatching." (:99-102,
:1323-1329). The counter-intuitive part: bridge_status counter is named
fence.mutation_rejects_by_code (:2821-2822) and counts reads as well. Every verb crossing
bridge falls there (query_*, telemetry, raycast, captures, world_spawn…) and also
wait_for(players_at_least|players_at_most), which polls with query_all_players (server.py:2645-2650).
wait_for(log_matches) does NOT fall: reads log files without touching bridge (server.py:2603-2637,
:2686-2697).
dayz_test_run(...)→ retainrun_id.- Optional without adopting:
wait_for(log_matches, "…")to wait for startup via RPT. session_acquire_wait(purpose=…): grant adopts the ONLY ownerlessRUNNING_IDLErun and declares it inadopted_run(_adopt_on_grant,loopback.py:3119-3120,:3193-3230). Checkadopted_run.okbefore first verb. With multiple idle runs it answersadopted_run.error = "multiple_idle_runs"and adopts none (:3202-3208):dayz_test_stopsurplus run and repeat.wait_for(players_at_least, 1)and remaining verbs.- [EXACT] The adopted lease expires by itself (added 2026-09-27, LFPowerGrid, three runs): it lasts 120 s and is NOT renewed internally, fresh MCP client or not. Send
session_heartbeatat least every ~90 s during the whole run, including while a human plays. Past the deadline the run goes ownerless again (grace.remaining_scounts down) and the daemon stops it (lifecycle_stop_outcome: stopped), even with the user in front of the game. (measured in game, DayZ 1.30.164014 Exp)
- [EXACT] The adopted lease expires by itself (added 2026-09-27, LFPowerGrid, three runs): it lasts 120 s and is NOT renewed internally, fresh MCP client or not. Send
session_release→dayz_test_stop(run_id).
With an MCP client prior to 8f5727f of DayZ_MCP (2026-09-06) rejection arrived as a
BARE remote_error, without code or hint (ledger entry fb-20260906-193626-f45d, first attributed to a
client without polling); with current client it arrives as run_not_owned: This run has no owner
(RUNNING_IDLE)…. In both cases remedy is adopting, neither retrying nor relaunching. This rule corrects
step 1 of recipe SP-292 and clarifies first bullet of SP-152, below.
Two lease failures around long waits (measured in game sessions, 2026-09-28): (1) wait_for over the log does NOT renew the lease — a wait longer than ~2 min expired with lease_expired; interleave session_heartbeat between waits. (2) When a user message interrupts an in-flight call (a long wait_for), the next session_heartbeat answers lease_invalid: token was never valid for this client; if the run is still alive, session_acquire_wait readopts it. Also: dayz_test_close shuts the run down in order, with a termination line on both sides. [EXACT]
Companion externo: dayz-labs
[EXACT][CLAIM-R21-MCP-COMPANION-AUTHORITY]
external_companion_no_lifecycle_authority. Release dayz-labs v0.1.35,
reviewed at pinned commit
dbd6ad3e54e30c81a9aeb88fcb9f60f007804c2a, can serve as reference or
optional companion. Its start/stop/restart verbs are excluded while
DayZ_MCP holds a run: only dayz_test_run / dayz_test_stop on exact
run_id govern that lifecycle. Do not install or update companion
as part of a gate; always document the examined pinned_version.
Its interface is WPF and captures its own application:
wpf_not_layout_evidence. A correct capture from dayz-labs does not validate the
parser, proportions, clipping, or semantics of a DayZ .layout; UI
viewer needs its own render and comparison against in-game evidence.
The dayz_test_run request must select Mode=all, build when PBO
changed, and @DayZ_MCP as additional dependency:
-Mode all(server + client) is MANDATORY: visual capture reads from rendered client. A headless-Mode serverhas no window to grab.@DayZ_MCPis loaded alongside the mod under test; both peers (server + client bridge) poll the MCP server loopback.
PREREQUISITES (gate before driving)
dayz-mcpregistered and connectable. PRIMARY gate: are toolsmcp__dayz-mcp__*available in session? (ToolSearch finds them → gate PASS).claude mcp listis SECONDARY and NOT reliable: gives false× Failed to connectwith server operational — do not block on it if ToolSearch sees tools. Tools are only available in a session started WITH server already registered (broker registration--client, see TROUBLESHOOTING).Bridge config seeded. In-game bridge reads
dayz_mcp.json(url + key + pollHz) from its profiles; MCP server uses<DayZ_MCP_dev>\tools\.dayz_mcp.key. Seed the SAME key in launch profiles withinstall-mcp.ps1pointing to directories generated bydayz-test.ps1:<DayZ_MCP_dev>\tools\install-mcp.ps1 ` -ServerProfiles "<TargetMod>_dev\_server\profiles" ` -ClientProfiles "<TargetMod>_dev\_client\profiles" ` -MissionPath "<...>\mpmissions\dayzOffline.chernarusplus"[verify on first run]— generic seeding (arbitrary mod +@DayZ_MCPviadayz-test.ps1) was until now only exercised throughrun-fase3.ps1. The FIRST run of this skill validates that bridge finds config in these paths; ifbridge_statusreportslast_poll_age_s = null, config is not where bridge looks for it — adjust path and declare it.bridge_statusgreen. ALWAYS first call:bridge_status.server_peermust have freshlast_poll_age_s(not null). For capture,client_peeras well.never polled→ bridge did not start (config/key/startup) → abort and diagnose, do not keep driving blindly.Mods passed via
extra_mods/base_modsmust be REAL DIRECTORY underP:\Mods(→!Workshop); Steam junctions ARE NOT valid. Two layers, confusing them costs a session:- Public boundary
_valid_public_mod(DayZ_MCP_dev\tools\dayz_mcp\dayz_test_tool.py:75-85) rejects:,\and/across all projects → absolute workshop path never passes through tool. That is by design, NOT a gap in your project's policy. (Updated 2026-10-03: the boundary now also accepts an absolute path inside the selected project'smod_roots;_valid_public_modcallsdayz_test_request._valid_mod_entry(dayz_test_tool.py:269-272,dayz_test_request.py:222-233, DayZ_MCP_dev506de5d). A workshop path lies outside them and still fails, withbad_mod.) - Subscribed mods exposed by Steam in
!Workshop(@CF,@Dabs Framework,@VPPAdminTools) are Junctions tosteamapps\workshop\content\221100\<id>, and path identity guard rejects them →dayz_test_failed, which is the generic catch-all ofserver.py:1101and swallows the real cause.
Operational consequence: CF/Dabs/VPP are only loaded by declaring them with absolute path in project
default_base_modsinside launcherrequest-policy.json— which is how SUB_BRZ and LFHeli declare them. Mods that are real directories (@DayZ_MCP,@LFHeliCore, mod under test) do go viaextra_modswith relative name.Diagnosis in one command:
Get-Item '<!Workshop>\<@Mod>' -Force | Select-Object LinkType→Junction= relative does not work. Measured 2026-08-02 withpreflight=true(pure dry-check:dayz_test_worker.py:533-534returns before build and launching anything): without mods OK ·@DayZ_MCPOK ·@A6_SR2MOK ·@CFFAIL ·base_mods=["@CF"]FAIL.[EXACT][CLAIM-MCPV-JUNCTION-EXTRA-MODS-130] (Corrected 2026-10-26, stable box, DayZDiag 1.30.164200.) A Steam junction did pass in
extra_mods:@SurvivorAnims, a mount-point junction in!Workshop(Workshop item 2918418331), passedpreflight=truewith projectDayZ_MCPand then loaded in two runs, server and client (-mod=P:\Mods\@DayZ_MCP;P:\Mods\@SurvivorAnims;...in the RPT header,Worldmodule at 2349 files with its scripts). The rejection above was measured on 2026-08-02 with@CF. Run the preflight on the junction you need before going throughrequest-policy.json.When the mod under test does not compile, test a probe copy of it (added 2026-10-03, measured 2026-10-02, dayz-mcp run 606a5dbb) [EXACT][CLAIM-MCPV-PROBE-COPY], for example when its build packed another session's untracked test scripts:
- copy the deployed PBO entry for entry without those entries (every other entry byte-identical,
the same header properties, the SHA-1 trailer recomputed) into a probe folder of its own under
P:\Mods; - host it in another approved project whose
default_base_modscarry what the mod needs (CF and Dabs there): an absolute workshop path inbase_modsfails withbad_mod, since each entry must be a folder name or an absolute path inside the project'smod_roots(dayz_test_tool.py:399,dayz_test_request.py:222-233); - writing under
P:\Modschanges what every later run can load: ask the owner in chat first (in that session the agent harness's auto-mode permission check blocked the write until the owner agreed), and move the probe out ofP:\Modswhen done.
In that run the copy (641 of the 645 entries) compiled the mission module, and its models were the deployed ones byte for byte.
⚠ Before blaming the bridge for
version_blocked/last_poll_age_s=null: check thatkeyofdayz_mcp.jsonin project profiles is the SAME asDayZ_MCP_dev\tools\.dayz_mcp.key. A stale key gives 401 and bridge never polls, with symptom identical to "mod is not loaded". Key rotations leave behind projects not exercised since then (2026-08-02: MERCEDES_AMGLF and LFPowerGrid with old key, SUB_BRZ/LFHeli/DayZ_MCP with current key).⚠⚠ The symptom is NOT always
version_blocked/last_poll_age_s=null— and believing so costs the session just the same (measured 2026-08-17 in LFPowerGrid, with this warning already written above and overlooked). With stale key the peer can readversion_state: okwithlast_poll_age_sof 1.485 s: it is data from LAST good poll, which may belong to another already dead run. A dead bridge reads like healthy this way. The telltale signature is in game script log:[MCP-POC] poll error=5(server) /[MCP-CLIENT] client poll error=5(client). That 5 isEREST_ERROR_CLIENTERROR(scripts\3_game\http\restapi.c:16-17), meaning daemon returned 401 — 401 DOES NOT appear anywhere on game side. Two-command gate before blaming anything:- Compare key of each
dayz_mcp.jsonthe run will use against keyfile. These are two paths and$profile:wins over$mission:(MCPBridge.c:145-149,MCPClientBridge.c:234-237); if profiles key is missing, bridge falls back to mission key, which is usually the stale one. Same length (43) is NOT proof: compare the bytes. Invoke-WebRequest "http://127.0.0.1:8765/poll?key=<keyfile content>"→ 200{"commands":[]}proves daemon and live key are fine, isolating failure to run JSON. And fixing it DOES NOT require server restart:ReloadKeyAfterFailure(MCPBridge.c:373-398) rereads key when backoff hits ceiling. Fix JSON, wait ~20 s,[MCP-POC] poll key reloadedappears and peer returns tolast_poll_age_s0.2. That saves the boot.
⚠ Player spawn needle cannot presuppose gender. The example
Create entity type 'SurvivorM_—suggested bywait_fordescription itself— fails in silence with a female character: 300 s timeout with player already in world andCreate entity type 'SurvivorF_Helga'written to RPT. UseCreate entity type 'Survivor.- Public boundary
BRIDGE GOTCHAS THAT COST ONE RUN EACH (added 2026-08-18)
Verified in-game on 2026-08-18 during ATM cell test battery. All six cost at least one run each, and five of six produce a verdict accusing the mod without the mod having any issue (6 runs, 0 actual mod failures). Check them BEFORE writing first probe.
dayz_test_runDOES NOT load@DayZ_MCPby default, and returnssucceededanyway. Both processes launch alive, the game works, and the bridge stays silent. The symptom you receive (server_poll_stale/client_not_polling/ready.reason=no_run) points to bridge or key, not to mod set, so time is lost in the wrong place. Gate: passextra_mods=["@DayZ_MCP"]and confirm in server log that defineDayZ_MCPappears in all four modules, or look for[MCP-POC]lines.logs_sincedoes not accept its ownmarker. It RETURNS it as dict {path: [offsets]} and its parameter requires string: Pydantic ValidationError. Incremental draining is impossible. Workaround: read entire log at end of sequence and assign responses in order, verifying alignment with some field from event itself (a type, an id). If two probes share that field, markambiguous_alignmentinstead of guessing.wait_for(log_matches)does not work to wait for YOUR response. With defaultlookback_lines=200, a line from previous probe satisfies pattern instantly. And withlookback_lines=0it has been seen timing out with line already present in client log, returning a line from another flow inobserved. Use it as opportunistic wait; verdict comes from pollinglogs_since.- Player inventory is NOT reset between runs. Second run finds what
first left behind, nothing fits anymore and dotation fails silently (
create_failed, or endow of 0 units). To chain:dayz_test_stop, delete<mission>\storage_1\players.dbwith backup, and relaunch (~3 min). WARNING:clean=trueDOES NOT do this — forcesBuild=trueand passes-clearto AddonBuilder, meaning rebuilds the PBO and breaks binary identity, which is precisely what an A/B test cannot afford. Balance/state that mod saves in server profile SURVIVES deletion. inventory_givereturnscreate_failedwhether classname does not exist or does not fit. They are indistinguishable. Before blaming inventory, verify classname exists: grep in missiontypes.xmland test with a control vanilla item (Appleworks). And do not presuppose mod config classnames exist: they can point to a mod not loaded, in which case exact classname count gives 0 forever and entire feature looks broken without being broken.- Do not believe a verdict without looking at client log. Many mod events are written by CLIENT, not server. In test battery originating this section, automatic verdict said FAIL or INCONCLUSIVE six times in a row while client log showed operations executing properly. Dangerous failure mode of a cell is not "does not measure": it is "measures poorly and accuses the mod", and that propagates to project documents. If a verdict says FAIL, open client log before recording it.
world_spawnwithok=1DOES NOT prove object stays where you asked (added 2026-08-21, DayZ-MCP council).IsSpawnReady(MCPBridge.c:2729-2753) takes ACTUAL position of object itself, searches objects at that position, and considers itself ready if it finds itself. An object is always where it is: gate is tautological and satisfied for any live object, even if physics is already ejecting it. Does not compare against REQUESTED position, does not measure drift, and does not require inter-tick stability. Real case: spawn at urban point withok=1and vehicle ending up at Y = -40 km. Distinct from inverted coords failure above, which times out: this gives false PASS. Rule: after anyworld_spawnthat matters, confirm withobject_inspectorentities_querythat distance to requested position is as expected, and repeat reading a few seconds later to rule out drift. A playbook verdict looking only atokis invalid.
THE LOOP
- Launch via
dayz-test.ps1 … -ExtraMods "@DayZ_MCP"(wait until client and server are in; BUG-009: client autoconnect is flaky — higher-ServerWait/ retry). - Gate
bridge_status(PREREQUISITES.3). - Scene conditions (comparable captures):
world_time_setat noon (all 5 argsyear/month/day/hour=12/minute=0are mandatory) andworld_weather_set(overcast=1.0). Overcast sky = diffuse light: eliminates specular glint from direct sun that burns weapons/materials to white and hides detail (A6_SR2M finding 2026-06-17). DO NOT usetime_multiplier=0before pending animations (freezes sim). - Playbook depending on mod type (below).
- Report with evidence.
PLAYBOOKS (pass/fail criteria by type)
All start from spawn. world_spawn(type=<classname>, pos=[x,y,z]) → PASS if ok and without
unknown_type/spawn_failed; save actual pos for subsequent steps.
Static object / container / building
- Load: spawn ok (above). FAIL → classname does not resolve (mod not mounted: absolute
!Workshoppaths, see dayz-test-ingame; orCfgPatchesdoes not register). - Visible + textured + winding: orbit camera (≥4 poses — front/side/back/overhead) with
camera_set+capture_screenshot; inspect each PNG. PASS = object visible (not invisible), without missing textures (no solid magenta/white/black), plausible proportions, without inverted faces or holes (winding). Hole/missing-face from one angle and solid from opposite = inverted winding. Before failing a model on textures, look at the vanilla terrain and sky in the same frame: if they are flat too, the client did not load its textures and the frame says nothing about the model (dayz-test-ingamereferences/session-findings.md, "H2R import (2026-10)"). - Collision:
scene_raycast(from_pos, to)(defaultmethod="rvproxy": LOD intersection in theintersectmode) aimed at the object in each ofgeom,viewandfire: the battery below for a small object or an item, multi-point for a building (walls, corners, floor). PASS = rays that should hit yieldhit=truewithobject_type/object_classof object. No hit where it should hit = ViewGeo/FireGeo missing or improperly resolved (LODs). (Changed 2026-10-03: this bullet asked for rays "from ≥2 angles", which do not tell the causes below apart.) Ray battery and its controls (added 2026-10-03; the battery of
dayz-p3d-audit"Absolute winding check" rule 6, fired on DayZDiag 1.29.163709 on 2026-10-02, dayz-mcp run 606a5dbb) [EXACT][CLAIM-MCPV-COLLISION-BATTERY]: nine rays per mode.- One vertical ray down the object's axis. On a solid box whose collision spans its own axis,
like the measured kit box, a LOD recentred by a missing
autocenter=0(dayz-p3d-auditkiller #3) still sits on that ray, so a miss there is not the recentring. On an object with gaps or several separate parts the shifted collision can leave the axis: check that the ray crosses the collider both where it is modelled and where the recentring would put it. - Four horizontal rays through the middle, one from each side, and two more heights on one side.
- Two rays from inside the object outward: a sound object answers
entry 0, exit 1at distance 0.
Fire the same battery, in the same run, at two controls: an object from the same PBO and load path, which separates the model from the build and the loading, and a vanilla object of the same physics layer (
physLayer;WoodenCratesets none and takesitem_smallfromInventory_Base,DZ/data/config.cpp:3152; for a static, theLand_Container_1Aohcontrol under WHAT IT DOES NOT COVER). In that run the LFPowerGrid kit box took 0 of 27, while the same PBO's logic-gate kit andWoodenCratetook 21 of 21 each (the battery without the two extra heights). The battery locates a miss; it does not name the cause. When the suspect is the winding, only a pair that changes the winding alone decides: the kit's paired run in rule 6 did; the Rule 12 pair, which also turned the Visual LOD and the normals, supports the cause without deciding it.- One vertical ray down the object's axis. On a solid box whose collision spans its own axis,
like the measured kit box, a LOD recentred by a missing
- Picking an item up proves nothing about its collision (added 2026-10-03; vanilla 1.29 and
bridge source read, the pickup seen in the same run) [EXACT][CLAIM-MCPV-PICKUP-NO-RAY]. Vanilla
targeting casts
RaycastRVProxyfrom the camera, 5 m, with the defaultObjIntersectView(4_world/classes/useractionscomponent/actiontargets.c:211-219,3_game/global/dayzphysics.c:88), and at a camera pitch of −45° or lower it also takes the objects of a 30°, 3 m cone and scores them by their distance to that ray, with no hit on them needed (actiontargets.c:286-287,:444-445,:730-735). The take action's target condition then checks only the distance to the object's position:ActionTakeItemToHandsusesCCTObject(4_world/classes/useractionscomponent/actions/interact/actiontakeitemtohands.c:13,4_world/classes/useractionscomponent/targetconditionscomponents/cctobject.c:10-22), and its other conditions (takeable, not being placed or deleted, attachment state, room in the hands;actiontakeitemtohands.c:31-41) do not look at collision either.action_usebuilds its world target from the nearest object of the class, withcomponentIndex -1and the object's position as the cursor hit, and casts no ray (MCPClientBridge.c:3769,:3783,:3861). In that runaction_use(ActionTakeItemToHands)put the kit box in the player's hands while all 27 rays of its battery missed it. Read collision fromscene_raycastagainst a same-layer control.intersect="view"is the cursor's mode, but the bridge casts a 0.05 m sphere when the radius is 0 (MCPBridge.c:2496,:2528-2531), where the vanilla ray has none. The cone path is read in the source, not measured: no MCP verb sets the camera pitch. - Physics and player collision (added 2026-10-02, measured on DayZDiag 1.29.163709)
[EXACT][CLAIM-MCPV-COLLISION-PROBES]:
scene_raycast(method="bullet", radius=0)castsDayZPhysics.RayCastBulletin the server's physics world, restricted to the layers BUILDING, DOOR, VEHICLE, ROADWAY, TERRAIN, ITEM_SMALL, ITEM_LARGE and FENCE (MCPBridge.c:2983-3004); the defaultrvproxyintersects the LODs instead. Count a hit only whenobject_typeis the target and the position is where its face should be: a vertical ray through a box without collision returnshit=1on the terrain. A miss alone does not prove the object has no physics shape (its layer may be outside the mask), and a hit does not mean the player collides (theWoodenCratebelow). For player collision the run teleported the player 4 m from the object's centre and calledplayer_move(to=<4 m past the centre, on the far side>, speed="walk", hold_s=10), then read the end position withquery_player_state(server). Blocked meansarrived:falseAND the player stopped just before the face, after the full hold (about 0.36 m before it on that run's 2 m boxes);released_by:"hold"only says the hold expired, andarrived:falseanywhere else is INCONCLUSIVE. Free meansarrived:true(7.5 m in about 5 s); walk the same distance and bearing on open ground as the free reference. Every walk of that run went north nearly straight ahead (applied_angle_deg0, and 0.04 on one walk), so it did not exercise a steering angle or its sign. A walk-through alone does not prove missing collision geometry: a vanillaWoodenCrate(item_small, low) took every ray and did not stop the player, while aHescoBox(item_large) stopped it; that run did not separate the crate's layer from its height. - Axis-aligned test fixtures: try
rotation=64, then check the pose (added 2026-10-02, same run):rotation=0leaves the bridge'sRF_DEFAULT(512, the config's placement), which yawed anInventory_Baseprobe about 10 degrees (a side-face reading moved 0.22 m across 1.2 m of the face);rotation=64spawned the same box with its faces on the world axes (equal readings at both offsets), on flat concrete.scripts/3_game/ce/centraleconomy.cgives 64 two names,RF_IGNORE("object will spawn as model was created", :56) andRF_RANDOMROT(:62); the result agrees with the first comment and does not settle how the engine reads the value. Before trusting face coordinates, check the pose: two parallel rays at one height on a vertical face catch a yaw (unequal readings) but not a tilt, so also readscene_raycast(method="bullet")normals on two faces that are not parallel, such as a side and the top (a tilt about one face's normal leaves that normal unchanged), and check that each lies along its axis, allowing for the ground: the statics below read up to 0.31 degrees off the axis, the terrain about as much, the items on it. Read the fixture again a few seconds later. It may change an item's configured resting side; slopes and settling were not measured. Thenormalof a defaultrvproxyreply is not a face normal (added 2026-10-02, two runs) [EXACT][CLAIM-MCPV-RVPROXY-NORMAL]: the bridge copies the engine'sRaycastRVResult.dir(MCPBridge.c:2958), for a ray the "direction and size of the intersection" (scripts/3_game/global/dayzphysics.c:104), asenforce-script-reference("Surface normals") found on house walls. In this run and in run cf2d6bb3 it lay along the ray in all 190rvproxyhits on an object, the box yawed about 10 degrees included ((-1.963, 0, 0) and (-1.966, 0, 0)), and its length is the ray's path through the object: (-2.1, 0, 0) across a 2 m box is its width plus the 0.05 m sphere at each side.bulletreturns the hit face's normal (MCPBridge.c:2998): a unit vector in all 40 of its hits, up to 0.31 degrees off the axis on theHouseNoDestructstatics and on the axis on the items. (Corrected 2026-10-02: this entry read the pose from the returnednormallying along the axis, citing (-2.1, 0, 0); with the default method it always does.) An item can come out with its sides swapped, and none of these checks shows it (measured 2026-10-02 on DayZDiag 1.29.163709, dayz-mcp run cf2d6bb3) [EXACT][CLAIM-MCPV-ITEM-YAW]: one MLOD held two 2 m boxes, A (x -3..-1) inComponent01and B (x 1..3) inComponent02, and a twin had B in no component; each model was spawned, binarized and as MLOD, withflags=8389668androtation=64asHouseNoDestructand asInventory_Base(item_large). On the four statics the west box was A, as modelled: it answered as component 0 (Component01;Component02reads 1), and on the twins it was the only box that took rays. On the four items the boxes had swapped sides, as a turn of 180 degrees about Y leaves them: the west box answered as component 1, and on the twins every hit was component 0 on the east box while the west box took no ray. The boxes are symmetric in z, so these rays cannot tell that half-turn from a mirror in x. Parallel rays read equal on the items too, and the normals pointed the same way as on the statics, since the swap keeps every face on the axes; a fixture symmetric about its origin, like the single box above, cannot show it, andobject_inspectreturns memory points in model space (GetMemoryPointPos,MCPBridge.c:2240), not the pose. So give the fixture sides a ray can tell apart and check which answers where: one component per side, read from thecomponentofrvproxyhits (geom,vieworfire; the bridge leaves it 0 onbullethits,MCPBridge.c:2983-3004), or a collider on one side only. Put the parts off-centre in z as well to tell a half-turn from a mirror, and probe along z too: a quarter-turn moves parts that lie along x onto the z axis.telemetry_read(mode="object_at")returns the object'sGetOrientation()(MCPBridge.c:3137); that run did not read it. Not measured: whether the swap is fixed or random (all four items swapped; 64 is alsoRF_RANDOMROT), otherrotationandflagsvalues, base classes and layers, and the cause. - Standing on top: move the player before reading its height (added 2026-10-02, measured on
DayZDiag 1.29.163709 in a later run, on 2 m boxes) [EXACT][CLAIM-MCPV-STAND-PROBE]: a second
reading of the object's physics body next to the walk-into probe above, on its top instead of a
side face. An idle player is not a probe. After
player_teleport(pos=[x, <top + 0.07>, z], skip_clearance_check=true)the player kept the teleport height (y 340.6000) and did not fall, on a box top and above open ground alike: the server read it twice over 3-4 s (query_player_state), and above open ground the client's physics position (thestart_posthatplayer_movereports) still read it 15 s after the teleport (the worn-items section below saw the same after a teleport to y=160). Everyquery_player_stateof these probes read godmode on, the DayZ_MCP default; the run did not separate the bridge, godmode and the engine as the cause. A short move applies the fall: the run teleported the player above the centre of each top, calledplayer_move(angle_deg=0, speed="walk", hold_s=0.5), thenquery_player_state. Above open ground the player dropped to the ground (y 340.6000 to 338.459); on the boxes, threeInventory_Baseitems at least 4 m from their neighbours (two outward-wound, MLOD and binarized, and one inward-wound), the server's y matched the top to within 0.0001 m (spawn y + 2.000 m, wherescene_raycast(method="bullet")hit it) and its x, z lay 0.70-0.75 m from the centre, inside the 2 m top. Read the server's position after the move: with x, z still inside the top's footprint, y at the top's height there means the player stands on it and y at the ground means it fell through; an endpoint off the footprint, or any other height, is INCONCLUSIVE. Start above the centre of an isolated top (no other support at that height within the walk's reach, or the player can end on it), and run the same teleport and move above open ground as the falling reference. Keep the move short: 0.5 s took the player 0.70-0.75 m by the server's position read after the move, with the edge 1 m away; a top whose nearest edge is closer than that can be walked off in 0.5 s, and the endpoint check then reads INCONCLUSIVE. - Placement:
telemetry_read(mode="object_at", type=<classname>, pos=<spawn_pos>, radius=2)→found=true,pos~ spawn, reasonableorientation. PASS = not buried or floating (cross-referencepos.ywith visuals).
Item / arma
- Load + visible/textured/winding as above, with emphasis on proportions vs real-world reference and on post-import geometry (orientation, winding) — where generated/imported meshes tend to fail.
telemetry_read object_at→attachment_count,health01. Telemetry PASS = found + healthy stats.- Collision: as for a static object, the ray battery with its two controls. Picking the item up, by
hand or with
action_use, does not test it (static-object playbook, "Picking an item up proves nothing about its collision").
Vehicle (placement/structure only)
- Load + visible + collision + telemetry.
vehicle_enter(pos)→seated=trueconfirms seat.telemetry_read→engine_on_server,wheel_count,fuel_fraction. vehicle_enterSEATS player (placement/seat). To DRIVE: drivability ladder (§DRIVABILITY +references/acceptance-ladder.md) covers autonomous driving via owner-side verbs (vehicle_get_in_client/engine_set/vehicle_control/vehicle_telemetry).
CAPTURAR ARMA ALZADA / ADS (raise client-side) [VERIFIED-SR2M gate iter36→37]
To validate RAISED/aimed weapon pose (e.g. support hand grip) there is no player input
tool. Weapon raise is forced IN THE MOD, client-side — captured character is client's LOCAL
player, so server init.c override DOES NOT alter the pose client renders
(exact symptom: server log states raised=1 but capture shows weapon LOWERED).
Drop-in: a modded class MissionGameplay in a gate mod (declares a missionScriptModule in its
class defs, files[]={"Mod/Scripts/5_Mission"}; build -PackOnly so .c survives packing):
modded class MissionGameplay
{
override void OnUpdate(float t)
{
super.OnUpdate(t);
PlayerBase p = PlayerBase.Cast(GetGame().GetPlayer()); if (!p) return;
HumanInputController h = p.GetInputController(); if (!h) return;
h.OverrideRaise(HumanInputControllerOverrideType.ENABLED, true); // ENABLED persiste; ONE_FRAME parpadea -> idle
}
};Key takeaways:
- 3rd-person aim pose depends SOLELY on
IsRaised()(dayzplayerimplement.c:1726, AimingModel) → sustained raise is enough. DO NOT callSetIronsights(): forces ironsight camera which fights MCP free-cam, and server-side left weapon untextured. WeaponADS()(human.c:86) is INPUT flag without script override (human.c:234-255) → always 0 even if ADS works. Success signal =IsRaised()/ client log, NOTWeaponADS().- Keep
OverrideRaise(ENABLED)server-side (init.c) as well so server agrees. - Judge grip by hand at NATIVE resolution (crop orbit frame), never rescaled
contact-sheet. Grip mechanism + geometric parity: skill
dayz-animation-pipeline(references/weapon-in-hands.md).
TOOL → WHAT IT VERIFIES
| Tool | Verifies | Failure signal |
world_spawn | classname loads | unknown_type / spawn_failed → mod not mounted |
camera_set + capture_screenshot | render: visible, textures, winding, proportions | invisible / magenta / holes |
scene_raycast (default rvproxy) | LOD collision (Geometry/ViewGeo/FireGeo, by intersect) | no hit where it should hit |
scene_raycast(method="bullet") | a physics shape on the masked layers (static-object playbook) | no hit on the target where it should hit; a miss alone is not proof |
player_move walk + query_player_state | the player stopped by the object (static-object playbook) | arrived:true through it; a walk-through alone is not proof |
telemetry_read (object_at) | placement, orientation, attachments, health | found=false / buried pos |
bridge_status | peer liveness (gate) | last_poll_age_s=null → bridge down |
world_time_set / world_weather_set | reproducible scene for captures | — |
WHAT IT DOES NOT COVER (ALWAYS declare in report)
- Player and UI actions: open/close doors, interactive inventory, shooting, reloading,
menus. There is no generic player input tool. (Driving IS covered — ladder
§DRIVABILITY /
references/acceptance-ladder.mdwith owner-side verbs.) For a building, geometry/collision YES; doors NO → manual test. exec_enforcedoes not execute on the headless diag server (GATE4B-LIM, engine limitation like MakeScreenshot) — do not rely on it to "execute arbitrary verification logic".telemetry_readis exposed as-is (BUG-010/011/012, hardening pending): does not certify large JSONL fixtures or extreme ranges.- CONTINUOUS actions (with progress bar):
action_useSTARTS them but does NOT COMPLETE them (measured 2026-09-07, LFPowerGrid, ledger entryfb-20260907-184749-3fc1). Returnsok=1, started=1and the server effect never arrives:OnFinishProgressServerdoes not fire and the target remains in the world (entities_queryat 0.003 m after two attempts with 60 s and 45 s waits).key_pressis not the way out: it documents itself "not OS input, key-up, hold", and a continuous action needs SUSTAINED input. This immediately kills dismantling, deploying/deploy, and crafting, which is precisely how a mod moves money and persistent objects.- What CAN be affirmed, and it is not little: the CONDITION is evaluated for real, so
started=1means "valid candidate" and serves as a measure that the mod offers the action on that target in that state. Always verify it with a negative control that must come out red (an excluded target returnscondition_failed), orstarted=1proves nothing. - Corollary that bites separately: since deploying from a kit is a continuous action, there is no
way via MCP to create a truly persistent object —
world_spawnuses default flags and does not survive restart. Add thatdayz_test_stopdoes not shut down gracefully (the next startup prints... was not closed. Always shut down the server gracefully) and no test ofOnStoreSave/OnStoreLoadbetween restarts is conclusive via MCP today. If the result comes out "did not persist", that is the harness, NOT the mod: do not report it as a bug.
- What CAN be affirmed, and it is not little: the CONDITION is evaluated for real, so
class Doors(Building) doors do NOT open by MCP withoutdoor_index(measured 2026-09-07, LFSecure I-0/I-4, PBO L4):action_use(ActionOpenDoors, classname=<door>)returnscondition_failedeven with the player 1.4 m away, because the bridge builds theActionTargetwithcomponentIndex=-1andBuilding.GetDoorIndex(-1)(native,3_game/entities/building.c:17, "index of the door based on the view geometry component index") resolves no door.object_anim(source=<door source>, phase=1.0)answersphase=1, but the engine's door controller returns it to its "wanted" phase (re-readphase=0) andIsDoorOpen(index)stays false, so every action that needs an open door also returnscondition_failed. Withoutdoor_index, opening/closing, sound and sync were a MANUAL test (the user's F) and MCP could only verify "starts closed" (object_animreadsphase=0); see the update below.- Update (measured 2026-10-01, DayZDiag 1.29.163709, test building whose door component is a button on a moving piece) [EXACT]:
action_usewithActionOpenDoors/ActionCloseDoorsanddoor_indexDOES open and close the door even there.object_anim(SetAnimationPhaseNow) on the door's source still cannot hold a static pose: the door settles atajarwith phase 0.07 instead of the requested phase — doors cannot be posed statically. For the verdict, useobject_doorsand rays. setup_failedis a FALSE NEGATIVE for local instantaneous actions (IsLocal() && IsInstant():ActionTogglePlaceObject,ActionDropItemSimple; measured 2026-09-07): the bridge checksGetRunningAction()==nullright afterPerformActionStart, and an instantaneous action has already finished. The hologram appeared and the object fell to the ground in both cases. Rule: withsetup_failedon an instantaneous action, verify the EFFECT (capture,entities_queryof dropped object) before marking the action as failed.scene_raycast:entry=0is TERRAIN andnormalis NOT a unit normal (measured 2026-09-07 withintersect=fireandgeom): a vertical ray against the ground returnshit=1,object_type="",surface_type=cp_grass|cp_concrete2,entry=0,exit=0; against an object it returnsentry=1. Thenormalfield isRaycastRVResult.diras-is, which in line-object collision is "direction AND SIZE of the intersection" (3_game/global/dayzphysics.c:104): measured magnitudes 0.16-0.42. Its direction is the ray's own ("Axis-aligned test fixtures" above), so it says nothing about the face: never read it as a normal nor use it as a threshold (dot >= 0.9is never reached); a face normal comes frommethod="bullet". (Corrected 2026-10-02: this line said to use it only as direction (sign/axis).) A mod that filters!hit.entrydiscards natural ground (hologram that never snaps to ground: LFSecure I-1). (Qualified 2026-10-03: a ray that starts inside an object also readsentry=0: the inside rays of run 606a5dbb readentry 0, exit 1at distance 0 on the controls. Tell terrain by its emptyobject_type, not byentry=0alone;entry=1holds for hits from outside.)- Collision probe with vanilla CONTROL before blaming the mesh (measured 2026-09-07, LFSecure L3 -> L4):
if
scene_raycastdoes not hit a mod static, repeat the SAME ray (view, fire, and geom; from outside AND from inside) against a vanilla static spawned next to it (world_spawn(type="Land_Container_1Aoh"): flat walls,House,IsBuilding()true, also useful as a wall for holograms). Control HIT + mod MISS in all modes and from both sides = reversed collision convex components (inverted winding in Geometry/View/Fire: ODOL preserves them, engine does not see them); control HIT + mod HIT only from inside = reversed faces only in visuals. With the source winding restored (L4) the same ray gave the front face at +0.079 m and a wall of 0.42 m. The correction rule lives indayz-p3d-audit(0% agreement = discrepancy, not direction). (Qualified 2026-10-03: the two readings above name the likely cause, not a measured one; only a pair that changes the winding alone decides it, as "Ray battery and its controls" in the static-object playbook says.)
REPORTING
A report with: criteria table (criterion · PASS/FAIL/INFO · evidence), PNGs and telemetry/raycast JSONs as evidence, global verdict, and a "to manual test" section with what is not covered (doors, shooting, etc.). Context cost: each capture weighs ~25k tokens (~240-320 px) — batch orbit-pose captures and do NOT re-read a PNG except to verify something concrete (images inflate context quickly). Hard budget ~25k tokens/image: do not request higher resolution.
TROUBLESHOOTING
| Symptom | Cause | Fix |
claude mcp list → × Failed to connect | claude mcp list is NOT reliable: gives false Failed with the server operational. E4 contention on port 8765 (ExclusiveThreadingHTTPServer lock, fail-closed) was the PRE-broker failure mode | verify FIRST with ToolSearch (mcp__dayz-mcp__* tools available? → all OK, ignore Failed). Broker registration --client (since 2026-06-24) overcomes E4 contention: N sessions share the server without fighting for 8765, and orphan-guard releases orphans alone. If tools are truly missing: register in broker mode (--client) and open NEW session (tools load at startup) |
| tools do not appear in session | server was registered AFTER opening session | open a new session (MCPs load at startup) |
bridge_status.server_peer.last_poll_age_s = null | server bridge does not poll | check dayz_mcp.json in server_profiles + key; confirm @DayZ_MCP mounted (absolute paths !Workshop) |
client_peer … null (server ok) | client does not connect or client_profiles\dayz_mcp.json missing | BUG-009 (flaky autoconnection): larger -ServerWait / retry; seed client config |
version_state = legacy_blocked | --require-version ON against a bridge not sending ver= | deploy 4B PBO (sends ver=4~…), or register server without --require-version for that run |
| byte-identical captures between poses | grab caught a stale desktop frame (not render) | [EXACT] PrintWindow(hwnd, hdc, PW_RENDERFULLCONTENT=2) on the client's main window DOES capture the D3D game area — full scene in >20 captures even with another window covering it and without taking focus, as long as the Windows session is unlocked and the display is on (measured in game, DayZDiag 1.29, 2026-09-28). A black game area means the display is off or the session is locked, not that the route cannot read D3D. CopyFromScreen only works with the window in front, and a covered DayZDiag also drops to ~20 FPS; validate on a crop of the game area, not whole-frame variance (corrects the LL-247 entry of 2026-08-12) |
| tool timeout and then "zombie" commands on reconnect | BUG-024: timeout leaves command queued; bridge executes it on return | after a timeout, reconcile with bridge_status before continuing |
REFERENCES
dayz-test-ingame— build/deploy/launch (this skill composes it with-ExtraMods "@DayZ_MCP").DayZ_MCP_dev\tools\README-mcp.md— startup order + bridge troubleshooting.DayZ_MCP_dev\HANDOFF.md(LIVE-STATE) — server invariants, 11 tools, GATE4B-LIM, backlog._shared\dayz-conventions.md— L2 (LODs, ViewGeo/FireGeo, DayZ response format).- Precedent of camera+orbit capture: A6's
gate-mcp.ps1(window-grab by orbit, dayz-test-ingame finding 2026-06-17) — alternative batch sweep to agent driving tools.
VEHICLE: repeatable smoke recipe (added 2026-06-24)
Visual smoke of a CarScript vehicle driven by MCP, verified end-to-end on MercedesAMGLF 2026-06-24. Repeatable without re-deriving:
- Mission = stock
dayzOffline.chernarusplus(from DayZServer install), NOT a Phase 0 mount-probevoid main(): mount-probe spawns no player → client does not render the world and capture comes out black. Stock spawns player (CreateCharacter→CreatePlayer) → free-cam with rendered world. Pass it with-Mission "<...>\dayzOffline.chernarusplus". - Bridge seed in profiles generated by
dayz-test.ps1:dayz_mcp.json{"url":"http://127.0.0.1:8765/","key":"<key>","pollHz":5}(ASCII) in<Mod>_dev\_server\profiles\and..\_client\profiles\. The bridge reads it from$profile:(serverMCPBridge.c:125, clientMCPClientBridge.c:211); key =DayZ_MCP_dev\tools\.dayz_mcp.key. Seed BEFORE launch (the bridge reads config in its init, only once). Do not useinstall-mcp.ps1 -Register(re-registers broker mode--client). - Launch:
dayz-test.ps1 -Mod <Mod> -Mode all -Build -PackOnly -ExtraMods "@DayZ_MCP" -Mission "<...>\dayzOffline.chernarusplus" -ServerWait 240.-PackOnlymandatory in mods with.c(binarize drops them → NO_IGNITER). Call the.ps1via absolute path (P:\ is subst). - Readiness: stock runs CE (
InitOffline) ~1-3 min; during CEbridge_statuscomes out STALE (last_poll_age_sgrows even thoughversion_state=ok) — do NOT spawn there. Wait forconnected to serverin server'sscript*.logand forbridge_statusto return fresh (<1 s). Poll log host-direct with PowerShell, NOT bash Monitor (inherits bindfs cache, LL-142). - Smoke (grouped, R5):
world_time_set(year,month,day,hour=12,minute=0)(all 5 mandatory) +world_weather_set overcast=1.0→world_spawn <Class> pos≈[player+~10]→ wait 30 s (survives without native crash = LL-099 ruled out) →camera_set(cam_mode "lookat",cam_pos+look_at) +capture_screenshot+scene_raycast+telemetry_read object_at. Healthy telemetry =found=1,health01=1,velocity 0. - Flaky capture: grab sometimes catches loading screen or client's "Continue" menu overlay
instead of render. If it happens: wait for settle (~30-40 s in background — foreground sleep is
blocked in this environment) and recapture; use several angles. Top-down that clearly reveals proxy
(mis)alignment:
cam_pos=[carX+3, 16, carZ+1]look_at=[carX, ground+0.1, carZ]. ~25k tokens/image — batch captures, do not re-read a PNG except to verify something concrete. A frame flat everywhere, vanilla terrain and sky included, is not a flaky grab of the model: withhold its texture verdict, check that the client window has focus and capture again after settling, since textured runs have opened with 4-11 s of flat frames; if the scene stays flat, its visual evidence isSETUP_FAILand the run is relaunched (dayz-test-ingamereferences/session-findings.md, "H2R import (2026-10)"). - User's eye > capture when render is ambiguous: in MercedesAMGLF grab was flaky and user reading was reliable diagnosis of winding and proxy alignment. If there is a human in the loop, cross-check with them. (Case s3 2026-06-24: I was about to sign PASS on faint renders; user's eye caught proxy misalignment I could not resolve → correct FAIL.)
- SHARED SESSION. Port 2302 and Steam client remain single resources, but
exclusion is coordinated with the runbook's FIFO lease, not attributing processes nor evicting
other sessions.
dayz_test_runacquires and maintains exclusion; retainrun_idand terminate that same run withdayz_test_stop. NEVER kill a process to unlock the box: if state does not reconcile, retain the process and declare degraded shutdown. - "Wheels = sheet/flat disk" is NOT a proxy bug. A
world_spawnof a CarScript without attachments leaveswheel_count=0/attachment_count=0→ only hub/disk is visible, without tires. Expected in visual smoke; real wheels require attachments (physics phase), out of scope for smoke. legacy_blocked("poll did not include ver=") is usually INCOMPLETE INIT, not version mismatch (added 2026-06-28). Refines the homonymous row in TROUBLESHOOTING: with 4B PBO deployed, right after launching serverbridge_statuscan come outlegacy_blocked/last_poll_age_s=nullbecause the bridge has not yet completed the handshake; transitions tookwithver=4~…when server mission LOADS completely (~1-2 min). Do NOT redeploy nor re-register for that — wait and re-check. It is only a real mismatch if it stayslegacy_blockedwith mission already loaded (client connected, world rendered). Origin: SUB_BRZ Phase 5 2026-06-28.- Historical pattern superseded 2026-07-15.
cmd start/.batavoided a background job losing its child, but created a process outside the registered lifecycle. For any current smoke, use exclusivelydayz_test_run, retain itsrun_idand terminate only that run withdayz_test_stop. No unmanaged fallback exists to bypass the lifecycle guard.
DRIVABILITY + ACCEPTANCE LADDER (summary — detail in reference)
Phase 5 of DayZ-MCP project added and gated in-game owner-side verbs that DO drive the
car (client takes ownership and handles throttle/steer): vehicle_get_in_client(pos) (seats +
ownership), engine_set("start"/"stop"), vehicle_control(throttle, steer, brake, handbrake,
hold_ttl_s) (SUSTAINED control, fail-closed), vehicle_telemetry(), vehicle_release(), and
query_get_in_condition(pos, component) (server peer, diagnoses which of the 7 gates of
ActionGetInTransport blocks). On top of them runs the acceptance ladder rip→drivable:
ordered rungs R1 spawns → R2 render → R3 get-in available → R4 seated → R5 drives → R6
wheel direction, each reading in-game ground-truth and mapping its failure to a known fix in the
SUB_BRZ taxonomy (dayz-vehicles/references/). Reference orchestrator references/drive_ladder.py
(drives R1→R6, outputs verdict.json; does NOT apply fixes nor rebuild) + fixtures tests/test_drive_ladder.py.
Full detail (verbs, verified owner-side mechanism, preconditions, spawn placement,
R2.5 restore-gameplay, anti-false-green guardrails, ladder table, and failure→fix mapping) →
references/acceptance-ladder.md.
(added 2026-07-14) No restore-gameplay tool: for user MANUAL test, relaunch without @DayZ_MCP
camera_set ALWAYS suppresses player control (SuppressGameplay() -> PlayerControlDisable, see
§R2.5) and there is NO MCP tool that fires RestoreGameplay() — only internal gate drive_probe_client
does it, and it is not exposed as a verb/tool. Practical consequence: after an MCP smoke that used camera_set
(free-cam or static-cam lookat), the USER cannot take player control to test the car by hand
— their input remains suppressed and there is no verb to revert it on the fly.
Rule: when workflow passes from MCP smoke (automatic) to user manual test (driving, judging
feel/aesthetics), relaunch the game WITHOUT @DayZ_MCP (or at least with a client that never received
camera_set in that session). A clean client has normal control. If the client was already neutered:
reconnecting (Esc -> Disconnect -> Reconnect) recreates the player with clean camera/control; there is no tool
shortcut. Origin: SUB_BRZ s35 — time was lost looking for a non-existent restore tool and user had
to close the game and ask to relaunch normally.
(added 2026-07-14) Autonomous smoke: world_spawn takes engine [x, y_up, z], and capture with sleeping display = black frame (SP-060)
Dos caveats de smoke MCP autonomo verificados in-vivo (SUB_BRZ s32):
world_spawntakes vector in ENGINE order[x, y_up, z_north](MCPBridge.c:1638Vector(x,y,z)), but bridge connect-log prints player position as<x, z_north, y_up>. Passing the log triplet VERBATIM spawns the object at ~6 km altitude ->IsSpawnReady(2.0 m radius) is never satisfied -> job timeout +found=0, and engine auto-deletes the orphan (NETWORK (E): Will delete object ... outside world coords). Cost 3 timeouts in a row. Rule: convert<x,z,y>->[x,y,z]before everyworld_spawn/camera_set; after a spawn timeout, grep RPT foroutside world coordsBEFORE retrying (distinguishes bad-coords from slow-spawn). Verifiable withscene_raycastto terrain (gives real y_up).- Capturing with the display asleep = BLACK frame even though the client runs (on 2026-07-11 a locked session with LockApp also gave black frames; since 2026-09-14 the capture backend fails outright on a locked session instead, see point 4). Fix: bring the client window to the foreground + input wiggle (mouse / F15) before EVERY
capture_screenshot. Misleading symptom: it looks like "the mod's render is broken" and it is the compositor. - (minor) Spawn adjacent to player can fall INSIDE a building -> probe 3-4
scene_raycastto clear terrain before choosing pos. - [EXACT] A locked Windows session is a distinct capture failure (added 2026-09-14, SUB_BRZ s93): with
LogonUIalive,capture_screenshotdoes not return a black frame — it fails outright withcapture_backend_failedon every attempt while the game client stays alive and probing. Before budgeting captures in an unattended run, runGet-Process LogonUIfirst: with a locked session there is no host capture at all, for the MCP capture and for any window-grab script. (measured in game, DayZ 1.30.164014 Exp)
Origin: SUB_BRZ s32 MCP smoke (2026-07-11): 3 world_spawn timeouts with raw log pos + 2 black captures with LockApp; both resolved with the above.
(added 2026-07-14) Telemetry numeric gates: calibrate against MEASURED PHYSICAL FLOOR, not ideal fixtures (SP-061)
Invariant for any acceptance-ladder / numeric gate on in-game telemetry (car drive_ladder, spikes, future harnesses):
- A threshold calibrated with ideal mathematical fixtures is a gate that no real physics passes. The engine has a native noise floor (body dither between SetVelocity and reading; client network interpolator). Before setting a threshold: MEASURE the floor in a real run (metric p95 on real data), threshold = floor x margin (>=2x), and document the measured numbers next to the constant. Two convergent independent measurements (implementer + receiver, +-20%) validate the number. Case: LFHeli W0 batch1 - jitter gate at 0.5 m/s with real floor 4.3-5.1 m/s -> false NO-GO that cost an entire corrective pass; recalibrated gave GO with 2.2x margin.
- *The perceptual smoothness metric is frame-to-frame zigzag in METERS (2nd difference, |delta v_implied|dt) - NOT |residual delta vs ideal| per frame (autocorrelated; measures 2x real zigzag) and NOT implied-velocity in m/s (scales 1/dt: same process gives different floors at 30 vs 60 FPS and breaks A/B FPS cells).
- Startup transients (setup teleport + interpolator chasing it) are excluded with bounded warm-up in client score window (real transient 0.25 s -> warm-up 1.0 s = 4x margin), fail-closed everything else.
- (LFHeli X.5e/f extension) Relative STRUCTURAL checks also need an absolute floor, and frame-time mediaN is fallacious with bimodal distribution. A run at 250 FPS with bimodal frame-time (4 ms bursts + ~50 ms population) broke three relative thresholds: max-gap 3x median, dt-tol 30%*dt, coverage span/median_dt. Fixes: absolute floors with physical basis (gap 0.150 s; dt-tol 0.015 s) and coverage by mean of dt column (mean of t DELTAS is TAUTOLOGICAL: span/(n-1) -> ratio ~1 always - vacuous check, G3 pitfall).
Cross-ref dayz-vehicles (drive_ladder) y dayz-mod-workflow ("primer run real -> retune, no NO-GO"). Origen: LFHeli X.5d (2026-07-11), doble medicion convergente + selftest 31/31.
(added 2026-07-14) Telemetric gates (extends SP-061): float epsilon at boundaries + AGGREGATED temporal balance invariant (SP-063)
Two adversarial defects reproduced on an already double-sealed gate (Codex re-seal, confirmed by receiver):
- Temporal threshold comparators need float representation epsilon. A "boundary precisely tolerates" contract (gap 150 ms / deviation 15 ms) is violated in binary: accumulated deltas give
0.15000000000000002 > 0.15-> boundary false REJECT, and NOT deterministic. Fix:> limit + EPS(1e-6 s) in EACH temporal comparator, with N-1/N/N+1 boundary fixtures (149/150/151 and 14/15/16 ms). - Coverage by count x mean is an evadable IDENTITY by biasing the denominator.
rows >= K*span/mean(dt)<=>sum(dt) >= K*span: a SUSTAINED +50% biased dt column (under per-row floor) compensates for 40% lost rows -> fail-open GO on incomplete trace. Fix: AGGREGATED two-sided balance invariant|span - sum(dt[1:])| <= max(1%*span, 0.1 s)- for honest producer it is ~0 by physical identity; measured <=0.0001% of span in 9 real CSVs vs 9.98% in adversarial fixture. - (Meta) per-row floor + aggregated invariant are a mandatory PAIR: per-row catches isolated outlier; aggregated catches small sustained bias. A single level leaves an open flank - and fixture demonstrating it is COUPLING of two checks "closed" separately.
Cross-ref dayz-vehicles (drive_ladder). Origen: re-sello LFHeli X5EF (2026-07-11), F-01/F-02 con outputs literales + fixture cruzada 40%-drops + dt-6ms -> GO fail-open.
(added 2026-07-18 s37) Server-side conditioning of a custom car (real OnDebugSpawn)
vehicle_get_in_client executes OnDebugSpawn CLIENT-side = no-op under server authority
(symptom: misleading vehicle_fixture_ready=1 with wheel_count=0/fuel=0 in server
telemetry). To condition for real (authoritative wheels+fluids) without touching code:
vehicle_enter(pos) (server seat) and then raw enqueue
vehicle_drive {throttle:0.01, duration:0.5} — its PREP phase executes car.OnDebugSpawn()
SERVER-side (MCPBridge.c:2104-2112) and the 0.5 s micro-drive is negligible. NOTE:
vehicle_drive requires the SERVER-side seat (gives not_seated with owner-client seat).
vehicle_prepare_fixture takes any CarScript classname: an object of another class fails with
fixture_not_vehicle (DayZ_MCP_dev/addon/scripts/5_Mission/MCPBridge.c:1214-1280).
[EXACT][CLAIM-MCPV-PREPARE-FIXTURE-ANY-CARSCRIPT] Between 2026-08-18 and 2026-10-01 it returned
vehicle_fixture_ready=1 with wheel_count 4 on 15 other CarScript types, among them SUB_BRZ,
LFQuad2, Arma2Quad, Hatchback_02 and the vanilla CivilianSedan; three test variants of one
quad returned fixture_not_ready. (Corrected 2026-10-03: this said "vehicle_prepare_fixture does
NOT work outside the Mercedes (MERCEDES_AMGLF hardcoded in MCPBridge.c:835 and loopback.py:113;
open issue to generalize it)". The loopback copy of 2026-07-25 still had that check; the tools'
2026-08-16 release does not.) Raw /enqueue
requires {identity, lease_token} in body in addition to ?key= (identity/token come from
session_acquire). gear idx of vehicle_telemetry: 0=R, 1=N, 2=1st ... 7=6th.
Verified in-game SUB_BRZ s37 (wheel_count 0->4, fuel 1.0, complete kit, run B3 to 6th).
(added 2026-07-22) Bare spawn exposes undercarriage → looks like misalignment; condition before judging alignment
A world_spawn of a CarScript vehicle creates it WITHOUT attachments (wheel_count=0, attachment_count=0): wheels/tires are NOT there, so brakes (rotor + caliper — caliper is usually RED), suspension and hubs remain EXPOSED in the wheel arch and are seen "floating" with a gap. That is NOT misalignment: it is correct base geometry, symmetric and in place, contained within the volume the absent wheel would occupy.
Rules:
- A smoke with bare spawn is MATERIALS-ONLY — validates body paint/textures/winding, NOT undercarriage alignment nor "complete" look.
- To judge alignment/look WITH wheels: CONDITION the car (
wheel_count 0→4) withvehicle_enter(pos)+ server-side micro-drive (OnDebugSpawn; see note "Server-side conditioning (real OnDebugSpawn)" s37 above) and recapture with wheels on. - Faced with a part that "looks displaced": MEASURE before concluding (bbox/centroid/left-right symmetry + bisection vs backup + containment in wheel volume) — do not sign "broken" or "OK" by opinion.
Verified: SUB_BRZ 2026-07-22 — a "misaligned parts" scare turned out to be exposed brakes/suspension from bare spawn; forensics (Codex, py3d) measured symmetry ≤0.003 m, 0-movement bisection across 6 shells, and containment in wheel volume (<rip-import>\work\reviews\2026-07-22-SUB_BRZ-misalign-forensic.md). Cost 30 min of avoidable forensics. Cross-ref LL-209.
Rules promoted from lessons corpus (added 2026-07-27)
Promoted from AI/20_Knowledge/lessons-learned.md so they arrive via trigger instead
of depending on someone remembering to look them up. Each rule cites its originating LL-NNN;
the full entry (symptom, origin, evidence) lives there. Do not remove the citation: the
lessons-index.md index detects promotion by searching for that reference inside skills.
- LL-181 — Before blaming the mod for an automated FAIL, verify the actuator/bridge source and confirm the stimulus reached the subject. Run an equivalent delta control or manual test to distinguish a harness defect.
- LL-187 — If several defenses intercept the same failure, design a per-layer repro reaching its protection point. Demand each layer's specific signal; an aggregated PASS of “does not fail” does not prove all work.
- LL-202 — At the first anomalous error of a client-side verb, verify the client's PID, its RPT tail, and
bridge_statusbefore continuing. If peer died, extract minidump/evidence and document degraded shutdown; do not diagnose subsequent errors as harness state.
The daemon serves the code it loaded at STARTUP — a new verb does not exist until restarting it (LL-223, added 2026-07-29)
If you add a verb to the bridge (SERVER_COMMANDS / CLIENT_COMMANDS in loopback.py) and the tool
responds not_whitelisted, do not look for the bug in your diff: the daemon loaded that module
when it started and has not read it again. loopback.py and server.py are NOT sealed in
app.pyz —editing them takes effect without bundle rebuild— but that restarts nothing.
What deceives most: the tool does appear registered in your MCP client, because the client started after the edit. The visible half of the system confirms the verb exists while the deciding half remains with the old list.
Command check (source mtime vs startup of deciding process):
(Get-Item '<...>\dayz_mcp\loopback.py').LastWriteTime
$pid_ = (Get-NetTCPConnection -LocalPort 8765 -State Listen).OwningProcess
(Get-CimInstance Win32_Process -Filter "ProcessId=$pid_").CreationDateSource newer than process = that is the cause, stop looking at code. Measured 2026-07-29:
loopback.py 15:57:54 vs daemon started 14:18:42 (1 h 39 min offset).
Restart: Stop-Process on 8765 listener; client re-spawns it lazily on next
call and daemon_generation changes (thus confirming it is another process). Restarting the daemon
is declared safe (BUG-062b). Warn beforehand: momentarily disarms MCP tools of
other live sessions, so check session_status (owner/queue) first.
After restart there is a re-handshake window: first calls may return
version_blocked or peer_reconnect_flush. They are transients — retry, do not rediagnose.
Confirm with bridge_status that both peers have version_state: ok and low last_poll_age_s.
Sibling but distinct from sealed bundle trap: there app.pyz seals COPIES of modules
from the test lifecycle (dayz_test_worker.py et al.) and rebuild+CAS is needed; here there is no
sealing at all, it is enough for the process to be old.
Origen: DayZ_MCP, gate in-game de query_all_players (2026-07-29).
"Open mod UI without keyboard" chain VERIFIED in-game + three pitfalls of use (SP-292, added 2026-08-18)
Cycle 1 gate of DayZ_MCP (2026-08-17, run 28f2e26f, PBO BCA758A1…): full chain works end-to-end.
Measured recipe (LFPowerGrid + @DayZ_MCP; adapt names to mod):
dayz_test_run(project="LFPowerGrid", mode="all", extra_mods=["@DayZ_MCP"])→wait_for(log_matches, "OnStoreLoad SUCCESS")(77 s / 26 probes) →session_acquire_wait(purpose=…)(adopts run: checkadopted_run) →wait_for(players_at_least, 1). (rev. 2026-09-06: previous order in this recipe probedplayers_at_leastBEFORE adopting; that probe crosses the bridge and idle run fence rejects it withrun_not_owned— §COMPOSITION, "Bridge startup sequence".)world_spawn(type="LFPG_BTCAtmAdmin", pos=[x,0,z])~3 m from player →action_use(action="LFPG_ActionOpenBTCAtm", classname="LFPG_BTCAtmAdmin", radius=5)→started:1(lookup byType().ToString()works at runtime).wait_for(log_matches, "[BTCOpenResponse]", lookback_lines=200)— with lookback: response lands ~200 ms after trigger and with cursor "from now" it is ALWAYS lost (BUG-086, 2/2 timeouts with line already in log).ui_tree(path="BTCAtmRoot")— with empty path returnsno_menueven though UI is OPEN: a Dabs/ScriptView panel is a pre-created host, not aUIScriptedMenu. Pass root name of.layout(grep -oE 'FrameWidgetClass \w+' gui/layouts/X.layout | head -1).ui_set_text("EditBtcAmount", "1")with INTEGER (GetBtcInput()is int: "0.001" → 0 →ShowStatuswithout RPC) →ui_click("BtnBuyBtc")→clicked:1 handler=LFPG_BTCAtmView user_id=100(#ifdef DabsFrameworkbranch ofInvokeUiClickfires with Dabs loaded by another mod) →wait_for(log_matches, "[BTCTxResult]", lookback_lines=200)and readerr=.session_release→dayz_test_stop.
Coexistence pitfall (measured 2026-08-18 00:01): bridge_status said box FREE (peers null, no lease, no runs) and 40 s
later a navprobe run started OUTSIDE lifecycle and WITHOUT lease; a probe executed player_teleport and moved ITS player.
Before any mutation on "the first human": bridge_status.coordination.active, server_peer.last_poll_age_s
and Get-CimInstance Win32_Process -Filter "Name='DayZDiag_x64.exe'"; if there is a server polling that is not yours, do NOT mutate.
Verifier and bridge integrity: evidence, contract, and identity
The bridge measures and discriminates subjects; a green is only valid if the command received a response and the protocol preserves the identity it intends to isolate.
- Every negative criterion requires per-command liveness (LL-267). Before resolving "X did not occur", require a response attributable to that command, for example
answered(tag), not a previous response from the run. Without it, the case isINCONCLUSIVE, neverPASS. Publish the global response count and, if zero, abort early instead of printing a collection of verdicts. A denial case only passes after demonstrating that the subject could receive and execute the request. - Census keys before closing the wire (LL-315). Search each new top-level key in the serialized flat DTO and across all its consumers. Obvious names such as
state,status,error, ortypeare usually taken; if they collide, nest the payload under a feature-specific object —the measured case ended up as{ok, dialog:{state,…}}— instead of overloading the field. The gate reads the real shared contract; the producer's suite does not accredit forward compatibility. - A handshake mutation is global and authentication is not identity (LL-317, LL-319). Before changing a version constant or deploying the PBO/daemon carrying it, inspect processes and sessions currently loading that mod. If an outside game is present, do not publish: coordinate or revert; holding your lease does not prove no one else consumes the protocol. Furthermore, maintain two fields with distinct contracts: instance identity remains stable over the process lifetime and is not hot-reloaded; the key only authenticates and can rotate. Knowing the instance does not grant authority, and a shared profile token does not identify a process. Never reuse the secret as identity.
- A cache must preserve the discriminator (LL-320). Before caching fencing, authorization, rate-limit, or session resolution, write what property distinguishes the result and demonstrate that the key preserves that identity. Caching by instance allowed two processes to share a PID —the intruder received 100 out of 100 mutations and binding never switched to AMBIGUOUS—; caching the table for 50 ms hid the second polling peer —40/40 ticks without a single command—. Measure hits and cost under real pattern under load: in the measured case the useful rate was 0.0 and rest cost did not represent loaded regime. If the cache does not save measured work or merges distinct actors, it is eliminated.
Vehicle ladder — session requirements (added 2026-08-24)
- Verified canonical site: ladder with drive requires a point with >=150 m clear in the driving direction (protocol requested in ledger entry fb-20260824-025758-2509). The historical G0 site froze the vehicle (positional drivability, LL-359) and the end of its line blocked get-out against statics. Do not reuse sites without evidence from the session itself (surface_query + entities_query; scene_raycast in
viewdoes NOT see statics that stop a car). - Single process lifetime: the spawned daemon and its sequence die with the command tree that spawned them in this harness ("broke away" does not survive harvesting; adoption of an external daemon is also broken — fb-20260824-032050-9aed, LL-358). The entire run -> verbs -> teardown sequence goes INSIDE a single process (
AI/10_Projects/DayZ_MCP/lanes/2026-08-24/g0_full_abba.pypattern: preventive stop, run, wait bridge-ready, wait player, teleport with lease, work, dayz_test_stop).
Certified canonical site + instrumentation rules (added 2026-08-24 afternoon)
- Canonical vehicle site: NWAF
[4200.0, 0.0, 10650.0](certified 2026-08-24, PBO 28226C93B9B8, repo'sdocs/VEHICLE_TESTING.md): corridor 160 m x +-25 m north fully enumerated, drive delta_2s_xz 3.2 m, teardown verified. Re-certify withpython tools/g0_site_gate.py --pbo-sha256 <sha> --out <verdict.json>(game already running, bridge ready; SHA is mandatory). Alternates and degradation causes in doc. - entities_query ONLY with player inside the area (fb-20260824-123204-638e): far from
any player answers 0 or cap-128 (bimodal) and is NOT a visible error. surface_query is
globally reliable (static terrain). Corridors: 3 spheres r=65 with
count_totalas truncation indicator and distance check of the last row (nearest-first). - Canopy gate before ALL teleport to unverified coordinates (fb-20260824-115220-1bc1): scene_raycast geom y+30 -> y-5 must hit at <=0.05 m from the surface at the PLAYER and VEHICLE point; detects roofs, tree crowns, and water (hits the surface sheet above seabed with negative y).
Bridge v9 + certificado multi-agente (added 2026-08-24 noche)
- Bridge v9 (commit d73da6c; version gate requires daemon-PBO pair):
object_animandobject_inspectacceptobject_id(the one from world_spawn) and resolve against bridge registry - position-independent, reaches client-auth fixtures with replica at spawn.player_teleportrejects surface landings with covered column (clearance_blocked;skip_clearance_check=truefor interiors).entities_querybringsnearest_player_m+reliability(player_in_bubble | remote_unverified). - Measured caveat:
object_animwrite APPLIES (SetAnimationPhaseNow) butphaseof reply itself can lag by one tick (0 -> write 1.0 -> reply 0.0 -> next reading 0.599). Confirm with a subsequent reading, not with the reply. - Clearance probe ignores players (ignore='player'): a survivor standing in the column gave dy=1.671 and rejected the point (this is how x4300 was demoted in r13 by mistake).
- Long sessions: diag client dies after ~6 min without commands (client_not_polling). Runner with keepalive (e.g. vehicle_telemetry every ~45 s) for the duration of the session.
- Multi-agent certificate 3/3 (2026-08-24): Grok 4.6 (MCP-only), GPT-5.6 (codex), and Ox Alpha (opencode) drove spawn->fixture->seat->drive (100-163 m)->door by object_id->delete->release with minimal brief. The claim "agents from 3 families drive it" has evidence in lanes/2026-08-24/ma/ in the vault.
(added 2026-08-28) UI resolution sweep without reboot + window-grab<->engine calibration
Measured on flight F of sorter case (run dbca698d, ledger entry fb-20260828-160429-2899):
dayz_test_runwidth/height do NOT set client resolution (requested 1280x720, real viewport 846x461). Do not waste a reboot changing resolution.- Validated technique: SetWindowPos host-side (user32, SWP_NOZORDER) on live client
window +
ui_reload_layout-> viewport immediately re-measures, scenario and run are preserved. A sweep of N resolutions costs N reloads, not N boots. - Measured window frame (Win11): outer - client = 11 px left/right + 45 title + 11 bottom.
Fullres
capture_screenshotis 1:1 with viewport: engine_px = image_px - (11, 45). - The root of
ui_treewith proportionalsize 1 1IS the real viewport — use it as resolution oracle instead of trusting launcher requests. - Factor control: widgets with exact flags render at declared x (height/1080), positions included — if reference panel does not give that exact factor, calibration is wrong, not layout.
- TextWidget does not expose its text via
ui_tree(text_readable=falseby contract): to measure GLYPHS the instrument is the frame (fullres + crop + per-pixel measurement), never the tree. Tag cases INSIDE strings to recognize them in capture.
(added 2026-08-29, SP-350) THREE LAYERS that do not name each other: do not declare that a verb does NOT do something reading only Python side
The MCP server has three layers and none names the others in its own text:
1. the tool in Python `dayz_mcp/server.py`
2. HTTP ingress `loopback.py`, `session_coordination.py`
3. the bridge in Enforce `MCPBridge.c`, inside the gameMany tools are a thin wrapper ending in runtime.call_bridge(...). Real
semantics live downstream. That is why a lane opening only one layer believes
it has the entire system before it, and signs off on behavior it has not seen.
Measured on 2026-08-29: two independent reviewers made the SAME mistake the same night, on the same system.
- One reviewer declared the claim "
y=0snaps to the ground" ofworld_spawnFALSE, reasoning that Python passesposverbatim through_require_vec3without touching it. Premise was correct and conclusion false: snapping occurs in the bridge.MCPBridge.c:ValidateSpawnArgssetsvalidation.flags = ECE_PLACE_ON_SURFACEas default forflags=0, andIsAllowedSpawnFlagsdemands that bit in every accepted combination except one,ECE_CREATEPHYSICS|ECE_TRACE. Furthermore the vault already had the fact measured four times (dayz-control-plane-gotchas.md:pos=[7500,0,7500]->pos_real y=313.14). - Another reviewer declared that "no file produces
box_claimed" by searching for it indaemon.py. The emitter lives two hops further, insession_coordination.pyinsidebox_wait_touch, and is reached vialoopback.py.
Both opened files and cited path:line. Verifying is not enough: one must verify in
the layer where the behavior would live.
Reglas
- Before declaring that a verb does NOT do something, check if its body ends in
call_bridge. If it ends there, the only honest response isnot verifiable from Python side, and stating that one would have to openMCPBridge.cto decide it. - Before declaring that a field is not produced, do not trust the module where assumed
architecture would place it. Grep field name across ALL
tools/dayz_mcp/. - Before contradicting documented behavior, grep the vault. It is memory of MEASUREMENTS: contradicting a measurement requires refuting the measurement, not reading code from another layer. Takes five seconds.
- Useful asymmetry: to assert that something DOES happen, seeing it once is enough. To assert that it does NOT happen, one must have looked where it would happen.
When writing a review brief
If the lane's workspace contains only one layer, say so in the brief and require that everything
relative to the others goes to LO_NO_VERIFICADO. In the run originating this section
the brief did not say so, and both lanes filled the gap with inference instead of with a
declaration of ignorance. A well-placed "unverifiable" is worth more than a brilliant
and false finding, because the false one travels: by the time the refutation arrived, another session had
already applied a fix to something that was not broken.
(added 2026-08-29, SP-351) The box is shared: join the FIFO, do not wait for notice — and the oracle is the EFFECT
session_status's blocked_on is an ORDER, not decoration
With the box occupied, session_status returns literally:
"blocked_on": "DayZ test box; next: call dayz_test_run(..., wait_for_box_s=<n>) to join the box FIFO"Measured on 2026-08-29: an AUTONOMOUS NIGHT session read that line on each query for seven and three quarter hours and kept waiting for another session to notify it that it was releasing. The queue existed, was named in the response, and was not used.
When it was finally used: wait_for_box_s=600 -> active_run_exists with
hint: "stop it with dayz_test_stop(run_id=...)". Once again the next step served in the
response. And upon setting a 15 min deadline for the neighboring session, it released in two — it had spent hours
defending a fixture that had never been used (its own capture gave
frame_client_all_black, zero wiring activity on its server).
Rules:
- If a tool tells you how to unblock yourself, do it before waiting for anyone.
- In an autonomous session there is no "wait for notice". Either you join the resource queue or you put a deadline on whoever holds it. Waiting without a horizon is not courtesy: it is giving up the assignment. A horizon of type "when user finishes" is not a horizon: it is an open-ended dependency, and whoever offers it should release resource and request it again (relaunching takes minutes; a fixture is remade in two verbs).
The oracle is the EFFECT, never the response
ok does NOT mean success, and there are at least four different encodings coexisting.
Measured in-game on 2026-08-29, same tool, same response, opposite effect:
object_delete(999999999) -> ok:1, deleted:0 <- no borro nada
object_delete(<id real>) -> ok:1, deleted:1 <- borroVerified recipe, one oracle per verb:
| what you want to know | do NOT look at | look at |
| did it place where requested? | ok | surface_query(x,z).y against pos_real of world_spawn |
| did it delete anything? | ok | deleted |
| did it respawn? | ok / requested | query_player_state.pos BEFORE and AFTER |
| was the wait satisfied? | ok | satisfied |
player_respawn works headless, and ONLY from the death screen
Verified 2026-08-29 (run 02524f97): kill player with external damage
(world_spawn of live infected with flags=3108 next to them; falling does NOT work with an open
panel), wait for capture to go through its three phases —normal, desaturated with
blood (unconscious), completely black (dead)— and then player_respawn()
triggers the vanilla sequence ("Spawning in 8 s"). Measured: position jumped ~1,878 m.
Pitfall: on a LIVE player it returns exactly the same (ok:1, requested:1) and does
NOTHING — byte-identical position. The response does not distinguish both cases; position does.
key_press delivers a DIK to mission, is not operating system input
key_press(dik=1) returns delivered:1 and does not open the vanilla menu (ui_tree ->
no_menu). It is not a failure: its description states "a mission callback, not OS input", and
vanilla menu does not hang from OnKeyPress. It works for modded UIs that DO hang from there. Do not
use it as a substitute for system ESC, and do not declare the verb broken from that test.
Driving a client by MCP: three traps (SP-450, added 2026-10-01)
Measured on DayZDiag 1.29.163709 with one client on localhost [EXACT]:
- With the CF + Dabs Framework + VPPAdminTools stack loaded, a mod's
MissionGameplay.OnKeyPressdoes not receive the keys sent byinput_trigger; without that stack it does. - The client can come up in the pause menu: ESC does not close it,
ui_clickoncontinuebtndoes. Print(variable)prefixes the line withstring name = '…', and the CLIENT log cuts lines at 255 characters (the server log does not): print short expressions.
Visual smokes: bubble follows player, not camera (SP-082, added 2026-08-31)
- Before spawn, read
query_player_state.posand place the object next to the player. Free camera more than 1 km away does not force streaming: it may photograph terrain even if the server confirms the object by raycast. - After several
camera_set, treat as orphan camera any series of identical frames from different positions. There is nocamera_release; restart or reconnect the client before diagnosing the asset. - Some vanilla vehicles resist
object_delete. Always checkdeleted;deleted:0means it was not cleaned up. If this occurs, cleanup is manual by the user via VPP.
File-based driver embedded in the mission for server-side logic (SP-074, added 2026-08-31)
When the test needs mod APIs that have no MCP verb, use an Enforce driver in the init.c
of the workspace mission (<dayz-projects>\<mod>_dev\_server\mpmissions\<mission>\), not
exec_enforce. Resolve-Mission of dayz-test.ps1 prefers that mission over the template.
Validated pattern:
- Server-only driver class, started with
CallLaterat 250 ms. - File-based protocol write-once per sequence:
$profile:<caso>\cmd_<seq>.json->res_<seq>.json, read and written withJsonFileLoader. Never rewrite aseq. - A single boot per run: sequence lives in memory and resets to 1 on restart. Between runs, archive the entire directory via rename; do not move its contents with wildcard.
- Print
MARKin script log to delimit each window. Every operation failure is serialized inside itsres_<seq>.json; driver poll does not die from a failed case. - Mission scripts see classes of all loaded mods and compile server-side with
#ifdef SERVERvisible. Host runner only writes commands, reads results, and trims log between marks. Cross this pattern withdayz-test-ingame. - [EXACT] Archive the case directory on completion — always, not only between runs — and never execute a command that already has its
res_<seq>.json. Measured failure (2026-09-27, LFPowerGrid): a driver whose sequence reset to 1 on a stalecmd_1.jsonre-ran the bank-mounting command every ~1.5 s; half an hour later the world held thousands of duplicate objects and the user aborted the next session for performance. The log lines dismissed as harness noise were the signal: a repeatedOP seq=1means the command IS running again, and duplicated live device ids mean two entities share one id. Before filing a repeated orERRline as noise, write in one sentence what it would mean if true; if that sentence describes damage, it is signal. (measured in game, DayZ 1.30.164014 Exp)
Lease preflight, object telemetry, and zombie commands (SP-152, added 2026-08-31; corrected 2026-10-03)
telemetry_read(mode="object_at")reads any classname: the bridge matches it by exactGetType()inside the radius (DayZ_MCP_dev/addon/scripts/5_Mission/MCPBridge.c:3007-3051), and the loopback checkstypeonly for a non-empty string (DayZ_MCP_dev/tools/dayz_mcp/loopback.py:1349-1358). [EXACT][CLAIM-MCPV-OBJECT-AT-ANY-TYPE] On 2026-10-02 (DayZDiag 1.29.163709, run1c289781) it answeredtelemetry.found=1withpos,orientation,health01anddeclared_slots(the reply's top-levelfoundstays 0) for three types:KP_CharAB_V1(health010), the vanillaZmbM_SoldierNormal(0) andKP_CharAB_V3(0.7525).inventory_attachreturned the same telemetry block forSurvivorM_Francis. It reads only the fields the bridge fills, not a mod's script members or sync variables. (Corrected 2026-10-03: this bullet said "On the frozen platform,telemetry_read(mode="object_at")only acceptstype="MERCEDES_AMGLF": the limit is in the Python loopback, even though the Enforce bridge is generic. For another classname, plan diagnosis with server logs and user tests; do not promise object telemetry." The oldest copy of the tools on disk (2026-07-25) has thetype != "MERCEDES_AMGLF"check only in the loopback'svehicle_prepare_fixturebranch, whose arguments also carrymode="object_at", and itstelemetry_readtook any non-emptytype; which verb returned thebad_argsbehind SP-152 on 2026-08-02 is not recorded. The tools' 2026-08-16 release has no such check.)query_*, telemetry, raycast, and captures cross the bridge and requiresession_acquire. Onlydayz_test_run/dayz_test_stopmanage their own lease; do not extrapolate that management to other verbs. (rev. 2026-09-06: "requiresession_acquire" includes ADOPTING the run that launch leftRUNNING_IDLE; the grant ofsession_acquire_waitdoes so and declares it inadopted_run— §COMPOSITION, "Bridge startup sequence".)- A
world_spawntimeout leaves a zombie command: it may execute after losing theobject_id. Before retrying, reconcile the effect withtelemetry_read(mode="object_at")for that type at the spawn position:telemetry.found0 means none of that type is inside the radius when the read runs (the zombie command can still run later), and two or more of that type inside the radius fail withambiguous_fixture(MCPBridge.c:3031-3045; not exercised on a zombie spawn). Do not blindly duplicate spawn. (Corrected 2026-10-03: this said "if theobject_atcap prevents it, use logs plus user inspection"; there is no such cap, first bullet.)
inventory_give: one call does not equal one unit (SP-300, added 2026-08-31)
inventory_give(classname) uses CreateInInventory; a stackable item is spawned with its default
quantity, which may be the full stack. Never turn number of calls into units or into
nominal value. After provisioning, measure the actual quantity of the item with entity inspection or with
the aggregated field exposed by the mod, and calibrate against that measurement all thresholds for cost,
leftover, and cleanup.
HUD with FPV camera gate: camera_set cannot photograph it (SP-327, added 2026-08-31)
camera_set installs a scripted camera. A HUD that gates with
DayZPlayerCamera1stPersonVehicle.Cast(player.GetCurrentCamera()) hides under that camera; it is
correct HUD behavior, not a bug of the HUD nor of the MCP. Capture it via manual user freelook
or via a custom headless mode of the mod. Internal RestoreGameplay() restores
player camera, but there is no public camera_release: if the flow does not expose that restore,
reconnect or relaunch as indicated in the previous section.
A negative of ONE sample is not a negative (LL-398, added 2026-09-01)
While closing a gate, a material override was rewritten on a worn garment and the garment did not change. The garment was at health level 4. It was written as a scoped finding —"at health level 4, a new override never gets rendered"—, with the mechanism declared as not established and with two captures separated by minutes. It seemed prudent. It was false.
The two captures ruled out the RENDERING artifact, not the sample size of one, which was what the entire claim depended on. Repeating the observation does not increase experiment sample size; it only confirms that the observation was read correctly.
And the variable was confounded. Between the print that worked and the one that did not, TWO things changed: the health level (0 -> 4) and the moment of writing (right after a state change, versus an arbitrary instant). The result was blamed on the one being watched.
Before writing a negative in HANDOFF from this path:
- Count experiment samples, not captures. A repeated capture is n=1.
- Enumerate everything that changed between the working and non-working cases, and if there are two or more, the negative names neither until the matrix is crossed.
- Scoping the finding to one axis seems cautious and is yet another claim: the chosen axis may be the wrong one, and then caution points in the opposite direction of the real mechanism.
SP-124 — Free lease does NOT imply free box
session_status may return owner: null, empty queue, and claimable: true while a DayZ
server and client are alive, launched outside the managed lifecycle by another project
line and occupying port 2302. The lease speaks of the lease, not of the box.
Before considering the box free, read -mod= of living processes:
Get-CimInstance Win32_Process -Filter "Name LIKE 'DayZ%'" |
Select-Object ProcessId, CommandLineThe command line tells whose run it is and whether it loads your mod.
Corollary for build: the "no DayZ process" guard can be narrowed to the two conditions it actually represents —no running process loads your mod, and the target PBO opens exclusively— instead of skipping it or waiting for the other line to finish.
Cross-ref: dayz-test-ingame (same rule, launch side).
Lesson LL-494 — widen closed allowlists in readers before writers (2026-09-10)
When a config is validated against a closed option set and long-lived readers re-validate every request, order is: (1) add the new token to the allowlist in code, (2) ship that code to every reader, (3) restart those readers, (4) only then write the value. Writing first invalidates the whole file for old readers (daemon_provenance_conflict / fail-closed). Not every resident process is a reader — measure which ones read the file and when. Cheap check: run the validator in a fresh process right after write and revert on failure.
Driving bench through the MCP: the fixture outlives a dead run, get-in can seat the wrong car, old dumps need their reader (SP-423, added 2026-09-24)
Measured 2026-09-13 on a car-tuning bench (LFCarTune: 17 episodes, DayZDiag, NWAF concrete,
one vehicle_trace per episode) with the dayz-mcp build before d065b0e.
- A
world_spawncar outlives its run. Created withflags=0, a car whose run dies beforeobject_deleteis persisted by the server and comes back in every new run.object_delete(object_id)cannot reach it: the id belonged to the dead run. Create bench fixtures withflags=8389668: [EXACT][CLAIM-MCPV-FIXTURE-NOPERSIST]ECE_PLACE_ON_SURFACE(1060) |ECE_NOPERSISTENCY_WORLD(8388608),scripts/3_game/ce/centraleconomy.c:30,37. vehicle_get_in_client(pos)answeredokwhile seating the player in that orphan, 2 m away, instead of the car just created at 0 m, in two episodes. Only the trace'scar_typeshowed it. Two cars of the same class in range makevehicle_prepare_fixturereturnambiguous_fixture.d065b0emakes get-in take the nearest vehicle; keep the check anyway.- After that, get-in returned
not_seatedfor any car, also on a clean site and afterplayer_respawn. Only a fresh client fixed it. - The drive controller skipped gears. Automatic
ShiftUp()fired on every tick with rpm above 0.8 × redline: 1st to 4th within 100 ms at ~33 km/h, and no downshift.d065b0ewaits 0.3 s between automatic upshifts (AUTO_SHIFT_SETTLE_S, dayz-mcpaddon/scripts/4_World/MCP_CarScript.c). Launch figures recorded before that build are only valid below ~33 km/h. vehicle_tracedumps of that build carry 0/1 booleans and lack fields added later. The reader coerces them and requires those fields since dayz-mcp1f71983, so a current checkout rejects them asdump_invalid. Pinning the reader atd2f9dadreproduced all 16 episodes without a difference.- With that build, 7 cars in a row within one run, each deleted with the client seated, worked. What broke get-in was the orphan, not the car count.
Rules for a driving bench:
- Create the fixture with
flags=8389668. - Before the first episode,
entities_query(pos, radius=30)shows no foreign vehicle. - After
vehicle_get_in_client, the returnedtypeis the class you created; otherwise recordcar_mismatchand skip the episode. - Before analysing an episode, cross-check the trace's
car_typeagainst the class you asked for. - A
not_seatedthat survivesplayer_respawnneeds a fresh client. - An analysis of stored dumps pins the
vehicle_trace.pyrevision they were recorded with.
Would close the gap: a get-in with a foreign vehicle 2 m away on a build with d065b0e, and a
5-car run with flags=8389668 killed halfway, checking that nothing persists.
Gating a PR through an external executor: rewrite TOML with its escaping, check baseline↔PBO drift first (added 2026-10-02)
Two defects, measured in one aborted attempt to gate an external PR by temporarily repointing the source path in a gate executor's TOML config:
- A programmatic swap of Windows paths inside TOML edits TOML escapes, not path bytes. In a
basic (double-quoted) string every backslash is escaped (
\\). Writing single backslashes turns\Uinto an invalid unicode escape, and the parser stops withTOMLDecodeError: Invalid hex valuebefore anything is evaluated: the run died in preflight. A hash that matches the planned bytes does not prove the TOML parses. Load the result withtomllibin a fresh process and compare the decoded path with the intended one before handing it over: a single backslash beforetornparses fine, into a tab or a newline. Literal (single-quoted) strings take backslashes as they are. - Check the deployed PBO against the sealed baseline before planning the gate. The executor validated the deployed PBO against a sealed baseline hash. When they differ, the gate needs the owner's baseline, PBO and source aligned first; no repointing or rebuild resolves it. Abort path: preflight rejects → restore the TOML and the PBO byte for byte → freeze the evidence → escalate.
Source: one gate run aborted in preflight; the clean abort preserved the owner's PBO.
Comparing worn items, and getting an infected to attack (added 2026-10-02)
Measured in one run (DayZDiag 1.29.163709, the chiral in-game checks of the character and clothing routes) [EXACT][CLAIM-MCPV-WORN-MANNEQUIN]:
- No verb takes a worn item off the player. A second garment cannot go into an occupied slot,
player_respawnon a living player does nothing even afterplayer_godmode(on=false)(see theplayer_respawnsection above), andplayer_teleportto y=160 left the player hanging in the air with no fall. To compare two garments, wear the second on a survivor spawned withworld_spawn(type="SurvivorM_<name>"):inventory_attach(object_id=<its id>, dest="attachment", slot=...)works on it, and it stands facing north. It cannot be given an item to hold (slot="Hands"returnsattachment_create_failed), so read its sides from its facing. - An infected attacks only once it notices the player. One spawned with AI (
flags=8391716, the living-infected 3108 plusECE_NOPERSISTENCY_WORLD) behind a player standing still walked away. Spawned 2.4 m in front of the player, it attacked after a shortplayer_move(jog, 1.2 s), wind-up and swings, again and again. Godmode does not hide the player from AI: in diagm_CanBeTargetedDebugstarts true (4_World/Entities/ManBase/PlayerBase.c:402, 1.29). - In this run, infected spawned without AI did not stay healthy. With
flags=8389668three of them idled in place, but about 12 minutes later one custom and one vanillaZmbM_SoldierNormalreadhealth01 = 0intelemetry_read(mode="object_at")and lay on the ground, the third read 0.75. No log line explains it and the cause was not found; one observation, no AI/no-AI control. Capture static infected early, and re-read their health before a late capture.
Check which mod a run loaded before reading its verdict (added 2026-10-03)
dayz_test_run boots the registered project its project argument names (a name with no launcher
policy is rejected with bad_project) and reports succeeded for that run, whether or not it is the
mod you meant to test. In one run the project argument named another registered mod by mistake: the
server loaded that mod and
@DayZ_MCP with another mission, never the mod under test, and the missing compile errors of the
mod under test were read as a fix. A "defect measured in the engine" was written from it and had to
be retracted. Before you attribute a compile result or any verdict to the mod under test, read the
-mod= and -mission= of that run in its RPT: the procedure is dayz-test-ingame's "An in-game
observation is worth whatever the -mod= line of ITS run is worth" (SP-124 above reads the same
line to know whose box it is).
unapproved_debug_image on a host with a non-English culture: check the server's code first (added 2026-10-03)
On a host with a Spanish (es) culture, dayz_test_run(build=true) on a project whose build runs
AddonBuilder was seen failing with native_debug_gate_rejected:unapproved_debug_image on
2026-10-03: the .NET helper loads localized resource satellites
(C:\Windows\Microsoft.NET\assembly\GAC_MSIL\*.resources\v4.0_*_es_*; seen:
mscorlib.resources.dll). The MCP server's debug gate admits structurally valid GAC_MSIL resource
satellites since a fix of 2026-10-01, so the rejection depends on the code the running server was
started from. The reply on the wire carries no detail (kind, pid, image): read the rejected image in
the MCP server's stderr log (search for unapproved_debug_image), and restart the server on current
code before treating the rejection as a defect of the gate. It is never a defect of the mod under
test.
Source: written into the installed copy on 2026-10-03 by a session testing a mod through the MCP; ported here in English, without private names, and checked against the MCP server's code (its debug gate's satellite rule dates from 2026-10-01).
Audit the deployed PBO's file index, not just the source tree, before attributing compile failures (added 2026-10-04)
The build can pack untracked scratch files, so assuming the PBO holds exactly the source tree
misattributes a compile verdict read from the boot's log: know what the build actually packed
before a decisive boot. The PBO index is plaintext near the start of the binary. A
grep -a "scripts\\\\" <mod>.pbo finds the names, but it prints whole newline-delimited chunks
of binary, not one name per line, so it is not a file list. To read the index itself,
dayz-aviation's "Read the PBO index before extracting" describes a reader over the header, and
dayz-test-ingame lists the PBO and compares the packed .c count with the source's.
Keep experiments out of the packed tree: a rebuild packs the scratch files still in it, and without
-clear its -temp sync can serve stale source (dayz-pbo-build, "Temp-stale trap"), so after a
rebuild read the deployed PBO's index again before the decisive boot.
Limits: the index audit establishes WHAT is packed, never WHY a compile failed.
Source: written into the installed copy on 2026-10-04 by a session that spent two days on a mod's compile failures; ported here in English, without private names, with the listing claim corrected against a measurement on real PBOs.
A server HANG is bounded by log tails first: read script.log's tail, and attribute to a REGION, not to the branch you hypothesize (added 2026-10-04)
A boot that never finishes (1 of N driver snapshots, RPT frozen, process alive spinning at low CPU) is a verdict without a location. Before attributing the hang to a specific branch:
- Read both log tails of the run, and start from the one that carries the markers. In the
run this section comes from, the RPT tail did not locate the hang (it ended in world-streaming
warnings); the useful markers (mod
MARKlines,[VanillaPrune]-style stage messages, init-completion lines) were inscript*.log. The interval between the last printed stage and the first missing one is a CANDIDATE, not a location. Before turning it into a location, check in the source and in the run's own logs: (a) the order of the markers and that each one is mandatory on that path; (b) that they belong to the same flow and to this run; (c) that their emission is visible. A marker printed from a deferred callback (the file-based driver pattern above starts withCallLater) is not in the same flow as the init stages, so its order relative to them is not guaranteed. If any of these guarantees is missing, instrument the entries and exits of the stages before narrowing. A missing stage proves lack of OBSERVED progress, not where the server spins: "no prune-stage message after init" is a different fact from "spinning inside the prune loop". - Verify the candidate region's loop structure in source before suspecting it (descending loop with every branch decrementing; per-tick resolution capped; helper functions loop-free) — this eliminates the cheap suspects and moves the suspicion to the remaining ones (world-scan helpers, engine-side recursion/re-entry), without ever NAMING them as cause.
- Regression evidence requires a controlled comparison between revisions; a bisect inside one revision only localizes. For a code path that never executed in-engine under any revision (the instrument compiled for the first time the same day), the attribution to the newest change remains unproven: the hang may be a regression, a pre-existing bug surfacing for the first time, or an instrument artifact, and the path never having run before does not clear the newest change either. A scenario bisect within one revision (one sub-path toggled: crossing the delete threshold vs marking strikes only) yields a CANDIDATE SUB-PATH where the hang triggers, never evidence of a regression: the base revision may carry the same defect. Evidence of a REGRESSION comes only from a controlled comparison between revisions: the same scenario, one variable (the revision, or only the newest change reverted). The merge stays on HOLD until that comparison closes.
Source: written into the installed copy on 2026-10-04 by the same session as the lesson above, after a seed-and-prune strike scenario hung the server between init and the first prune snapshot; ported here in English, without private names, and corrected in the review of PR #105: the log observation scoped to its run, the interval between markers made a candidate until the source and the run's logs confirm it, and localization within one revision separated from attribution to a revision.
Driving real inventory moves: what the verbs reach, and a key-driven test addon (added 2026-10-24)
Measured on DayZDiag 1.30.164200 (managed runs of project DayZ_MCP with a server-side logger as an
extra mod, all but the first also with a test-only driver) [EXACT][CLAIM-MCPV-INVENTORY-MOVES-130]:
hands_takeanswerednot_reachablefor an item in the cargo of a world container (WoodenCrate,Barrel_Blue, open, player 1.5 m away) and for an item in theRB_LvL1_Plankslot of aLand_House_1M1(player 6.5 m and 12.7 m from its origin); other buildings were not tried. It worked for an attachment of an item lying on the ground (SparkPlugin aPowerGenerator) and for a container on the ground (WoodenCrateinto the hands).action_use(action="ActionAttach", classname="Land_House_1M1")with a plank in hands returnedcondition_failed; on aPowerGeneratorit attached the spark plug.- Of the inventory verbs of that build (
inventory_giveandinventory_attachcreate a new item,hands_taketakes to the hands,hands_attachputs the held item into an attachment slot of a world entity and was not tried in these runs), none puts an item from the player into a container's cargo or moves it between two containers. What did, in every case tried: a test-only addon whosemodded class MissionGameplay(#ifndef SERVER) maps keys inOnKeyPressto the client's own inventory calls on objects found withg_Game.GetObjectsAtPosition, fired withkey_press(DIK 63-67 for the moves, 68 for the combine, 88 for the half split, 87 for a split to the hands):PredictiveTakeEntityToHands(cargo to hands, building slot to hands),PredictiveTakeEntityToTargetCargo(hands to cargo, crate to crate) andPredictiveTakeEntityToTargetAttachment(hands to an empty building slot), and the stack callsCombineItemsClient(hands onto a stack in a crate or a building slot) andOnRightClick(split a stack in half). Each one reached the server as a real inventory command. Read the hands on the server (weapon_state): a client-sideno_item_in_handsfromaction_useis the client's prediction. The runs loaded no CF/Dabs/VPP stack (SP-450 above: with that stackOnKeyPressdoes not get the keys). Ship the driver as its own PBO and leave it out of the release. dayz_test_runstarts the server without-adminlog: no run left a.ADMin its server profile, so lines written withGetGame().AdminLogare not readable from the harness. Have the test build copy them toPrintand readscript_*.log(logs_since).- The player keeps what it carried between runs (gotcha 4 above). A plank left in the hands by the previous run turned the next take from a slot into a swap.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

