Compiled 2026-07-12 from a GitHub-wide search (repo/code/README search + walking each known modder’s account), then reading each repo’s README and key sources. Everything below was verified against the actual code, not just descriptions. Class/method names are as they appear in those repos (2021–2024 game versions) — re-verify signatures against current game DLLs (ilspycmd) before relying on one.
Why this document: these repos collectively map which Amplitude systems are patchable and how the community solved problems we share (lifecycle timing, per-entity runtime state, sim→presentation sequencing, distribution). Use it as a technique index when building the next Factory feature.
| Repo | Problem it solves | Headline technique for us | License | Last activity |
|---|---|---|---|---|
| Theadd/Humankind-GUI-Tools | In-game toolbox: unit spawner, cheats, live map editor, free cam | PostAndTrackOrder(...).UponCompletion(...) + PresentationArmy.DoUpdateMesh |
MIT | 2024-10 |
| Theadd/Modding.Humankind.DevTools | Framework library: game-state facades, lifecycle, hotkeys, window kit | Readiness gates; Interop-vs-Simulation dual access map | MIT | 2022-11 |
| Theadd/HumankindHacks | Cheat/QoL plugins + shared-project framework v2 | AssetHunter (find which loaded bundle holds an asset); zero-Harmony coroutine architecture |
none | 2022-06 |
| Theadd/ModdingTools | CLI that scaffolds a BepInEx plugin workspace | Assembly publicizer + wildcard-reference build props; hot-reload loop | none | 2023-01 |
| Gedemon/HK-CultureUnlockFromTerritory | Cultures unlock only if you hold their historical territory | Sandbox.ThreadStart/ThreadStarted lifecycle trio; Databases.GetDatabase<T>().Touch(row) |
none | 2022-10 |
| Gedemon/HK-Uchronia | TCL successor + battle postures, AI faction choice, debug overlay | Per-entity save extension keyed by GUID; presentation controllers callable from Update() |
none | 2022-04 |
| Gedemon/HK-Cognomen | Dynamic empire names from civics/ideology/era | Postfix the game’s SimulationEventRaised_* handlers (our fire-on-attack pattern) |
none | 2022-04 |
| Gedemon/HK-DelayedAITurn | “Humans first, then AI” turn mode | Order veto: prefix DepartmentOfCommunication.ProcessOrder*, return false |
none | 2022-03 |
| shakee2/shakee.Humankind.FameByScoring | Fame from periodic scoring rounds instead of era stars | MajorEmpire.Serialize postfix → mod state inside vanilla saves; full lobby-option recipe |
MIT | 2023-01 |
| rykolu/HkFPCameraModSimple | First-person fly camera (F2) | Disable PresentationCameraMover, add own fly-cam, nearClipPlane = 0.01 → in-game model inspection |
none | 2022-01 |
| rykolu/HkModEntry | Pre-BepInEx custom mod loader (discontinued) | Anti-pattern (patched game DLL); repo doubles as decompiled Amplitude.UI reference |
none | 2021-09 |
| Touhma/Humankind_Plugin | JSON-configurable balance knobs XML mods can’t reach | Postfix Initialize() on data definitions; write-defaults-then-read JSON config |
none | 2021-09 |
One author, one layered stack: DevTools is the base library-mod, GUI-Tools the flagship built
on it (hard [BepInDependency]), HumankindHacks a later monorepo re-extracting the framework,
ModdingTools an offline scaffolder. All BepInEx 5 / IMGUI.
Problem solved. Resurrects the 100+ hidden debug windows Amplitude shipped disabled inside the
game (MilitaryCheats, DistrictPainter, PawnAnimation, Pawns, GPUProfilerWindow, …) and
adapts ~15 into a toolbox: spawn any unit anywhere, paint districts, free camera, cheats, live map
editor.
How. All state changes go through the order bus — SandboxManager.PostOrder(new OrderSpawnArmy {
WorldPosition = …, UnitDefinitions = new[]{ unitDefinitionName } }), OrderDamageUnits,
OrderChangeUnitsXP, editor orders like EditorOrderCreateExtensionDistrictAt. Reads selection via
Snapshots.ArmyCursorSnapshot.PresentationData and resolves GUID → presentation object with
Presentation.PresentationEntityFactoryController.GetArmy((ulong)guid). Extends the sealed
PresentationCursorController by caching its private MethodInfos and injecting a custom cursor
subclass — grafting behavior into presentation controllers without Harmony.
What we could use.
PostAndTrackOrder(order, empireIndex).UponCompletion(() => army.DoUpdateMesh(false, false)).
A sanctioned “make this pawn re-evaluate its visuals after the sim change lands” primitive —
exactly the sequencing our respawn-after-load and freeze/strip effects currently approximate with
frame delays.OrderSpawnArmy + PresentationCursorController.ChangeToDefaultCursor(new
StaticString(unitDefName)) spawns our Era-6 units on turn 1. Wiring this to a debug hotkey would
remove most of our test friction (no more playing/dev-console to reach late eras).VERSION file from raw.githubusercontent.com and disables all
tools when IsOnlineGame && LatestVersion != PLUGIN_VERSION — cheap desync guard for when the
Factory ships publicly.Problem solved. Gives other plugins documented facades (HumankindGame, HumankindEmpire),
a reliable loaded/new-turn lifecycle, attribute-driven hotkeys ([InGameKeyboardShortcut],
[OnGameHasLoaded] — discovered by scanning all loaded assemblies for [DevToolsModule]), and an
IMGUI window framework. Hot-reloads via BepInEx ScriptEngine (deploy to BepInEx\scripts\).
What we could use.
SandboxManager.IsStarted &&
Amplitude.Mercury.Presentation.Presentation.HasBeenStarted; UI root exists when
GameObject.Find("/WindowsRoot/SystemOverlays") != null. Its only Harmony patch — postfix
AIController.InitializeOnLoad / InitializeOnStart, prefix OnNewTurnStarted — is how the whole
framework learns loaded-vs-new-game-vs-turn. Cleaner anchors than our timer heuristics.Amplitude.Mercury.Interop.AI.Entities.* = fast
read snapshots, Amplitude.Mercury.Simulation.* = live sim. DevTools documents which to use when —
the seam our pawn code lives in.[DevToolsModule] assembly-scan pattern is a ready design if the Factory ever wants
third-party model packs to self-register runtime hooks.R.Fields reflection cache (every private FieldInfo in one file) so a game patch
breaks exactly one file.Problem solved. Cheat/QoL plugins (EndlessMovingArmies etc.) plus the framework as shared
projects. Notable: EndlessMovingArmies uses no Harmony at all — a coroutine polls until services
exist, then a MonoBehaviour loop reads/writes via Interop snapshots.
What we could use.
AssetHunter: AssetBundle.GetAllLoadedAssetBundles().Where(b => b.Contains(assetPath)) —
finds which loaded vanilla bundle contains a named asset at runtime. Directly attacks our
“find the borrowed vanilla visual” problem without the Unity-Inspector detour.GUISkin in an AssetBundle embedded as a manifest resource inside the DLL —
single-file distribution trick for our packaging goal.HumankindGame.TrySendChatMessage) and
honors an EnableInOnlineSessions config.Problem solved. dotnet-CLI that generates a complete plugin workspace for any Unity game:
detects the game install, installs BepInEx templates, and — the key part — publicizes every game
DLL into lib/references/ with a Directory.Build.props that wildcard-references them and
post-build-copies the plugin to BepInEx\scripts\ for ScriptEngine hot reload.
What we could use.
Four repos sharing one toolkit (HumankindModTool, credited to “AOM”) and one architecture:
publicized DLLs, PatchAll() in Awake(), patch classes organized by target namespace.
All unlicensed and dormant since 2022 — treat as technique documentation, don’t vendor code.
Problem solved. “True Culture Location”: you can only become Rome if you hold Latium. Territory renaming, historical city names, culture-change territory loss, extra empire slots.
What we could use.
Sandbox.ThreadStart prefix = entering game (init per-game state), ThreadStarted postfix =
world fully loaded (safe to touch databases), ThreadStart postfix = exiting (clear all statics).
Our respawn-after-load logic could anchor here instead of timers.Databases.GetDatabase<T>().Touch(row) — the universal “inject a database row at load time”
API, proven for LocalizedStringElement (localization for custom unit names!),
GameOptionDefinition, UIMapper.*TCL.json in a known
folder; the plugin discovers, validates against a map hash, and merges by priority. A proven shape
if our haf_models.json ever needs multi-pack merging.CollectibleManagerPatch comments enumerate the definition databases relevant to us:
PresentationUnitDefinition, UnitVisualAffinityDefinition, BuildingVisualAffinityDefinition.
And its district code shows visual affinity is mutable simulation state
(district.InitialVisualAffinityName = …) — the sim-side counterpart of our presentation-side
pawn work, potentially a cleaner lever someday.Problem solved. TCL successor: battle “postures” with forced auto-resolve, AI faction-choice rework, F3 territory debug overlay.
What we could use.
Battle.Serialize, keep a BattleExtension keyed by
battle.GUID — the template if fire-on-attack effects ever need battle-scoped state that
survives saves.Update():
Presentation.PresentationTerritoryHighlightController.SetTerritoryVisibility(i, bool),
PresentationCursorController.ChangeToDiplomaticCursor(...) — same static-hub family as our
PawnManager work; useful for debug overlays on model placement.UIManager.IsUiVisible = false + a hotkey = clean-screenshot mode for reviewing injected models.Problem solved. Dynamic empire titles (“Celtic Kingdom”, “People’s Democratic Republic”) derived from civics/ideology/era. A single ~1200-line file deliberately written as a modding tutorial — the best “how to write a Humankind Harmony mod” document in the ecosystem.
What we could use.
SimulationEvent<T>.Raised ourselves, postfix the game’s existing handler methods — e.g.
CultureManager.SimulationEventRaised_CivicChoiceChanged(object sender,
SimulationEvent_CivicChoiceChanged e). The handler already receives the typed event; no
subscription lifecycle to manage; fires exactly when the sim does. (FameByScoring below shows the
complementary variant: patching SimulationEvent_X.Raise itself.)PresentationAvatarController.DoStart/DoShutdown to
flip an isPresentationStarted static — a cleaner “presentation is alive” signal than timing.NotificationsController.AllConfigs[i].GetDescription via NotificationDataConfig.Forge(...).
Worth remembering when a Harmony target seems to not exist.Documents\Humankind\Temporary Files.Problem solved. “Humans first” turn mode: AI empires wait until every human has clicked End Turn. One file, ~230 lines, five patches — a good skeleton reference.
What we could use.
DepartmentOfCommunication.ProcessOrderEmpireReady(order) and
return false to swallow the order — there’s a ProcessOrderX per order type, so this is the
general veto point in the turn pipeline.AIController.RunAIDecisionCycle marks the AI-turn boundary; the sandbox runs on its own thread
(the mod blocks it with Thread.Sleep polling — possible there, never on the main thread).Problem solved. Fame from periodic Military/City/State/Economy scoring rounds instead of era stars, with ~10 new lobby options and a fame-history tooltip on the empire banner.
What we could use.
MajorEmpireExtension : Amplitude.Serialization.ISerializable holding mod data, written/read by
a postfix on MajorEmpire.Serialize(Serializer) via
serializer.SerializeElement("MajorEmpireExtension", …), switching on
serializer.SerializationMode (Read/Write). Allocation on MajorEmpire.InitializeOnStart postfix,
teardown on Sandbox.ThreadStart. Adaptable to per-unit state — if model assignments or
freeze-flags ever need to survive save/load without our sidecar JSON, this is how.Sandbox.ThreadStart patch casts
SandboxThreadStartSettings.Parameter() to SandboxStartSettings / ScenarioStartSettings /
GameSaveDescriptor to distinguish new game vs save-load vs scenario — a more principled home for
our respawnAfterLoad flag than detecting after the fact.HumankindModTool/GameOptionHelper.cs): prefix
OptionsManager<GameOptionDefinition>.Load, ScriptableObject.CreateInstance<GameOptionDefinition>(),
Touch() it plus %{key}Title localization plus OptionUIMapper rows → a real in-game option
instead of a BepInEx config file. Read back via
Services.GetService<IGameOptionsService>().GetOption(new StaticString(key)).CurrentValue.SimulationEvent_BattleTerminated.Raise,
SimulationEvent_UnitKilledByOther.Raise, SimulationEvent_NewTurnBegin.Raise.GameObject.Find("WindowsRoot/InGameOverlays/EmpireBanner/
_FamePennant/FameScore/") then attach a child GameObject with Amplitude UI components — the
quick-and-dirty way to add HUD elements.Problem solved. F2 toggles a first-person WASD fly camera. ~150 lines, no Harmony.
What we could use.
GameObject.Find("Camera") →
GetComponent<PresentationCameraMover>() (Amplitude’s RTS camera driver) → enabled = false,
add your own fly-cam MonoBehaviour, set nearClipPlane = 0.01f. Lets us orbit injected pawns at
close range in the real renderer — the truthful complement to our editor preview (a custom posed
viewer is impossible; this sidesteps it entirely).ImpostorCamera, AvatarCamera, Camera, MainView,
UIFxCamera) and the pawn render layers: PresentationPawn, PresentationPawnHidden,
PresentationPawnGhost, PresentationPawnGhostAndOpaque — useful if we ever need layer tricks on
injected geometry.Problem solved. A 2021 mod loader that patched Amplitude.UI.dll itself (decompile → inject a
DLL-scanner into UIUpdatingManager.Update() → recompile). Author’s own verdict: “Discontinued,
since BepInEx does this but better.”
Takeaways. Validates our BepInEx no-exe-patching approach — replacing shipped DLLs breaks every
game update. Two residual nuggets: a convention-based plugin contract (<Name>.Main.Load() +
optional per-frame OnUpdate) if the Factory ever wants drop-in model packs with runtime hooks; and
the repo is a grep-able decompiled snapshot of Amplitude.UI circa 1.0.x.
Problem solved. Makes code-initialized balance values (game-speed multipliers, era-star requirements, pollution thresholds, end-game conditions) configurable via JSON — the plugin half of a data-mod + plugin pairing, existing precisely because XML/data mods can’t touch definition fields set in code.
What we could use.
Initialize() on data definitions (GameSpeedDefinition, EraDefinition, …) — the
general lever for anything living in an Amplitude.Mercury.Data.Simulation definition. Relevant
if the registry ever needs to adjust UnitDefinition/pawn definitions in code rather than assets.BepInEx/config/...json; later runs read user edits back. Self-documenting — a nice touch for
haf_models.json template generation.___PrivateField parameter injection to read private manager state;
prefix with __result + return false for full replacement.Databases.GetDatabase(Type) that dumps every game database to JSON —
a poor-man’s data-mining tool worth resurrecting for exploration.1.0.2.139/, 1.3.248/) — sane layout
for tracking game patches.SandboxManager.PostAndTrackOrder(...).UponCompletion(...)
→ PresentationArmy.DoUpdateMesh) is the ecosystem’s sanctioned sim→presentation sequencing
primitive. Our respawn-after-load and any future per-pawn effect should evaluate it.Sandbox.ThreadStart (prefix=enter / postfix=exit) + ThreadStarted (world ready) +
Presentation.HasBeenStarted + PresentationAvatarController.DoStart/DoShutdown +
session-type detection via SandboxThreadStartSettings.Parameter() casts.SimulationEventRaised_* handler methods (Cognomen), or patch SimulationEvent_X.Raise itself
(FameByScoring — BattleTerminated, UnitKilledByOther are proven targets).Databases.GetDatabase<T>().Touch(row) injects database rows at load time — localization for
custom unit names, real lobby options (full recipe in FameByScoring’s GameOptionHelper),
UI mappers.Serialize(Serializer) on any sim entity + an ISerializable
sidecar = mod state inside vanilla saves, no external files.OrderSpawnArmy debug spawner + FP inspection camera + ScriptEngine
hot reload would together remove most of our test friction.