HumankindAssetFramework

Editor / Authoring Tools reference

The HAF Authoring Tools are a suite of Unity editor windows under Tools ▸ HAF. They are the bake half of HAF — you author custom content here, and the runtime plugin injects it in-game; the two halves talk only through the JSON pack registry. (The editor source lives in this repo under editor/ — the only copy. It moved out of the ENCReload Unity project on 2026-08-24 so the framework owns both its halves; ENCReload now consumes it as a package like anyone else.)

This page is the map — every tool, its exact menu path, what it does, and what it writes. For the deep, field-by-field workflows, follow the Deep dive links; this reference deliberately doesn’t duplicate them.

Installing them

Window ▸ Package Manager+Add package from git URL…:

https://github.com/sswelm/HumankindAssetFramework.git?path=/editor

The install is inert by design: automatic backups, the asset-delete guard and the console filter all default off in an installed package and on in the tools’ own development checkout (HafPackageContext decides by how the package is installed — git/registry is a consumer install, file:/embedded is the working copy). An installed package owns its own pack source (Assets/Pack/<YourPackName>/pack.json, starting empty) and deploys a derived copy under BepInEx/config/haf_packs/. It also ships the Blender helper scripts and the glbconv importer inside (Tools~), so a static .glb bake needs nothing installed beyond the package. See Getting-Started.md for the ordered path and Installation.md for install, verification and updating.

Check for Updates — Tools ▸ HAF ▸ Check for Updates…

Unity never reports updates for a git package, so the tools do it themselves: this asks the package’s own repository and either answers “up to date” or offers Update now, which hands the fetch to Package Manager (the same operation as its Update button). An installed package also checks once a day and prints one console line when a release exists — read-only, nothing sent, off via EditorPrefs HAF.UpdateCheck = false. Release notes are behind the package’s View changelog link. Details: Installation.md.

Where the tools write — the registries

Every registry is ONE file: the git-tracked project file is the source the editor reads and writes; the copy in the game’s BepInEx/config (auto-detected by ModelRegistry.ConfigDir) is a build artifact regenerated on every Save (units since 2026-08-19, districts and formations since 2026-08-20 via the shared SingleSourceRegistry engine — with pinpointed corruption and one-click recovery in each window). The runtime plugin reads the deployed copies on launch.

Registry Live path Repo backup Written by
Model registry (pack.json) config/haf_packs/<PackName>/pack.json Assets/Pack/<PackName>/pack.json Model Factory, Animation Lab, Resize Lab, Unit Retexture, Sound Studio, Global Era Lab
Districts (haf_districts.json) config/haf_districts.json (artifact) Assets/Databases/haf_districts.backup.json (THE source — historical name) District Factory
Formations (haf_formations.json) config/haf_formations.json (artifact) Assets/Databases/haf_formations.backup.json (THE source — historical name) Formation Override
Sound overrides (haf_sounds.json) config/haf_sounds.json project backup Game Sound Lab
Props (haf_props.json) Assets/Databases/haf_props.json Prop Lab (editor-side recipe store; the runtime reads the baked GUIDs, not this file)

<PackName> is ENCReload in the home project and is derived from the host project by HafPackageContext in a guest package install. pack.json is that project’s own pack (see Multi-Mod.md). Older docs/config call the base model file haf_models.json colloquially — the real shipped filename is pack.json in the pack folder. Baked assets (<name>_ModelMesh / _Skeleton / _Atlas) go under Assets/Resources.


Core model authoring

Model Factory — Tools ▸ HAF ▸ Model Factory

Author a static model (or the model half of an animated one): pick/create a resource, a target pawn, and a model file; tune geometry and shading; Bake → produces a Skeleton + Atlas and a registry entry. Key controls: target pawn, model file, Size, Strip parts, reduce-to-tris, height-UVs, winding fix, albedo brightness/saturation, keep-black, atlas size (256–4096), material mode, hide-donor, freeze-donor, re-spawn-after-load, embedded 3D preview. The preview stands the model on a true-size tile hex at the true in-game surface level — sunk or floating bakes preview that way (static entries show the shipped mesh; animated entries the rest-pose rig, the same faithful view as the Animation Lab). Boats stand on a water-blue hex instead of grass — detected from the pawn’s own Boat capability profile, never the name — so a waterline offset (hull below the blue) reads naturally. The forward arrow on the hex marks the in-game facing: dial the model’s nose/bow/barrel along it and the unit moves and fights the right way round. Orbit/pan/deep-zoom controls with a Center button that re-frames a lost view. Writes: pack.json (via ModelRegistry.Upsert) + baked assets, through ConfigFor → UniversalBaker. Deep dive: Factory-Manual.md.

Animation Lab — Tools ▸ HAF ▸ Animation Lab

The animation-only companion (docks beside the Factory): configures which clip plays and how for one model entry — state-driven idle/move/after/attack, deploy conversion, recoil/slam, turret & muzzle bones, hand props, donor sockets. Key controls: Clip field + range picker (▶) + Pick, state-driven toggle (idle-alt interval, attack repeats), deploy block (frames, strip-parts, recoil frames/step/return, slam), turret bone + aim axis, muzzle bone + offset, hand prop (bone/material/live rotation), animate-only bones, convert-raw-rig, fix-100×. The rest-pose preview stands the rig on a true-size tile hex at the in-game surface level (water-blue for Boat-profile pawns, forward arrow = in-game facing) — the faithful upright/grounded view for animated models — with orbit/pan/deep-zoom and a Center re-frame button, and a Play clip row that plays any baked role clip (Idle / Movement / After-move / Pre-move / Attack) textured and skinned right there — Pause, scrub, speed. That is the only view in which a subtle motion (a rolling wheel) is actually visible: the rest pose has no motion at all, and the raw-model ▶ picker is untextured. The deploy block’s Wheel bones (roll while moving) + axle axis / loop frames / degrees key that roll into the folded travel stance at conversion time (Movement clip = folded[1..N]). Writes: pack.json, same UniversalBaker.BuildAnimated pipeline. Deep dive: Animated-Models.md, Factory-Manual.md §16.

Vehicle Lab — Tools ▸ HAF ▸ Vehicle Lab

TRAILS — a split-trail gun’s deploy (2026-08-22, verified in-game). Mark a gun’s arms Trail in the part dropdown and the rigger gives each a bone hinged at its body end plus a second action, Deploy, that swings them open about the vertical — mirrored per side, with the direction chosen by testing which way moves the spade away from the centreline (so left and right open together whatever way the source faces). Dials: Spread (deg) and Deploy frames. That one rig then feeds the whole state machine: Idle stance Deploy[N..N] (parked, deployed), Movement Spin (wheels rolling, trails at their folded rest), After-move Deploy (opens on arrival), Pre-move Deploy[N..0] (folds before travelling). Trail is the artillery term — these are the arms of a split-trail carriage, each ending in a spade; Leg is deliberately left free for a walking mech limb. The preview picks the clip when a rig has more than one, so Spin and Deploy can each be judged on the turntable before a bake, and Checker paints a high-contrast skin so rotation is visible at all — an untextured wheel looks identical spinning or still. Checker is a deliberate material override: this preview validates moving parts, pivots, and axes, not the final atlas or in-game texture.

THE GUN COMES UP WITH THE TRAILS (2026-08-22). Two dials in that same section, both only live when parts are marked Gun:

THE THREE GUN ROLES — one elevation assembly, three meanings (2026-08-22). With recoil off, Gun, Cradle and Muzzle all weld to the single Gun bone because they elevate together about the trunnions. With recoil on, the generator adds a Barrel child: Gun + Muzzle ride it while Cradle stays on Gun. None of the three roles creates an independent bone by itself. What separates them is what else they mean:

role is in the breech→muzzle span? when recoil lands
Gun the tube itself yes — it defines the span the part that kicks back
Cradle the frame holding the tube — trunnions, recoil cylinders, the trough it slides in no the part that stays
Muzzle a separately-modelled brake / flash hider yes, and it pins the tip exactly rides the tube

The span exclusion is the point of Cradle: a cradle stops well short of the muzzle (26 units short on the M114), so folding it in would shrink the span and make Gun pivot’s fraction lie about where along the barrel the trunnion sits. On a model whose barrel already outreaches its cradle both ways, marking the cradle Gun gives a byte-identical rig — but the role is still the right home, because it is the split recoil will need.

Muzzle buys an exact tip: without it the muzzle end is the gun bbox’s far extreme, which a wide brake or a front bracket skews. That tip is the fire origin, and the run reports it gun-bone-local, in source units (scale by the bake’s size) in its DONE status — the value the Animation Lab’s Muzzle offset dial otherwise costs an iterate-and-relaunch loop to find. If the brake is modelled into the barrel mesh — as on the M114, where the tube tapers to 3.84 wide and then flares back to 5.22 over its last 6 units — there is nothing to mark; skip it. Marking the cradle as Muzzle is the trap: it pins the tip 26 units short and silently rescales the pivot slider.

RECOIL — the one motion that needs the barrel on its OWN bone (2026-08-22). Recoil (fraction of tube) is how far the tube kicks back when the gun fires, as a fraction of its own breech→muzzle length — a fraction, so the dial means the same distance-relative-to-the-gun on any model at any scale, and measured breech-to-muzzle rather than trunnion-to-muzzle so that moving Gun pivot doesn’t silently change what the number means. 0 = off, and off means the Barrel bone is never created: a gun that never recoils costs no bone and regenerates byte-identical to before the feature existed (verified — bone lists match the shipped M114 rig exactly).

With it on, the Gun-marked parts move onto a new Barrel bone, a child of Gun, and the Cradle-marked parts stay behind. That is the split the Cradle role was created for. Mark no cradle and the whole assembly slides back together, mount and all — the Lab warns about it.

Two design points worth keeping:

Recoil is a TRANSLATION, and the clip bake is rotation-only by default — tick Keep bone translations on the entry or the bake discards it and the gun does not move at all. That flag is the whole reason this can be an honest slide rather than the far-pivot RecoilArm rotation trick deploy_convert was forced into. Assign Recoil to the Attack clip.

THE FIRING CYCLE — proven numbers (2026-08-22, verified in-game). “They allow a reasonable time to aim, fire, recover and reload.” The elevation timing that came out of tuning the M114 is now the shipped default, so a new gun starts here rather than from scratch:

Animation Lab value what it buys
Raise over 1 s the gun is laid onto the target — and the shot WAITS for it
Hold after firing 1 s stays up through the recoil, then settles
Lower over 1 s eases back to the resting angle

Read as a rhythm: one second up, one second holding the shot, one second down — about three seconds from aim to stood-down, which leaves room for the recoil (0.67 s) inside it and reads as a crew working the gun. The angles stay per-model, because a howitzer and a tank destroyer want different envelopes: the M114 runs a 10° baked resting angle plus 35° of runtime lift, topping out at the 45° max-range pose on an 8-tile shot.

Raise over = 0 remains available and means track the turn — the elevation finishes exactly as the slew does, and the shot is not delayed. That was the old default; 1 s replaced it because a fixed, deliberate lay reads better than one whose speed changes with how far the unit happened to turn.

THE M114, END TO END — the worked example (2026-08-22, verified in-game). A towed howitzer rebuilt from a raw Sketchfab GLB on a Vehicle Lab rig, wheels + trails + gun, no converter. The shipped settings:

where setting
Vehicle Lab — parts l_wheel, r_wheel = Wheel · l_leg, r_leg = Trail · barrel1 (+ breech door, handle, lanyard) = Gun · cannon2 = Cradle · main = Body
Vehicle Lab — Spin 30 frames · −360° · axle AUTO
Vehicle Lab — Deploy Spread 28° · 20 frames · Gun pivot 0.25 · Gun raise on deploy 45°
Animation Lab — clips Idle/reference Spin · Idle stance Deploy[20..20] · Movement Spin · After-move Deploy[0..20] · Pre-move Deploy[20..0]
Animation Lab — flags Convert raw rig ✓ · Keep bone translations ✓ · Fix 100× ✗ · gunElevMax 0

The resulting 6-bone rig (Root, Wheel_00, Wheel_01, Gun, Trail_00, Trail_01) separates cleanly: Spin moves only the wheels, Deploy moves only the gun (45°) and the trails (28°). So it drives with the gun clamped and the trails folded, folds before it turns, and opens again on arrival.

Two dial choices worth knowing were not the measured-accurate ones. The real trunnion is pivot 0.4 — it lands within 0.8 units of the cradle’s centre — but 45° there drops the breech to Z 1.6, on the ground; 0.25 keeps the full 45° with ~11 units of breech clearance. And 45° itself is well above an M114’s parked elevation. Both were chosen by eye, and correctly: “when you see it you should think, ah that looks like a howitzer.” The bar is recognition at map zoom, and it can want a pose exaggerated past the accurate one. Measure to catch what is broken — geometry through the ground, a bone that never moves, a slice that holds the wrong frame — then let the eye pick the look.

This does not replace HAF’s runtime gunElevMax (Animation Lab ▸ Gun elevation — max) — that writes a BoneRotation slot, a channel the clip pose never touches, so the two compose: the clip sets the base firing elevation, the runtime adds the per-shot, distance-proportional lift on top of it. Dial gunElevMax against the raised base, not against level. The hand-converted M114 baked its elevation into the deploy clip too (deployReadyFrame), but out of necessity — a deploy-converted rig cannot carry authored bone motion at all (Animation-Pitfalls.md). Here it is a deliberate two-key authoring on a clean rig.

The Spin section leads with Enable spin animation — the master switch: off, the rig generates with zero wheel/rotor rotation and static tracks, keeping every bone, marking and dial for re-enabling (no more unmarking every wheel to still a vehicle). With no wheel/rotor/turret marked at all the section reads inert and grays out — except that Spin frames still floors the generated clip length (the one spin↔wave coupling, stated in the UI). Tracked vehicles then get Static tracks (no movement) — tread loops rigid, wheels still spin, far fewer bones — gating the tread speed/detail dials. Recipes that predate newer fields load them at defaults (absent JSON = the field’s default — the Lab never guesses a config the recipe doesn’t state) and say so: since 2026-08-20 the load status names the features the recipe predates (“recipe predates: wave rock, spin switch — loaded as safe defaults; Save to modernize”). The Edit existing dropdown leads each entry with its last-modified stamp, newest first, so the one you worked on yesterday is obvious. The recipe round-trip is also gated: Tools/check_handlists.sh fails the push if any Recipe field isn’t both written by Save and restored by Load (the canoe wave-config loss, made structurally impossible). Rig options apply through Generate rig → Bake → mod build. Turn a raw static vehicle model into a rigged, animated GLB (Root + per-wheel/turret bones, a procedural Spin action, optional rolling tracks and wave-rock) that the animated bake path then consumes — no Blender knowledge needed. Key controls: raw model + output GLB, Probe parts, per-part role assignment, use-source-skeleton (for pre-rigged SKM_ rips), part-hiding sliders, model roll/pitch/yaw, axle axis, Spin frames/degrees, tread speed/detail, wave-rock block, Generate rig, save/load recipes, Verify report. Writes: the rigged GLB + a recipe JSON (no unit registry write here — the GLB is then baked via Model Factory/Animation Lab).

Interior-part detection (strip what’s never seen). The probe classifies every part by escape-ray sampling: a part is external if any sampled surface point has a straight, unblocked line to infinity; a part blocked in every direction from every sample is interior — provably invisible (cockpit instruments, engine internals) and safe to strip. The Visibility switch (All / External only / Interior only) filters the part list; set it to Interior only and sweep the rows with I (Ignore = deleted from the output GLB) to reclaim the vertex budget for visible surfaces — on the RAH-66 that was 47 parts and 28% of the model’s vertices. The verdict is deliberately conservative: anything that peeks through an opening (canopy, gun bay) counts as external, so “inside-ish” parts remain a manual judgment call. Classification happens at probe time — re-Probe an older session to populate it. Verify also warns about interior parts not yet Ignored (name, role, vert count — clickable), so wasted budget can’t slip through unnoticed.

Rotorcraft roles (helicopters). Besides Wheel/Turret/Body there are Rotor (R) and Tail rotor (L) roles. Each rotor group fuses into one hub bone (unlike wheels’ proximity clusters, so a wide blade disc spins as one): the main rotor pivots on its central hub part and spins about that hub’s own pole-to-pole axis; the tail fan pivots on the blades’ centroid and spins about the axis perpendicular to the duct ring (lateral to the boom), with Tail-rotor axle X/Y/Z override plus yaw/pitch trim sliders for the final degrees. Rotors are excluded from the wheels’ rolling-contact speed scaling, so main + tail spin at the same rate, and a rotor marking switches the printed next-step recipe to the rotorcraft bake (continuous spin — State-driven OFF). Preview aids for dialing it in: Pause, ◀/▶ frame-step, and a Level line (horizontal reference at rotor height). Rotor bones are authored as axle frames (main: local Y = mast; tail fan: local X = the canted fan axle) so the donor’s own clip can drive them — see Donor-Clip-Flight.md. Two workflow notes: Orientation composes yaw-first (Pitch/Roll act on the grid-aligned model as you see it), and the sliders only take effect on the next Generate rig run — which is also a mandatory separate step before an Animation Lab rebake (the Lab reuses the last rig GLB; skipping Generate rig bakes the OLD rig).

Materials survive here, but are proven later. Generate rig joins the classified parts into a skinned mesh while preserving the source’s material slots. The HAF atlas is not created until Model Factory/Animation Lab bakes that GLB. For multi-material vehicles, start that bake with Material mode Auto/Multi and Reduce to ~tris = 0; only lower the triangle ceiling after the post-Bake mapping is correct. See Textures.md for the failure catalog.

New to the workflow? Follow Vehicle Lab quickstart for the complete raw-model → rig → animated bake → game sequence.


The injection-axis tools

District Factory — Tools ▸ HAF ▸ District Factory

Bake a custom static district building — imports a model, bakes a bone-free, auto-leveled FxMesh, writes the district registry entry (incl. the baked albedo GUID the plugin’s texture injection binds) the district repoint reads. An embedded preview pane shows the baked mesh, textured, on a true-size tile hex at the true in-game surface level — Facing and Position offset preview live; orbit/pan/deep-zoom + a Center re-frame button. Key controls: district name + Pick, model file, Size, Rotation offset (stand it up), Facing on tile (turn it), Position offset (place it), target tris, normals, strip parts, isolate. Writes: haf_districts.json (via DistrictRegistry.Upsert; mesh via DistrictBaker.BakeFxMesh). Deep dive: District-Visuals.md.

Prop Lab (attachments) — Tools ▸ HAF ▸ Prop Lab (attachments) · experimental

Author custom pawn attachments (weapons/gear): static bake → bone-free FxMesh → MeshCollection → FragmentMesh. Includes a dump tool for vanilla fragment GUIDs. Key controls: fragment GUID (dump), Size, rotation/position offset, target tris, borrowed material GUID. Writes: Assets/Databases/haf_props.json (editor recipe store; runtime reads the baked GUIDs). Deep dive: Pawn-Props.md.

Projectile Lab (munitions) — Tools ▸ HAF ▸ Projectile Lab (munitions) · experimental

The projectile injection axis — today mostly a dump/discovery tool: paste a vanilla projectile GUID to log its FX GUIDs/speed and walk each FxEvolverMaterial for mesh-typed fields, plus baking knobs toward a mesh-particle munition. The final mesh-swap output field is still being discovered, so its registry/asset output isn’t fully wired. Deep dive: Projectiles.md.

Formation Override — Tools ▸ HAF ▸ Formation Override

Link a unit to a custom/vanilla formation (changing displayed pawn count) by serializing the formation’s full layout into config; supports a single-unit link or a full formation replacement (macro). Key controls: mode toggle, unit + Pick, formation + Pick, re-read layout, packing jitter, formation scale + mode, footprint override. Writes: haf_formations.json (dummy positions + 6 orientation grids, via FormationRegistry.Upsert). Deep dive: Formations.md.

Resize Lab — Tools ▸ HAF ▸ Resize Lab

Runtime per-unit rescaling, no bake — rules {match, scale, era, trueSize, note} the plugin applies to any unit whose presentation name contains match. Key controls: the rule rows (match string, scale, era, note). Writes: the unitScales array in pack.json (via ModelRegistry.Save). Deep dive: Unit-Size.md.

Global Era Lab — Tools ▸ HAF ▸ Global Era Lab

Author how already-resized units rescale as the world ages — a 5×5 grid of (unit era × current era) multipliers (defaults 1.0), plus a “formation by size” threshold table. Key controls: the era-scale grid cells, formation-threshold rows (threshold + formation Pick). Writes: era/threshold statics into pack.json (via ModelRegistry.SaveStatics, preserving on-disk models). Deep dive: Unit-Size.md.


Textures & audio

Unit Retexture — Tools ▸ HAF ▸ Unit Retexture

Reskin an existing unit at runtime without baking a model — download its atlas to paint, replace with a PNG, or just grey/tint it. Key controls: pawn description, replacement PNG, brightness (gamma), desaturate, RGB ±255 tint, download-skin / Apply / Remove. Writes: texture-only entries in pack.json; skins under the pack’s skins/. Deep dive: Textures.md.

Sound Studio — Tools ▸ HAF ▸ Sound Studio

Configure one unit’s whole audio profile onto its existing registry entry — no bake: silence donor sound, idle growl, attack roar, death/battle cries, movement WAVs (spool-up → loop → spool-down), and the Wwise engine event. Key controls: pawn Pick, silence-donor, per-clip fields with ▶ preview / ■ stop, start offsets, one-voice radius, engine-event toggle. Writes: the unit’s entry in pack.json; sound files under the pack’s sounds/. Deep dive: Factory-Manual.md §13–14.

Game Sound Lab — Tools ▸ HAF ▸ Game Sound Lab

Author global audio overrides — silence any vanilla Wwise event by name-substring (units / ambient / music / UI). Distinct from Sound Studio (which is per-model). Key controls: override rows (silence substring), category tabs, a searchable catalog pick list. Writes: haf_sounds.json (via SoundOverrideRegistry.Save). Deep dive: Game-Sound-Lab.md.


Utilities, diagnostics & safety

In-game F8 window (runtime — part of the plugin, not Tools ▸ HAF)

Press F8 in a loaded game for HAF’s runtime panel. It’s trimmed to what a mod author needs while testing a build — “is HAF working, did it pick up my mod, do my models fit, and the live audio/texture/footprint authoring aids”:

Tests — Tools ▸ HAF ▸ Bake Tests…

One window is the whole in-editor test suite (it replaced seven bare menu items on 2026-08-20). Every bake integration test is a row with a plain-language explanation of what it tests and what it costs, a checkbox, and Quick/Everything presets; one Run button executes the selection and each run writes a durable report to Logs/haf_bake_tests_report.txt, with per-row PASS/FAIL in the window (failures unfold their detail). All tests are non-destructive (throwaway resource names — your assets and registry are untouched).

Fire and forget (since 2026-08-22): a run finishes on its own — alt-tab away, minimise Unity, walk off. It used to be driven by editor ticks, which Unity stops delivering when its window loses OS focus, so a 28-minute suite silently stalled the moment you looked at something else. The run is now one synchronous pass behind a cancellable progress bar, and the report is rewritten after every test, so even a cancelled or interrupted run leaves a record of everything that finished. The rows:

Run before committing baker/pipeline changes. See Testing.md and Factory-Manual.md §11.

Under the hood (non-window)