Skip to content
Back to skills

Dayz Characters

ASecurity

Import and author DayZ humanoids: infected/zombies, survivors and human NPCs on vanilla character bases. Covers retopo and rigging to OFP2_ManSkeleton, bind pose, weights, baked scale, UV/normal bake/textures, character LODs, config.cpp/model.cfg inheritance and in-game deformation. Use for custom zombie/infected/survivor/NPC, ragdoll, sandstorm PPE, "rig to OFP2_ManSkeleton", mesh explodes or stretches, spiky/pinched vertices, all weights on Pelvis, missing bone, giant infected, see-through ...

  • 16 stars
  • 0 votes
  • 1 copy
  • 5 views
  • Added October 2, 2026
ai-agentspythonrustgoshellawstestinggit

Works with

  • cli
  • mcp

Security analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add willy92wins/DayZ-Modding-Knowledge-Pack --skill dayz-characters --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dayz Characters?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dayz Characters
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/willy92wins-dayz-characters/badge)](https://www.skillsdirectory.com/skills/willy92wins-dayz-characters)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dayz-characters
description: >
  Import and author DayZ humanoids: infected/zombies, survivors and human
  NPCs on vanilla character bases. Covers retopo and rigging to
  OFP2_ManSkeleton, bind pose, weights, baked scale, UV/normal bake/textures,
  character LODs, config.cpp/model.cfg inheritance and in-game deformation.
  Use for custom zombie/infected/survivor/NPC, ragdoll, sandstorm PPE, "rig to OFP2_ManSkeleton",
  mesh explodes or stretches, spiky/pinched vertices, all weights on Pelvis,
  missing bone, giant infected, see-through body holes, foot sliding, wrong
  facing or animations not playing. Always invoke before rigging/configuring a
  DayZ humanoid. Compose with ai-3d-to-dayz, dayz-model-pipeline,
  dayz-animation-pipeline, dayz-pbo-build and dayz-test-ingame; use
  dayz-vehicles/dayz-aviation for those domains.
---

# DayZ Humanoid Character Modding

Custom humanoid characters in DayZ — infected (zombies), survivors, human NPCs — are built by
**reusing the vanilla character chain**, not from `EntityAI`. The hierarchy
(`DZ_LightAI → DayZInfected → ZombieBase → ZombieMaleBase / ZombieFemaleBase → variants`) gives you the
skeleton `OFP2_ManSkeleton`, every infected animation, and the AI driver **for free** through inherited
config — so the only thing you author is a new mesh. The real cost of the whole project is one step:
**rigging that custom mesh to `OFP2_ManSkeleton`**. Everything else is generic pipeline work delegated to
the skills below.

This skill is the character-specific layer on top of `dayz-model-pipeline` (generic geometry / LODs),
`dayz-texture-pipeline` (textures), `dayz-animation-pipeline` (the skeleton/anim deep-dive), and
`ai-3d-to-dayz` / `hunyuan3d-local` / `rodin3d-skill` (mesh generation). It is the humanoid counterpart
to `dayz-vehicles` (wheeled) and `dayz-aviation` (flight).

Scope: anything that walks on the player skeleton — infected, survivors, NPCs — generated by AI, modeled
from scratch, or imported from another game.
(desde 1.30 Exp: female military `ZmbF_SoldierNormal_Base` / `ZmbF_ArmyOfficer` / `ZmbF_eastSoldier_Base` and Nasdara `Zmb*_taki*` — `references/dayz-1-30-characters.md`.)

## PREFLIGHT

Gate real build work on `/dayz-preflight` (P:\ mounted, AddonBuilder, P:\Mods junction), per
`_shared/dayz-conventions.md`. Authoring config / rigging / baking offline does not need it; packing does.

**Before rigging a zombie/infected**: its canonical bind is the 95-bone `hermit_newbindpose.xob`, a SUBSET of
the player skeleton — NOT OFP2's full player bone list. Read THE CANONICAL-BIND INVARIANT (ZED bind) first.
(desde 1.30 Exp: that zed bind is unchanged; global bone indices are name-derived, not XML `index` values. [CHANGELOG] See `references/character-rigging.md`.)

## THE PIPELINE (who owns each stage)

| Stage | Owner | Notes |
|---|---|---|
| Generate / model the mesh | `ai-3d-to-dayz`, `hunyuan3d-local`, `rodin3d-skill` | multi-view + A/T-pose helps the rig; see CASE LOG |
| Retopo to low-poly manifold | `ai-3d-to-dayz` / `dayz-model-pipeline` | AI retopo leaves non-manifold — clean it |
| **RIG to OFP2_ManSkeleton** | **THIS skill** | the expensive, character-specific step → `references/character-rigging.md` |
| UV-unwrap + normal/AO bake (high→low→`_nohq`) | **THIS skill** + `dayz-texture-pipeline` | character-specific gotchas (conform misalignment, AI-high normals) → `references/character-uv-bake.md` |
| Texture `_co` / `_nohq` / `_smdi` | `dayz-texture-pipeline` | SubstanceToArma preset |
| Character LODs (Visual / Geometry / Fire / Memory / View) | THIS skill + `dayz-model-pipeline` | see CHARACTER LODs |
| config.cpp inheritance + model.cfg | **THIS skill** | inherit `ZombieMaleBase` → skeleton+anims+AI free |
| PBO / binarize | `dayz-pbo-build` | |
| In-game test | `dayz-test-ingame` (+ `dayz-mcp-verify`) | anims / foot-slide / collision / hit / spawn |

## RIGGING TO OFP2_ManSkeleton (the core — read `references/character-rigging.md`)

The single hardest step. Summary of the verified workflow ([LFInfectedBig ✓] 2026-06-24):

- **Skeleton** = `OFP2_ManSkeleton` (player + infected share it). The mesh MUST use the exact bone /
  selection names. In DayZ a "bone" in `model.cfg` is a **named vertex selection** weighted per-vertex;
  there is no shipped armature — `model.cfg` defines the skeleton, the `.p3d` carries selections+weights.
- **Armature source** = the official rig FBX `animation_rig_character.fbx` from
  [BohemiaInteractive/DayZ-Misc](https://github.com/BohemiaInteractive/DayZ-Misc) → "Rig and Animations".
  Blender-native FBX: armature with **114 bones (exact OFP2_ManSkeleton names)** + a weighted reference
  body `Male_body` in the **canonical bind A-pose**. Units are **centimetres**. Helpers (`*_Dummy`,
  `Weapon_*`, `EntityPosition`) import as **EMPTIES**, not bones → ignore them for body deform.
- **GOTCHA that silently ruins the bind**: before Blender bone-heat auto-weights, you MUST bake the scale
  into the armature (`transform_apply(scale=True)`). Scaling only the object/parent runs bone-heat at the
  rig's native scale against your differently-scaled mesh → **every vertex collapses onto `Pelvis`** and
  the rest of the bones get zero verts. Symptom: posing any limb does nothing.
- **Bind pose**: the mesh MUST ship in the canonical A-pose (anims are relative to that rest). If your
  mesh's limbs sit at a different angle, conform them to the rig (see reference) — a per-region rotation
  masked by the auto-weight value gives a crease-free shoulder.
- **Cleanup**: limit total influences to **≤4 per vertex** + normalize; target 0 zero-weight verts
  (zero-weight → pinched/spiky verts in-game). Disable deform on face/finger/eye bones for a body rig.
- **Blender deform-check = gross-motion sanity ONLY** (does a limb move at all; cross-midline weight
  bleed). It is a FALSE GATE for real deform — the engine skins against the real `OFP2_ManSkeleton`,
  not your rig-FBX armature (see the FALSE GATE section below). The deform gate is in-game/Buldozer.
  Bone-name mismatch → RPT logs `Bone X doesn't exist in skeleton OFP2_ManSkeleton` and that bone
  silently does not animate; a stray unnamed selection not in `skeletonBones[]` makes the mesh explode.

Bone catalog by zone lives in `dayz-animation-pipeline` → `references/player-skeleton.md`.

## THE CANONICAL-BIND INVARIANT — why a rig that looks fine at rest deforms under anims

[✓ in-game, LFInfectedBig S7 2026-06-24] The engine skins the mesh against the **canonical
`OFP2_ManSkeleton` bind** (the shared rig's bone positions), NOT a bind derived from your mesh. So the
inherited vanilla anims deform correctly only if your mesh matches that canonical bind in **three** ways:

- **Scale** — at ~vanilla size (the rig's `Male_body` ≈ 1.8 m). A mesh baked oversize (LFInfectedBig was
  2.277 m ≈ 1.27×) is bound against the 1.8 m canonical positions → limbs swing from the wrong pivots →
  gross deform under anims (not just foot-slide). Rescaling to ~1.8 m removed the catastrophic deform.
- **Orientation and handedness** — facing the same way as the rig, and not mirrored. A 180°-about-vertical
  error makes the character **face / walk backward** AND deform; a mirror makes limbs fling (next section).
  The `(x,z,−y)` transform shipped backward; `(−x,z,y)` turned it round but mirrored it. Both are det +1.
  The map that gets facing and side right is the det −1 `(x,z,y)` of dayz-model-pipeline Rule 12
  (corrected 2026-10-01; derivation in `references/character-rigging.md §6`).
- **Proportions / joint positions** — the mesh's anatomical elbow/knee/shoulder/wrist must sit at the
  canonical bone positions. An AI mesh has its OWN proportions; a UNIFORM-scale fit (`character-rigging.md
  §1`) preserves canonical proportions but cannot match a differently-proportioned mesh → bones land off the
  anatomy → limbs **overextend / stretch** under anims. Needs a **proportion conform** (§3), not just the
  arm-angle conform.

**The tell**: *fine at rest, breaks only when animating.* At the bind pose the skinned output equals the rest
mesh regardless of weights, so "looks right standing" proves nothing about the bind — only an anim (or a
deform-test posing real bone transforms, `character-rigging.md §5`) exposes a scale/orientation/proportion mismatch.

**Diagnostic** (offline, no in-game): per bone, distance from the bone head to the weighted centroid of its
verts; large distances (LFInfectedBig: LeftUpLeg 0.33 m vs 0.22 bone-len, hands 0.20 vs 0.06) = the bone
doesn't sit on the anatomy it drives → that limb will deform. (`_export/diag_bind_mismatch.py`.)

py3d transmits the per-vertex weights faithfully (the cross-side fix and the S7 fixes all propagated in-game)
→ Object Builder is not required for the export encoding itself.

### ⚠️ A ZED'S canonical bind is `hermit_newbindpose.xob` (95-bone SUBSET), NOT the player skeleton [✓ verified vanilla 2026-06-28]

Infected do NOT skin against the player rig. `ZombieBase` (and thus `ZombieMaleBase`, which your custom
infected inherits) carries an `enfanimsys` block — verified `DZ\characters\zombies\config.cpp:71-77`:
`graphname=…\infected\infected_main\infected.agr`, `defaultinstance=…infected_main.asi`,
`skeletonName="hermit_newbindpose.xob"`, `meshObject="…\z_hermit_m.xob"`. **`hermit_newbindpose.xob` is the
zed's bind pose, and it is a reduced subset.** Counting `<bone>` in `DZ\anims\cfg\skeletons.anim.xml`
(method: grep -c): **`hermit_newbindpose.xob` = 95 bones; `player_testing.xob` = 172 bones.** The 77-bone delta
the zed OMITS = the **entire face rig** (~40 `face_*`, `eyeleft/right`, lids, lips, tongue), the **finger**
bones (`pinky`/`ring`), **IK helpers** (`*handik`, `*forearmdirection*`, `righthandik_helper`), and all
**weapon** bones (`weapon_bone_01..06`, `magazine`, `trigger`, `bolt`, holsters, cameras).

- **Implication for a custom infected**: if your mesh carries weights on bones the zed bind lacks (face,
  fingers, IK), the engine resolves them wrong at runtime → deform, **including at idle** (the infected graph's
  default pose ≠ your A-pose). Conform/weight the mesh to the 95-bone `hermit_newbindpose` set, **or** override
  `enfanimsys` `skeletonName`/`meshObject` to `player_testing.xob` to restore the missing bones (the community
  fix for headgear/face stretch on zeds — Hoxdolum, DayZ Modders Discord 2024, mechanism confirmed vanilla).
- **Three bone catalogs — do NOT conflate**: the Enfusion `enfanimsys` `.xob` bind pose (zed 95 / player 172)
  is a DIFFERENT layer from the RV `model.cfg` `OFP2_ManSkeleton` (vanilla zombie ~159 bones, below). "Match
  the canonical bind" means the `.xob` the engine actually binds (`hermit_newbindpose`), not just the model.cfg
  skeleton. A mesh weighted to OFP2's full player bone list can still deform on a zed for this reason alone.
(desde 1.30 Exp: `infected.agr` remains the workspace index but is now Enfusion `AnimSrcGraph` text listing `.agf` subgraphs. Re-verified `enfanimsys` at `exp/characters_zombies/DZ/characters/zombies/config.cpp:50-57` (digest/skill `:71-77` was DeRap-shifted). Custom infected graphs: re-save the workspace in Workbench 1.30. Bone-index rule: `references/character-rigging.md`.)

### ⚠️⚠️ The Blender deform-test is a FALSE GATE — only in-game (or Buldozer) validates deform [LFInfectedBig S8 2026-06-24]

**The single most expensive lesson of this project.** Posing your imported rig-FBX armature in Blender and
measuring edge-stretch does NOT reproduce the engine's deform. S8 drove the Blender edge-stretch from **34× →
6×** across THREE rebuilds (re-weight, TPS conform, depth-preserving conform) while the in-game deform stayed
**unchanged and broken** (user: "sigue igual" — weird at idle AND animating) — a **3× false green**. Do NOT
trust the Blender armature pose-test as the deform gate; it cost ~12 offline iterations that produced zero
in-game improvement.

- **Why it lies**: the engine skins against the REAL `OFP2_ManSkeleton` from `DZ_Characters` (its bind /
  inverse-bind matrices + the skeleton your `model.cfg` declares), NOT the rig-FBX armature you posed in
  Blender. If your `model.cfg` skeleton hierarchy or the mesh's authored rest pose diverges from vanilla even
  slightly, the engine deforms wrong no matter how clean the Blender pose-test renders.
- **"Weird at REST / idle too" (not only walking) ⇒ bind / skeleton mismatch, NOT weights.** At the true bind
  pose LBS returns the rest mesh regardless of weights; if it looks wrong while barely moving, the engine's
  bind ≠ your mesh (model.cfg skeleton wrong, or rest pose ≠ engine bind). Re-weighting will NOT fix it.
- **Ground truth = a debinarized VANILLA character** (`DZ\characters\zombies\*.p3d` → external ODOL→MLOD converter).
  Its mesh IS authored in the exact engine bind pose, with the exact selection structure and a model.cfg that
  the engine accepts. **Copy its model.cfg skeleton verbatim** and conform/weight your mesh against ITS bind —
  do NOT hand-author a 114-bone hierarchy and assume it matches OFP2 (it didn't, here).
  (Scope: the debinarized vanilla is reliable for skeleton, named selections, model.cfg wiring and
  weights — NOT for visual-LOD vertex POSITIONS, which ODOL→MLOD conversion corrupts for character bodies;
  for positions use the `Male_body` of the rig FBX — see the addendum below / SP-034.)
- **Valid deform gates, in order**: (1) in-game spawn — the real one; (2) Buldozer (BI model viewer, real
  engine skinning). The Blender armature pose-test is good only for gross "does a limb move at all" sanity.
- **What DID hold up offline** (so the offline work isn't all waste): the winding gate `check_dayz_winding.py`
  (it held for the winding of the det +1 builds of that time, which in game turn out lit inverted; rewritten
  2026-10-02 for Rule 12, it now fails those builds on their normals, see OFFLINE GATE below and
  `references/character-rigging.md` Stage B), scale (f from
  armature span), orientation (T2), and **always LOOK at
  the REST SIDE/depth render** — a TPS conform to near-coplanar bone-midpoint targets flattened the mesh to
  paper (2D in-game); the flat side-view was in the render folder a whole build cycle before it was noticed.

### ⚠️ The mesh can be left/right MIRRORED vs the canonical skeleton — anims fling a limb back/up [LFInfectedBig S9 2026-06-25]

**The real root cause behind ~8 sessions of "arm bind" pain.** After making `model.cfg`'s `OFP2_ManSkeleton`
byte-match a vanilla zombie's (159 bones, `isDiscrete=0`, exact parents/order — which restored the leg
`leftupleg→propthighleft→leftleg` chain), the legs walked but the arms still flung back/up, equal at idle and
animating. The rest arms were a sane A-pose, so it was not pose/proportion — the whole mesh was **reflected in
X relative to the skeleton's left/right convention**: a `left*` selection's geometry sat where the canonical
`right*` bone lives, so the inherited anim drove it about a pivot ~2× a limb-width away → big lever → the limb
swings off even under tiny idle motion. AI/GLB meshes that went through a reflection (then had winding reversed
to render right-side-out) are prone to this, and a 180°-about-Y orientation fix can leave a residual reflection.
*Root cause, found 2026-10-01:* the reflection was the export map itself. Blender is right-handed and DayZ
left-handed, so every det +1 map (`(x,z,−y)`, `(−x,z,y)`) mirrors the mesh, and the face reversal it then
needs hides the mirror (dayz-model-pipeline Rule 12; `references/character-rigging.md §6`).

**The test (offline, decisive):** debinarize a vanilla male (external ODOL→MLOD converter), read the embedded
`Skeleton` bones + the visual LOD's per-vertex `vertex_bone_ref`, and compute each bone's vert-X relative to
the spine center. Vanilla puts `left*` on **+X**, `right*` on **−X**, consistent across shoulder/hip/arm (use
large-n bones; pelvis/hand centroids are noisy). Opposite side ⇒ mirrored. The conversion adds only
`boundingCenter` and reverses winding — it does NOT negate an axis — so ODOL-X == model-X and the comparison is
valid. (Gotcha: vanilla ODOL `named_selections` carry bone NAMES but EMPTY `selected_vertices`; real membership
is `vertex_bone_ref` pairs → `sub_skeletons_to_skeleton` → skeleton bone index.)

**The fix (two options; prefer the second):**
- **Swap L↔R selection names** in every LOD (`leftarm`↔`rightarm`, all sub-bones + fingers + the `l*`/`r*`
  memory points). Geometry/winding/normals stay byte-identical (confirmed-good rendering is untouched); only
  which bone each side binds to changes. Lowest risk, reversible. Leaves the mesh visually mirrored — fine for a
  symmetric humanoid; an off-center detail (a chest hole) lands on the mirror side (cosmetic).
- **Un-reflect the geometry**: negate X on all verts + reverse winding (and drop the swap). Makes the mesh
  properly handed. Bigger change (re-verify the winding gate + facing) but it can also clear a reflection side
  effect: single-sided faces culled from grazing angles the upright anim never reaches — e.g. a ragdoll corpse
  going invisible from one angle. Swap alone leaves the reflection, so that culling can persist; un-reflect
  fixes both at once. On a `(−x,z,y)` export with reversed faces, un-reflecting gives the Rule 12 positions
  and face order: `(x,z,y)` with the faces back in Blender order (Rule 12 also negates the normals). When the
  Blender source exists, re-export it that way instead of patching the p3d. The Rule 12 export this leads
  to was checked in game on a character (2026-10-02, `references/character-rigging.md §6`); the patch was not.

## UV + NORMAL/AO BAKE (read `references/character-uv-bake.md`)

Unwrapping the low and baking the high-poly detail into a tangent `_nohq` + AO is generic texture work,
but a rigged character adds gotchas that each cost an iteration ([LFInfectedBig ✓] 2026-06-25):

- **Bake from a PRE-conform proxy, not the conformed low.** The rig conforms the limbs (`character-rigging.md
  §3`); the high was never conformed → after rigging `low.dims ≠ high.dims` in those regions and a direct
  high→low bake is garbage there. Bake onto a proxy = shipped low's topology+UV with the pre-conform retopo
  positions (which align with the high); a tangent normal is pose-invariant so it applies to the conformed
  shipped low. Verify `proxy.dims == high.dims` first.
- **Do NOT `normals_make_consistent` the AI high-poly** (non-watertight → flips shells inward → black
  blotches). Use its original normals + `shade_smooth`.
- **Pre-fill the bake image with neutral `(0.5,0.5,1.0)` and `use_clear=False`** so misses stay flat, not
  black (a `(0,0,0)` texel decodes to an inward normal → renders black).
- **AO**: hide every mesh except the target (the coincident high self-occludes → dark AO) AND clear custom
  split normals + `normals_make_consistent` on the target.
- `_nohq` is DirectX **Y-** → green-invert the OpenGL bake (`dayz-texture-pipeline`). Triangulate before
  baking and ship triangulated so bake tangents == in-game tangents.
- Internal decorative geometry (ribcage): bone-heat fails on thin self-intersecting meshes → weight
  procedurally by height to the spine chain (or DATA_TRANSFER from the body, but strip arm/shoulder leakage).

The gate is a **lit preview of the low+normal vs the high**, plus numeric checks (UV stretch spread, %
black pixels = 0, AO surface mean ~200). Render and look (`blender-visual-review`).

## SCALING — runtime is broken, bake it into the mesh

"Make it bigger/smaller" has no config or script path: `SetScale`/`GetScale` are broken on entities
([T140705]) and the collision box does not follow; the Object-Spawner `scale` is static-objects-only.
The only route is to **bake the size into the mesh** on the same `OFP2_ManSkeleton`. Geometry + collision
LODs scale together. **BUT the engine binds against the canonical ~1.8 m bind (see THE CANONICAL-BIND
INVARIANT), so baked oversize scale does more than foot-slide — it DEFORMS the mesh under anims**:
LFInfectedBig at ~1.27× deformed grossly; rescaling to ~1.8 m removed the catastrophic deform [✓ in-game S7].
Small departures (≤~1.1×) may pass with only foot-slide / IK drift; a genuinely BIGGER animated character
that still deforms cleanly is **unsolved here** — it would need a custom skeleton with a scaled bind, not
baked mesh scale. Verify foot-contact / melee reach / door clipping / AI pathing in-game; if limbs distort
under anims, the size is fighting the bind.

## CONFIG INHERITANCE BY CHARACTER TYPE

Inherit the right base and the skeleton + anims + AI come for free via the inherited `enfanimsys` block.
Minimal infected pattern (verified vanilla, full detail in `dayz-custom-infected.md` §1):

```cpp
class CfgVehicles {
    class ZombieMaleBase;
    class MyZ_Base: ZombieMaleBase { scope=0; model="\MyMod\infected\myz.p3d"; hiddenSelectionsMaterials[]={...}; };
    class MyZ: MyZ_Base { scope=2; hiddenSelectionsTextures[]={"MyMod\data\myz_co.paa"}; };
};
```

| Character type | Inherit | Gives | Notes |
|---|---|---|---|
| Infected (male) | `ZombieMaleBase` | OFP2_ManSkeleton + infected anim graph + AI | [recipe ✓] community rule (PvZmoD): inherit `ZombieBase`, never `AnimalBase` |
| Infected (female) | `ZombieFemaleBase` | same, female proportions | |
| Survivor / human NPC | `SurvivorBase` / player chain | OFP2_ManSkeleton + player anims | `[TBD-verify]` — not yet built here; verify the base + clothing/proxy slots vs vanilla |
| Infected (female military, 1.30) | `ZmbF_SoldierNormal_Base` | female soldier AI | `ZmbF_SoldierNormal`, `ZmbF_ArmyOfficer`; `ZmbF_eastSoldier_Base` config-inherits `ZmbF_usSoldier_normal_Base` (`zombies/config.cpp:10376`) |

**THE WALL**: only **one** mod that modifies player/creature animations can be loaded at a time — loading
two crashes the client/server (Enfusion limit). A character that ships *new* animations conflicts with
every other anim mod on the server. A pure reskin that **inherits** the vanilla `enfanimsys` does NOT hit
this wall. Warn the user whenever a plan ships custom character anims. (`dayz-animation-pipeline` anchor 3.)

## CHARACTER LODs

- **Visual**: the rendered mesh (with the hole/detail).
- **Geometry (collision)**: must be **closed and convex** ([BI Validating Geometries]). A holed/concave
  torso is not convex → either convex-decompose into numbered `ComponentXX`, or (simple) keep collision
  **solid** and ignore the visual hole. Collision need not match Visual. Mass ≥ 10.
- **FireGeometry**: solid unless you want shots-through. With solid collision, bullets crossing a visual
  hole still hit — a design choice.
- **Memory / View / LandContact**: per `dayz-model-pipeline`. Use DayZ LOD resolutions, not Arma 3
  (`_shared/dayz-conventions.md` LOD table — ViewGeo `6e15`, FireGeo `7e15`).

## QUICK TRIAGE

| Symptom | Likely cause | Where |
|---|---|---|
| Posing the rig moves nothing / mesh won't deform | armature scale not applied before bone-heat → weights collapsed to `Pelvis` | `references/character-rigging.md` GOTCHA |
| Whole mesh explodes in-game | a named selection not in `skeletonBones[]`, or wrong skeleton name | RIGGING; check `model.cfg` skeleton |
| Spiky / pinched vertices | zero-weight verts or a selection with no weight | cleanup: normalize, fill weights |
| `Bone X doesn't exist in skeleton OFP2_ManSkeleton` (RPT) | bone-name typo (casing/underscore) | bone catalog in dayz-animation-pipeline |
| Limbs lag / float during anims | mesh not in canonical bind A-pose, or baked-scale drift | RIGGING bind-pose; SCALING |
| Anims don't play at all | not inheriting `ZombieMaleBase`/`enfanimsys`, or new-anim wall | CONFIG INHERITANCE |
| Bigger character, collision wrong | scaled at runtime instead of baked | SCALING |
| **Character faces / walks BACKWARD in-game** | det +1 export `(x,z,−y)`: it sends the rig front (−Y) to +Z; the bind faces −Z | re-export with `(x,z,y)` (Rule 12), not `(−x,z,y)`, which turns it round but mirrors it. `references/character-rigging.md §6`; THE CANONICAL-BIND INVARIANT |
| **Limbs overextend / stretch under anims, fine at rest** | mesh scale or proportions ≠ canonical bind | THE CANONICAL-BIND INVARIANT; proportion conform `character-rigging.md §3` |
| **Deforms wrong in-game but the Blender deform-test looked clean** | the Blender armature ≠ engine skeleton — the pose-test is a FALSE GATE | THE CANONICAL-BIND INVARIANT (FALSE GATE); debinarize vanilla + gate in-game/Buldozer |
| **Looks wrong at REST / idle, not only walking** | bind / skeleton mismatch (model.cfg ≠ vanilla, or rest pose ≠ engine bind) — NOT weights | debinarize a vanilla zombie, copy its model.cfg skeleton + match its bind |
| **Skeleton now matches vanilla but a limb flings back/up (idle AND animating), rest pose is a sane A-pose** | mesh is left/right **mirrored** vs the canonical skeleton — `left*` geometry sits where the `right*` bone is | debinarize a vanilla male, compare bone vert-X to spine center (vanilla `left*`=+X); cause = a det +1 export map; fix = re-export with `(x,z,y)` (Rule 12) or un-reflect (negate X + reverse winding, same result); swapping L↔R selection names only relabels the mirror. See THE CANONICAL-BIND INVARIANT (MIRROR) |
| **Custom infected/zombie deforms even at idle; mesh was rigged to the full player skeleton** | a zed binds to the 95-bone `hermit_newbindpose.xob` SUBSET (no face/finger/IK bones), not the player rig | conform/weight to the 95-bone set OR override `enfanimsys skeletonName`. See THE CANONICAL-BIND INVARIANT (ZED bind) |
| **Corpse/ragdoll goes invisible from a specific angle** | single-sided faces of a reflected mesh culled at grazing angles the upright anim never reaches | un-reflect the mesh (negate X + reverse winding; with the Blender source, re-export with `(x,z,y)`, Rule 12); a pure L/R selection swap leaves the reflection so the culling persists |
| **Whole mesh renders flat / paper / 2D in-game** | a conform/warp collapsed depth (Y) — e.g. TPS to near-coplanar bone-midpoint targets | conform in X/Z only, preserve Y; ALWAYS check the REST SIDE render, not just front |
| **In-game diffuse = "camo" / black-grey facet noise, persists without `_nohq`** | corrupt high→low bake (mis-aligned high/low) — the noise is in the `_co` DIFFUSE, not the normal map | open and LOOK at the baked `_co` PNG; `references/character-uv-bake.md` pre-conform proxy |
| **Green/grey/brown "camo" patches that SURVIVE replacing the `_co` entirely (uniform `_co` too) AND disabling `_nohq`** | an ENVIRONMENT/terrain texture in an rvmat stage with `uvSource="tex"` projects that texture onto the body UV. [LFInfectedBig S10] `dz\data\data\env_land_co.paa` (a landscape photo = DayZ env map) was in Stage7 with `uvSource="tex"` → painted the landscape over the mesh; survived 6 `_co` iterations because the fix is the rvmat, not the diffuse | **Audit EVERY rvmat stage** — an env/macro/detail map must be `uvSource="none"` (sampled by reflection), never `"tex"`. `ImageToPAA in.paa out.png` to LOOK at each stage's texture. The `_co`+normal Blender preview HIDES rvmat stages = FALSE GATE. First prove the mesh is clean with the CLAY-TEST (render `.p3d` geometry, NO textures) |
| **Solid, but dark on the sunlit side and lit in the shade (inverted lighting)** | first suspect: shading normals stored OUTWARD, as in the det +1 recipe (LFInfectedBig as shipped) or a Rule 12 export with its normals re-negated (base shading measured in game 2026-10-02 on an untextured client); rule out the material and `_nohq` too | check the stored sign with dayz-p3d-audit's absolute check (agreement near 0 % = normals against the winding); if they point outward, store them inward: Rule 12 / `py3d.blender_to_dayz()` negates them |
| **Textures on the INTERIOR / model see-through from outside (inside-out)** | the faces are not in the MLOD inward order: a det +1 map without face reversal (LFInfectedBig S6), or a det −1 map with one. Blender/Three.js previews are double-sided and HIDE this | export with Rule 12: `(x,z,y)`, faces in Blender order, normals negated. Reversing every face is right only under a det +1 map, and that map mirrors the mesh. See `references/character-rigging.md §6` |

## OFFLINE GATE — run before EVERY PBO (catches the inside-out bug without an in-game cycle)

```
python references/check_dayz_winding.py <source_mlod.p3d>
```
A double-sided preview never shows DayZ's single-sided culling, so an inside-out model (textures on the
interior) only surfaces in game, losing a test cycle, and normals stored the wrong way only as inverted
lighting. Run the gate after building the `.p3d` and before AddonBuilder. It reads every visual LOD in the
MLOD convention of dayz-model-pipeline Rule 12, which `py3d.blender_to_dayz()` writes: the vertex-order
cross product and the stored normals both point into the material.
- **Winding**, per closed shell (points welded, faces linked only through edges that exactly two faces
  share, `proxy:*` faces left out): the signed volume by winding must be negative, the production sign of
  dayz-p3d-audit "Absolute winding check". A positive shell is inside-out: `face.vertices.reverse()` on
  every face of it, the whole LOD when every shell reads positive. Never decide on the sum over the LOD,
  which can hide an inverted part. A shell whose faces disagree with their neighbours is made coherent first.
- **Normals**, per shell and corner by corner, read as that reversal leaves them: each corner normal against
  its face's vector area (the fan sum, right on a non-convex quad too); within 5° of the face plane it has
  no clear sign and is not read. A shell agrees above 90 % of its corners, a tolerance rather than proof of
  every corner: smooth-shaded exports carry corners against their face. Below 10 % in every shell they
  disagree, which with the winding right means normals stored outward: negate the normal pool and keep the
  faces, never reverse them on that reading; the gate counts the corners that agree now and that fix turns
  too, and withholds it when part of the LOD is not read. Anything else lists the shells, to read part by
  part, never a corner on its own sign: in a shell that reads cleanly, negate the normals of the faces that
  read against their winding and keep the rest; leave any other shell as it is, to inspect (py3d README,
  "Winding", step 3). A corner normal smoothed across a sharp fold can point against a face wound right.
  py3d `_pct_normal_agreement`, the absolute check's count, reads only each face's first corner; the gate
  prints it alongside, over every face of the LOD. *(Changed 2026-10-03: this read "Anything else lists the
  shells to fix corner by corner.", and the gate's fix negated each corner against its face; a flat
  tetrahedron wound right, with one area-weighted normal per point, has 6 of its 12 corners against their
  faces.)*
- **Not scored**: an open part (a sheet, an open tube) or a flat one (welded twins) has no volume sign; its
  faces are counted as not scored, for dayz-p3d-audit's visibility battery. A visual LOD with no closed
  shell is not measurable.
- **Exit** 0 PASS, 1 defect, 2 invalid input, ODOL or not measurable. A PASS with faces not scored says so on
  its own line. A PASS cannot see a mirror: the det +1 export with reversed faces and negated normals passes
  too; check chirality on an asymmetric feature (Rule 12, `references/character-rigging.md §6`).

[OFFLINE MEASURED 2026-10-02] `references/winding_fixtures/` holds the three MLODs of the Rule 12 in-game
probe byte for byte (`make_fixtures.py` rewrites them with py3d 1.8.0) and the correct one with its normals
negated; `tests/test_check_dayz_winding.py` holds the expected verdicts. The gate passes the correct
export and the mirrored one, both solid in game, and fails the inside-out one (reverse every face, then
negate the pool) and the outward-normal one (negate the pool). On the three LFInfectedBig builds of the
chiral check it passes the Rule 12 build (48 closed shells negative; 99.8 % of its 131,278 corner normals
with a clear sign agree, 99.5 % in the body, its lowest shell, with 219 against their face and 200 within
5° of it; the open ribcage tubes, 63.5 % of the faces, not scored) and fails the two with outward normals
on their normals (0.2 % of their corners agree, every shell below 10 %). Measured on a skinned character too (LFInfectedBig, in game 2026-10-02, chiral
check in `references/character-rigging.md`): the Rule 12 build is solid and lit like vanilla; the builds
with normals stored outward, which the gate passed before this rewrite, are lit inverted (base shading,
untextured client), and the shipped one is also mirrored. (claim: CLAIM-CHAR-NORMALS-INWARD-SKILL)
*(Rewritten 2026-10-02. The gate encoded the LFInfectedBig det +1 build, normals stored OUTWARD and
`cross·normal < 0`, and exited 1 on all three MLODs of the Rule 12 probe, including the one that renders
solid and reads correctly (`cross.normal_positive=1.00`, `normals_outward=0.00`), with fix hints that would
turn it inside-out; until then this section sent inward-normal builds to the absolute check. S6 confirmed
that build's winding only.)* Detail + why (incl. why NOT to compare against a debinarized vanilla) in
`references/character-rigging.md §6`.


**SP-290 (measured 2026-08-17):** exit 0 is not "safe to ship." The gate fails only on relative
winding (`cross.normal_positive` / `cn<=0.30`). It **measures** global inversion — it prints
`normals_outward[info]=0.00` and `NOTE normals look INWARD too (separate lighting issue): negate stored normals`
— but does not fail for it. Three fixtures from one source (`Crate\models\Crate_Wooden.p3d`,
9 LODs), gate run as shipped: intact (A) → exit 0, `cross.normal_positive=0.00`; winding inverted,
784 faces (B) → exit 1, `cross.normal_positive=1.00` (the named case — the gate DOES catch it);
winding AND stored normals inverted, 784 faces + 488 normals (C) → exit 0 `PASS`. That C state is
internally coherent, globally inside-out — the silent Blender Z-up → Y-up ship (`DZ-R` Rule 13;
WallLamp, Crate_Wooden). Do not ship on PASS; read the `[info]` line. Required of
`references/check_dayz_winding.py`: (1) `normals_outward == 0` (or below a threshold) MUST fail,
not annotate — combined with inverted winding this is the state the gate exists to block, not "a
separate lighting issue" *(2026-10-01: implemented as `NORMALS_OUTWARD_MIN = 0.35`, but it contradicted the
MLOD convention measured since: shading normals stored inward, dayz-p3d-audit "Absolute winding check"
(engine verdict 2026-09-07) and Rule 12 (in-game 2026-10-01), and it failed every correct Rule 12 export.
2026-10-02: replaced. The gate reads the direction from the signed volume of each closed shell and fails
stored normals that disagree with the winding; C's state, winding and normals turned together, fails on
its volume where its shells are closed (the probe's `mirror_c`, inside-out in game: +0.1086). See OFFLINE
GATE above)*; (2) three exit codes, not two — today `exit 1` is both "defect found"
and "invalid input". An ODOL `.p3d` (`a6\LMGs\M249\m249_new.p3d`) raises a bare `AssertionError`
and exits 1, indistinguishable from inside-out. Need: `0` PASS, `1` defect, `2` invalid/non-MLOD
with an explicit "this file is ODOL, debinarize first"; (3) keep the three fixtures next to the
check (`f0_fixtures/crate_{A_intacto,B_solo_winding,C_winding_y_normales}.p3d`, regenerable with
`f0_winding_v2.py`). Fixture C inverts stored normals to reach the internally-coherent
globally-inside-out state; it does not byte-reproduce an exporter that also mirrors coordinates.
*(2026-10-02: the fixtures next to the check are the Rule 12 probe's, `references/winding_fixtures/`; the
crate fixtures are not in the Pack.)*

## CITE-THEN-VERIFY

Character config and bone names are easy to half-remember. Before writing a class name, base, property or
named selection, grep vanilla (`P:\dz\characters\`, `DZ\characters\zombies\`) or the cited reference, and
keep provenance labels (`[LFInfectedBig ✓]`, `[recipe ✓]`, `[TBD-verify]`). The bone names MUST match
`OFP2_ManSkeleton` exactly — verify against the rig FBX or `player-skeleton.md`, never from memory.

## REFERENCES

- `references/character-rigging.md` — the full verified rig workflow (armature source, scale gotcha,
  auto-weights, conform-to-bind, cleanup, deform-test) with the Blender-headless script patterns.
- `references/character-uv-bake.md` — UV-unwrap + high→low normal/AO bake: the pre-conform-proxy alignment
  fix, AI-high normal handling, neutral-prefill misses, AO occluder isolation, internal-mesh weighting, with
  Blender-headless script patterns.
- `dayz-custom-infected.md` (vault `20_Knowledge`) — the infected recipe: config inheritance, scaling,
  the see-through hole, LODs, with primary-source citations. Deep config detail lives here.
- `dayz-animation-pipeline` → `references/player-skeleton.md` — the OFP2_ManSkeleton bone catalog by zone.
- `references/character-ragdoll.md` — 1.30 `human.ragdoll` (`RagdollDef`) on `SurvivorBase`.
- `references/dayz-1-30-characters.md` — 1.30 Exp humanoid delta (variants, agr/agf, PPE, skinning, surrender).

## CASE LOG (append one entry per real import)

### LFInfectedBig — giant infected with a see-through torso hole (2026-06-23 → ongoing)
First character imported. ~1.2× infected, pass-through chest hole (ribs+spine), reskin of `ZombieMaleBase`.
- **Mesh**: AI-generated (Rodin v2.5 multi-view + `TAPose` recovers an A-pose from arms-down refs) →
  scripted ribcage cavity → retopo 14k manifold (hole open) → high-poly 1M kept for the bake.
- **Rig** [✓ 2026-06-24]: auto-weights from the BI DayZ-Misc FBX; the **armature-scale-apply GOTCHA**
  cost an intermediate failure (all weights → Pelvis) before it was caught; arms were 28°/34° narrower
  than canonical → conformed via weight-masked rotation; cleanup to max-4 influences, 0 zero-weight;
  deform-test passed (6961/7000 verts move, smooth, cavity follows the torso).
- **UV + bake** [✓ 2026-06-25]: Smart UV + rotating pack (uniform density, bbox 96.8%, triangulated 14k).
  Normal high 1M→low baked via a **pre-conform proxy** (the S4 conform had opened the arms ~8 cm →
  low≠high); the rainbow/black-blotch/dark-AO failures all traced to alignment, AI-high `make_consistent`
  flips, black-cleared misses, and coincident-high AO self-occlusion — all fixed (`references/character-uv-bake.md`).
  Maps in the NEW UV. Ribcage decimated 95k→15.8k + spine-height weights (bone-heat failed on the thin
  geometry); deform-test dot 0.823. `pose_position` ships REST=bind; set `'POSE'` for the deform-test.
- **Export rig→`.p3d` + texture + LODs + config + PBO** [✓ 2026-06-25]: gate R15 closed. Export = **py3d
  direct** (now verified in §6, no longer `[TBD-verify]`): fractional per-vertex bone-weight selections
  (lowercase), transform `(x,z,−y)` det+1 DERIVED from the armature matrix, validated against a debinarized
  vanilla zombie (ground truth for selection names + Y-up frame + character LOD anatomy). *(2026-10-01: that
  derivation missed the handedness change; the map is `(x,z,y)`, Rule 12. See `references/character-rigging.md §6`.)* `_co` baked DIFFUSE
  high→pre-conform-proxy onto the new UV; Super rvmat (Stage4 AS `uvSource="tex"`, not "none"). 6 LODs:
  Geometry hull `class='man'` (not skinned), FireGeo skinned-to-bones + `dmgzone_spine` + `meatbones.rvmat`,
  ViewGeo, LandContact, Memory (points from bone positions). model.cfg = full OFP2_ManSkeleton hierarchy,
  self-contained. binarize + CfgConvert + AddonBuilder → 6.4 MB PBO, all offline-verified.
- **Inside-out winding fix** [2026-06-25, deployed + offline-verified; in-game re-confirm pending]: first
  in-game build rendered inside-out (textures on interior) — the AI/GLB+retopo source ships glTF-CCW winding
  that DayZ back-faces despite the det+1 transform. Fix = reverse every visual face. Built
  `references/check_dayz_winding.py` (FAILs the inside-out build, PASSes the fixed one) and wired it as a
  pre-PBO gate so this never costs an in-game cycle again. Corrected §6 (it wrongly said "no flip").
  *(2026-10-03: the inside-out render came from the det=+1 map, not from the source's winding. From
  Blender any det=+1 map mirrors the mesh, and reversing every visual face then hides the mirror
  (`references/character-rigging.md §6`; LFInfectedBig S6). In game on 2026-10-02 the shipped build, det=+1
  with its faces reversed, read its chiral marker mirrored and lit inverted; the Rule 12 build, `(x,z,y)`
  with the faces in their order and the normals negated, read it correctly and lit like a vanilla zombie.
  §6's "no flip", which this entry called wrong, holds under Rule 12's det=-1 map, and
  `check_dayz_winding.py`, rewritten for Rule 12 the same day, passes the Rule 12 build and fails the
  shipped one on its normals.)*
- **Scale + orientation fixed, residual = proportions** [✓ in-game S7 2026-06-24]: the deep investigation
  diagnosed the S6 breakage offline. Texture "camo" = a corrupt high→low bake (noise in the `_co` DIFFUSE,
  not `_nohq`; body UV was clean) → replaced with a clean procedural flesh. Deform had two systemic causes,
  both confirmed in-game: (a) **scale** — the 2.277 m (1.27×) mesh fought the canonical ~1.8 m bind → uniform
  rescale f=0.7901; (b) **orientation** — the `(x,z,−y)` transform was 180° about vertical → faced/walked
  backward → fix `(−x,z,y)` (also det +1: it fixed the facing and kept the mirror that S9 found).
  Both landed (faces forward, vanilla-sized, camo gone). py3d weights transmit
  fine (Object Builder not needed). See THE CANONICAL-BIND INVARIANT.
  (`_export/{rescale_1x.py,build_full_p3d_1x_t2.py,diag_bind_mismatch.py}`.)
- **S8 — re-rig attempt + the FALSE-GATE wall** [2026-06-24, deform still broken in-game]: tried to fix the
  proportion deform offline. Diagnosed the shipped deform: asymmetric auto-weights (LeftUpLeg 37 vs RightUpLeg
  166 verts) + 19% cross-midline, and `clean_cross_side` AMPLIFIED it to 21–34× (it keeps the dominant side
  per vert; on bled asymmetric weights it reassigns whole regions to the OPPOSITE limb). Replaced the bind
  with **envelope weighting** (distance-to-bone-segment + sidedness fade + a Pelvis→crotch column) — robust
  for the see-through cavity (data-transfer from a solid `Male_body` mis-weighted the tunnel interior → 100×
  spikes). A TPS proportion conform to bone-midpoint targets **flattened the mesh to 2D** (targets near-
  coplanar in Y); fixed with an **X/Z-only conform preserving Y**. Blender edge-stretch fell 34×→6× over 3
  rebuilds. **BUT all 3 in-game builds deformed the same** (user: weird at idle AND animating) → the Blender
  deform-test is a FALSE GATE (see THE CANONICAL-BIND INVARIANT). Root cause now scoped to a **bind/skeleton
  mismatch** (model.cfg skeleton or authored rest ≠ vanilla OFP2). Confirmed in-game still-good: scale,
  orientation, winding, 3D volume (X/Z conform). Reusable: envelope weighting, the `_conform/` diagnostics.
- **Next (decided with user)**: STOP iterating against the rig FBX. **Debinarize a vanilla zombie, copy its
  model.cfg skeleton + match its bind/selection structure**, re-author the custom mesh against THAT, gate on
  in-game/Buldozer only. Brief `LFInfectedBig_dev\reviews\2026-06-24-prompt-vanilla-zombie-reproduce.md`.
- Detail: `30_Sessions\2026-06-24-LFInfectedBig-s8-offline-gate-false-green.md`,
  `2026-06-24-LFInfectedBig-scale-orient-fixed.md`, `LFInfectedBig_dev\HANDOFF.md` (live state).

[T140705]: https://feedback.bistudio.com/T140705
[BI Validating Geometries]: https://community.bohemia.net/wiki/Validating_Geometries


## (added 2026-06-28) Custom character deforms ONLY under animation = proportion, and the vanilla-body escape

Case LFInfectedBig (AI zombie ~1.2x): correct shape at rest, limbs over-extend when animated
("Vietnam flashback" at shoulders/arms). Verified diagnosis chain:

1. **A ZED's bind is `hermit_newbindpose.xob` (95 bones), not the player's** — `enfanimsys` block of
   `ZombieBase` (DZ\characters\zombies\config.cpp:71-78). Quick triage: diff the mesh's WEIGHTED
   bones against those 95 (parse `DZ\anims\cfg\skeletons.anim.xml`). BUT **0% weight outside the 95
   does NOT mean the deform is solved**: LFInfectedBig had all 42 weighted bones inside → the deform
   was NOT missing bones nor Hoxdolum's facial fix, it was **mesh-vs-skeleton proportion** (bone
   heads 0.10-0.22 m off the anatomy). diff script pattern: see _judge\diff_zed_skeleton.py.

2. **When the mesh doesn't match the skeleton, conform/re-weight has a ceiling** — it attenuates the
   deform (~30-45%) but never removes it; the geometry stays far from the pivots. **Robust exit =
   Camino C: rebuild on a VANILLA well-rigged body** (zero deform by construction) and graft only the
   distinctive bits (torso hole, look/texture, baked scale). Use this when conform/re-weight stalls.

3. **To get an EDITABLE vanilla body with positions AND weights: use the `Male_body` of the rig FBX
   `animation_rig_character.fbx` (Blender), NOT the ODOL->MLOD converter.** The conversion (ODOL->MLOD)
   CORRUPTS visual-LOD positions of character bodies (non-human bbox, span ~2.5x height); the ODOL
   `vertex_bone_ref` carries the weights but the positions are garbage. The FBX gives correct human
   positions + clean vertex groups (verified: Male_body 7499v, bbox 1.4x0.28x1.8, 0 unweighted,
   <=4 infl). Extract: import_scene.fbx -> mesh whose name contains 'body' -> matrix_world@v.co +
   v.groups; bone heads from the armature (`bone.head_local`). See _judge\extract_fbx_rig.py.

4. **py3d: to READ selection weights from an MLOD use the constructor `py3d.P3D(open(p,'rb'))`, NOT
   `m=P3D(); m.read(f)`** — only the constructor populates `selection.points = {Point: weight}`. An
   old debinarized MLOD may keep selection NAMES but with `.points` empty (weights lost).

5. **Offline deform judge (canonical LBS) — useful but with a hard limit.** A hierarchical FK with
   bind = bone-head positions reproduces a mis-proportioned mesh's deform (validated against in-game)
   and, with a negative control of capsules centered on the bones, discriminates good/bad. BUT it
   assumes **identity-rotation bind**; it is NOT faithful to a rig with real bone orientations in the
   bind (a vanilla body) -> the FK chain explodes (edge-stretch 400x+). To validate a well-rigged
   body offline: FK with FULL bind matrices, or deform in Blender with the native armature (reconcile
   the bind-matrix after FBX import, which otherwise explodes). The faithful deform gate is in-game.
   Cross-ref: SP-030 (judge method + silhouette 2nd axis), SP-034 (converter gap), LL-169.

## (added 2026-07-14) Offline deform judge = hierarchical LBS with CANONICAL bind, not the Blender armature (SP-030)

The deform-under-animation of a mesh rigged to `OFP2_ManSkeleton` CAN be judged OFFLINE - no in-game build, no real RTM - with a CANONICAL hierarchical LBS: `v' = sum(wi * Gi * Bi^-1 * v)`, where the bind `Bi = T(head_i)` uses the REAL bone heads (the ones the engine uses: Memory-LOD pivots / a `bones_*.json` derived from the rig) and `Gi` is FK over the REAL model.cfg hierarchy (`skeletonBones[]`), rotating each bone about ITS head and propagating to children. This reproduces the real over-extension (bones far from the anatomy -> long arcs when rotated) that an in-game build takes ~hours to reveal.

This does NOT contradict the FALSE GATE section above: the Blender armature / Blender deform-test / single-pivot rotation ARE false gates (they re-bind to non-canonical positions). Two reliable gates and one not:
- FALSE: Blender armature / Blender deform-test / single-pivot rotation (e.g. `inv_hunch_test.py`).
- OK: hierarchical LBS with bind = canonical heads + model.cfg hierarchy.
- OK: **mandatory synthetic negative control** - a capsule dummy centered EXACTLY on each bone segment, weighted 100% to that bone. Under the same pose it must bend clean; if the judge also breaks it, the judge over-reports and is useless. Turns "reproduces the deform" into "discriminates good vs bad".

Second axis (from the head-to-head round): a conform judge needs TWO axes reported together - (1) reduced over-extension under anim (edge-stretch), (2) rest-pose silhouette preservation (mean radius of a region's verts to its dominant bone's head->tail axis, original vs conform). Optimizing only (1) lets silhouette distortion pass: in a blind A/B the user's eye caught an arm-thickness diff (orig 0.140 m -> A 0.124 vs B 0.113) that all three offline metrics missed.

CAVEAT (why this is doc-applied, not "verified"): this is an offline PREFILTER validated to DISCRIMINATE (negative control + the S8 false-green case), NOT quantitatively validated against the engine; the final fidelity gate is still the user's eye in-game, and the LFInfectedBig case that produced it is not yet in-game-confirmed. Reference impl: `LFInfectedBig\_judge\judge_lbs.py` + `judge_control.py` + `judge_compare.py` / `judge_metric.py`. Origin: 2026-06-28 deform gate.

## (added 2026-08-03, ArmorHneck, IN-GAME VERIFIED) WORN CLOTHING binds via skeleton name "DayzTemporarySkeleton" — NOT OFP2_ManSkeleton

**The single fact that makes custom worn clothing (ClothingTypes m/f) work.** A worn p3d whose
model.cfg declares `skeletonName="OFP2_ManSkeleton"` renders as a RIGID block anchored above the
player (floating ~1 m, rotated, not animating) — the engine treats it as a model with its own
skeleton and never re-binds it to the wearer. ALL vanilla worn clothing (verified: armbend_dynamic_m,
chainmail_m via ODOL `model_info.skeleton.name`) compiles with the skeleton named
**`DayzTemporarySkeleton`** — same 159 bones, same hierarchy/order as OFP2, only the NAME differs
(a placeholder the engine substitutes with the wearer's skeleton). Fix = rename the CfgSkeletons
class + every `skeletonName=` in the clothing model.cfg to `DayzTemporarySkeleton` and rebinarize.
The community 114-bone OFP2 template fails TWICE for clothing: wrong bone count AND wrong name.

Worn-clothing canonical frame (measured on chainmail_m/hoodie_m, anatomical components — a global
Z histogram is TOO COARSE and false-negatives a 180° flip): **-Z = chest/front** (hoodie front
drawstrings at Z -0.14..-0.07; chainmail pelvis/spine centroids Z -0.04/-0.08), +X = anatomical
left, +Y up, origin at feet. A mesh built facing +Z disperses into "exploded" rigid pieces when
worn (each plate transformed by its bone from a flipped frame). Fix = R_y(180°) (det=+1, do NOT
touch winding) + swap left*/right* selection PAIRS in every LOD.

Handedness of that fix (added 2026-10-01). Not touching the winding is right: R_y(180°) is a rotation.
The swap it needs shows that the received mesh was a mirror, though: it faced +Z with `left*` on +X.
ArmorHneck's received p3d was the det +1 map `(x, z, −y)` of the artist's Blender coordinates, and the
shipped one is `(−x, z, y)` of them (`ArmorHneck_dev\CLAUDE.md`, artist alignment 2026-08-04).
Rotation + swap fixes the binding and keeps the mirror, which symmetric plates hide; text, logos, buckles
and one-sided straps come out reversed. With the Blender source, re-export it with `(x, z, y)`, Rule 12,
and no swap. With only the p3d, the reflection depends on its state: the received `(x, z, −y)` p3d takes
`z → −z`; the shipped `(−x, z, y)` one takes `x → −x` with the L/R swap undone, as "un-reflect" in THE
CANONICAL-BIND INVARIANT (`z → −z` on it would face +Z again). Either one moves every point (memory points
included), reverses every face and reflects the normals the same way. Decide on an asymmetric feature in
game: chiral check in `dayz-clothing`, CANONICAL WORN FRAME.

Diagnosis ladder that isolated this (reusable, each step in-game-cheap):
1. Cross-matrix bisection: {armband class + vanilla worn p3d} vs {vanilla top class + custom p3d}
   — separates config-side from p3d-side in ONE config-only rebuild.
2. Compare `ODOL.model_info.skeleton.name` custom vs vanilla worn (the tell: vanilla has NO
   "ofp2_manskeleton" string anywhere; a compliant clothing ODOL says DayzTemporarySkeleton).
3. Structure parity checklist custom-vs-vanilla worn: skeleton 159 bones, per-vertex weight sums
   (vanilla = raw bytes summing 255), sub_skeletons palette, sections (tops use `camomale` alone;
   ONLY armbands carry camoMale_big_a/b + small_a/b complexion variants), empty Geometry LOD with
   autocenter=0 is the vanilla worn pattern.
4. The registration gate is a live spawn (`unknown_type` = config not loaded — also caught here:
   config.bin from CfgConvert did not register at all; pack clothing PBOs with config.cpp as TEXT).

Origin: ArmorHneck 2026-08-03 (third-party RAR, ~6 in-game cycles to isolate; dual research
Claude+Codex on debinarized vanilla). Full case: `ArmorHneck_dev\CLAUDE.md` + research workspace
REPORT.md (armorhneck_research_ws).

## DayZ 1.30 Exp (build 1.30.164014)

What changes: `SurvivorBase` binds `ragdoll = "DZ/characters/bodies/human.ragdoll"`; infected `.agr` is an Enfusion index over `.agf`; new female military + Nasdara infected classes; `MINDSTATE_COWER`; human `Skinning` drops `Lard`; sandstorm PPE via `DEF_DUST_PARTICLE_BREATH` / `DEF_DUST_PARTICLE_EYES`.
What breaks: 1.29 custom infected animation workspaces; `EmoteManager.ForceSurrenderState` (use `PlayerBase.SetSurrenderState` / `IsSurrendered`); recipes expecting human `Lard`.
Migration: keep inheriting `ZombieMaleBase`/`ZombieFemaleBase` (or a 1.30 female-soldier base); re-save infected `.aw` in Workbench 1.30; if you replace a survivor body, keep/override the `ragdoll` path; do not author correlative bone `index` values in `skeletons.anim.xml`.
Checklist: [ ] workspace re-saved [ ] ragdoll path on survivors [ ] no `Lard` in human `Skinning` [ ] PPE constants if the character wears masks/eyewear [ ] `MINDSTATE_COWER` if you patch infected AI.
Detail: `references/character-ragdoll.md`, `references/dayz-1-30-characters.md`, `references/character-rigging.md`.

Files in this skill

  • SKILL.md44.1 KB
  • references/character-ragdoll.md3.7 KB
  • references/character-rigging.md16.9 KB
  • references/character-uv-bake.md8.6 KB
  • references/check_dayz_winding.py6.7 KB
  • references/dayz-1-30-characters.md6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.