For adopters who just want to use the Factory, no build is needed — grab a released HumankindAssetFramework.dll. Build only if
you're changing the plugin.
Needs the .NET SDK. The project file is HumankindAssetFramework.csproj. Put a References\ folder next to the .csproj containing:
BepInEx.dll+0Harmony.dll+MonoMod.RuntimeDetour.dll+MonoMod.Utils.dll+Mono.Cecil.dll— from<Humankind>\BepInEx\core\(the MonoMod/Cecil trio are Harmony's own runtime deps — the unit suite EXECUTES patches for the shared-seam census tests)UnityEngine*.dll+Newtonsoft.Json.dll— from<Humankind>\Humankind_Data\Managed\(Newtonsoft is provided by the game at runtime — used for robust registry parsing, sinceUnityEngine.JsonUtilitysilently returns empty in the game's Mono runtime;Private=false, so it's not copied into the built plugin. No Amplitude DLL is needed — the plugin's only compile-time game surface is string-based reflection; the vestigialAmplitude.Mercury.Animation.dllreference was dropped 2026-08-17.)
No game installed? pwsh tools/fetch-refs.ps1 collects every reference DLL from public sources
(nuget.org, the BepInEx release, BepInEx's unstripped-Unity mirror) — this is what CI uses; it never overwrites
DLLs you already copied from the game. GitHub Actions builds + runs the full test suite on every push
(.github/workflows/ci.yml), along with every source-only guard — docs links, binding-catalog surface, hot path,
parse shape, editor source, and registry schema parity. Both halves now live in this repo, so parity needs no sibling
checkout. See
Testing.md for which guards run in which lane, and why the hook is not enough on its own.
Then:
dotnet build -c Release
and copy both bin\Release\HumankindAssetFramework.dll and bin\Release\Haf.Schema.dll → <Humankind>\BepInEx\plugins\
— or just run bash tools/deploy-plugin.sh, which copies both. Haf.Schema.dll (netstandard2.0) is the shared model
schema that ModelDef (editor) and ModelEntry (plugin) both inherit — the build produces it automatically via a
ProjectReference, but the plugin won't load without it (BepInEx reports the missing dependency), so the two ship
together. The editor package consumes the same tracked DLL from editor/Plugins/Haf.Schema.dll.
Refresh the tracked editor DLL when
Haf.Schemachanges. The editor compiles against the DLL undereditor/, not the source project, so a schema change is invisible on the editor side until that in-repo artifact is refreshed — and the failure looks like a compile error naming a type that plainly does exist ('PackValidator' does not contain a definition for 'ValidatePack'), which reads as your mistake rather than a stale binary. Hit exactly this on 2026-08-23 adding the wrapper rules. Iftools/editor_compile_check.shreports a member that you can see inHaf.Schema/, copy the DLL before believing it:cp Haf.Schema/bin/Release/netstandard2.0/Haf.Schema.dll editor/Plugins/Haf.Schema.dll
Blender is needed for .blend import, animated-model import, Strip parts, and Reduce-to-tris decimation —
auto-detected under Program Files, or point the Factory Settings override / EditorPrefs 'ENC.blenderPath' at
blender.exe. Static GLB/OBJ/FBX bakes with neither Strip nor Reduce need no Blender: the GLB path uses the
self-contained packaged editor/Tools~/glbconv/glbconv.exe (no .NET install required, and its Weld & simplify option decimates without
Blender). A dotnet glbconv.dll fallback exists for local dev.
The heavy geometry passes shell out to headless Blender (editor/Tools~/prep_model.py, rig_anim.py, blend_export.py)
rather than being written in C#. The scripts are Python, but Python is only the remote control — the actual
decimation/import/export executes inside Blender's C/C++ core:
- Quadric edge-collapse decimation is hard to write well (error quadrics, topology preservation, UV/normal
attribute handling). Blender's Decimate modifier is a battle-tested implementation; the script that drives it is
~15 lines. The C# alternative that predates it —
glbconv'sWeld & simplifyvertex clustering — survives as the Blender-free fallback, but clustering averages UVs across seams (scrambles textured skins) and eats thin features, which is exactly why the COLLAPSE path was added. - Blender was already a hard dependency for
.blendconversion and the animated pipeline (armature slimming, clip baking, FBX export) — Strip/Reduce ride an install the modder already has for those. - Performance: the language is irrelevant to speed here. The per-bake cost is Blender process startup + model import/export round-trip (measured: the vast majority of a ~33s prep pass on a 13.6 MB model), not execution. That's why strip+reduce were merged into ONE Blender session (one startup, one round-trip, ~24% faster) instead of optimizing any code.
- Future (package-readiness): the static pipeline could go fully C# — an in-process quadric simplifier (e.g. a
UnityMeshSimplifier-class library) plus node-name filtering inside
glbconvwould eliminate both the Blender dependency for static models and the round-trip cost. Blender would remain only for what genuinely needs a DCC:.blendfiles and animation. Tracked inFramework-Review.md.
dotnet test Tests/HumankindAssetFramework.Tests.csproj -c Release
An xUnit suite (net471) over the pure registry/parse/era layer — the half that touches no live-game reflection,
and where the bugs have historically been. Needs the same gitignored References\ DLLs as the plugin build (mirrored
into the test bin). Full detail — what's covered, what's deliberately out of scope and why, how to add a test — is in
Testing.md.
ENCReload/Tools/package-release.sh builds the player download: the Humankind mod and the HAF plugin that
renders its custom units, in one reproducible artifact.
bash Tools/package-release.sh # -> Distribution/release/ENCReload-<ver>.zip
bash Tools/package-release.sh --no-zip # stage only (fast, for inspection)
bash Tools/package-release.sh --haf <dir> # explicit plugin checkout
Two numbered folders, because the halves install to different roots — the mod is a Humankind Community module, the plugin is a BepInEx DLL, and no single "extract here" covers both:
| In the zip | Goes to |
|---|---|
1_Humankind_game_folder/BepInEx/ |
the Humankind install (the folder with Humankind.exe) |
2_Community_mods_folder/ENCReload.<guid>.<ver>/ |
the Community folder the player's other mods are in |
READ_ME_FIRST.txt is generated from a tracked template (Tools/release/) with the version and module GUID
substituted, so the shipped instructions cannot drift from what shipped. The plugin is built by the script,
never collected from wherever a DLL happened to be lying — the hand-made v0.1.0 zip ended up 178 commits behind
master exactly that way.
The stale-bundle guard is why this is a script and not a checklist. pack.json's skel/atlas/clip GUIDs
point into the asset bundle, so a re-bake after the last mod build produces a pair that looks fine and is
broken — "waiting for leaves to load…" forever, or a scrambled texture from a mismatched mesh/atlas pair. The
script refuses to package when any baked asset (or pack.json) is newer than the bundle: the same rule the
District Factory's health check already applies at authoring time, moved to where it would otherwise reach a
player. It excludes the bake-test fixtures by the same three prefixes the delete guard uses
(__feat_ / __smoketest__ / __convgate__) — a leftover fixture is newer than everything and would otherwise
fail every package.
The Model Factory and other authoring windows live in editor/ and install through Unity Package
Manager. They compile and run in the host Unity project; ENCReload is the reference consumer. Packaged Blender and
converter helpers live under editor/Tools~, and HafPackageContext supplies safe guest-project defaults and derives
the guest pack identity. The deleted baker/ editor snapshot must not return: it was a drifting second source.
Two live things that exist only here, and are built from here:
baker/glbconv/— the single source of truth for the GLB→OBJ converter (2026-08-17). Build withdotnet publish -c Release, deploy the exe toeditor/Tools~/glbconv/, and A/B-diff the OBJ output before deploying (procedure inbaker/glbconv/BUILD.md).baker/reactor_silhouette.py— the headless-Blender district silhouette helper (District-Dedicated-Visual.md).