AshForge Mod Kit · Assets · the whole lane, one course
Where art comes from, what survives
being written down, and what stops a bad mod shipping.
Three things that look unrelated and are one chain: the library you pull art from, the failure that loses your work while reporting success, and the gate that refuses to let a mod ship with its weapons in the wrong place.
What it is
The Mod Kit's view onto the Vault asset library, the rules for writing data down safely, and the convention gate that runs over a deployed mod.
Modules 1–4 are the library, 5–9 are persistence, 10–14 are the gate.
The one that has cost twice
⛔ A Godot struct written straight to JSON writes nothing of itself, successfully. A vector becomes {}; a colour becomes a payload full of plausible numbers that cannot restore it.
Modules 5–9. It has hit two tools, and once it survived review.
This lane is 14 things worth knowing, not three escalating tiers. The old split implied the later material needed the earlier and it does not — read straight through, or jump to the module that matches your problem. The Mod Kit's other lanes are separate courses.
Orientation
What you are looking at
The tab reads the library's own index and shows you what has been published, with the lineage each asset carries.
Verified end to end: an asset published to the library appears in the tab's tree by name, and its lineage survives the round trip through JSON — where it came from travels with it rather than being reconstructed. That matters later, when signing asks your mod to account for every asset it ships.
The tab reads the format through the shared reader, not a Vault object. That is not fussiness: the Mod Kit and Vault are separate applications and neither can reference the other's types. What they agree on is a folder and a file layout, which is the loosest coupling that could possibly work — and therefore the most robust.
The action
Pulling one in
Select an asset, press Use in This Mod, and a copy lands in your mod's
assets/ folder under its own category.
| What was checked | Result |
|---|---|
| The file arrives in the mod | MyMod/assets/textures/rust_plate.png |
| The bytes match the published original | Identical |
| The library object afterwards | Still there, untouched — the Mod Kit only ever reads |
The destination folder comes from what the asset is — a texture lands under
textures/ — so your mod's asset folder stays organised without you filing
anything. And because this is a copy rather than a reference, your mod is self-contained
from that moment on: it will build, package and sign on a machine that has no Vault library
at all.
The control
The button's three states
The button is always there. Whether it is usable answers a question you would otherwise have to guess.
| Situation | Button |
|---|---|
| No mod open | Disabled. There is nowhere to put the copy. |
| Mod open, nothing selected | Disabled. Nothing to copy. |
| Mod open, asset selected | Enabled. |
This is the same lesson as the Definitions workspace's header, arriving through a different control: the tool tells you whether a mod is open, and it will not let you do the damaging version of the mistake. Where the Definitions workspace saves somewhere unhelpful, the Library tab simply refuses — a copy has to have a destination, so there is no "somewhere else" for it to go.
Edge case
Empty is not broken
If you have never opened Vault, you have no library. The tab shows that as empty, not as an error.
Verified: a library that does not exist yet reads as empty rather than raising. That is a deliberate choice with a reason attached — a tool that reports a normal situation as a failure teaches people to ignore its warnings, and the warnings here are worth reading.
So an empty Library tab means one of two ordinary things: nothing has been published yet, or you have not used Vault on this machine. Neither needs fixing, and neither is the tool malfunctioning.
Mechanism
Fields and properties
Two reasonable decisions, made by two different teams, that produce silence when combined.
Godot's maths types store X, Y, Z as fields
— the fastest thing they can be, which is what you want in an engine that touches them
millions of times a frame. The JSON writer, following the .NET default, serialises
properties and ignores fields.
Neither is wrong. Together they mean a struct handed straight to the writer produces a document with nothing of the struct in it, and the writer reports success because nothing failed — it wrote everything it was willing to look at.
Vectors
The loud one
A raw Vector3 serialises to {}. Empty braces.
This is the merciful version, because once you have opened the file you cannot miss it. A
position, a rotation and a scale all written as {} is unmistakable, and it explains
the symptom exactly: every prop reads back as the default, which is the origin, and a scale of
zero.
Verified in both directions — a raw vector still writes nothing, and through the correct path a scale of 2 does not come back as 0. That second assertion exists because the symptom people actually report is "my props vanished", and a zero scale is what makes a prop invisible rather than merely misplaced.
{}. If your positions and scales are empty
braces, you have found it, and nothing you do in the interface will fix it — the
data never reached the file.Colours
The quiet one
A raw Color does not write nothing. It writes a great deal, and none
of it is the colour.
Unlike a vector, Color exposes a set of derived, read-only properties
— an 8-bit red, hue, saturation, value, luminance, and more — and those are
properties, so the writer takes them. What it omits is R, G, B and A: the four numbers
the colour is actually made of.
★★ The result is the most dangerous shape a bad file can take: a payload full of plausible
numbers that cannot restore the value. It does not look empty. It looks like a rich,
well-populated colour record. A reader that asks for R finds no such key, falls
back to a default, and hands you white.
| Type | What lands in the file | How you notice |
|---|---|---|
Vector3 | {} | Immediately, on sight. |
Color | Hue, saturation, luminance, 8-bit channels — no R/G/B/A | Only by round-tripping. The file looks fine. |
That asymmetry is why the harness asserts both types separately. A check that only covered vectors would have passed the whole time the colour bug was live.
History
What it cost, twice
The same mismatch, in two tools, found months apart, by two different symptoms.
| Where | What people saw |
|---|---|
| Atlas, Aug 2026 | Prop placements wrote as {}. Every prop reloaded at the origin with zero scale. |
| The Mod Kit's model importer | Imported colours came back white. The reader looked for a channel that was never written and defaulted, silently. |
The importer case is the instructive one, because it passed review. Someone read that code, saw a reader with a sensible fallback, and moved on — the fallback was the thing hiding the fault. A default value is a good idea when the data is genuinely absent and a disguise when it should have been there.
The fix
What correct looks like
The suite's house pattern is to never hand the writer a struct at all.
Keep the individual numbers on the saved model, and expose the convenient struct as a computed value the writer is told to ignore. The file then contains real components, and the struct is reassembled on the way out — so code keeps working with vectors and colours while the document stores what it can actually restore.
Verified through that path: a vector round-trips, a colour round-trips with its alpha, real components and real channels appear in the file, and a colour written in the web notation still reads. The importer's reader recovers the colour it was given.
| Also checked | Because |
|---|---|
| A malformed vector does not throw | One bad entry in a hand-edited file should not take the whole load down. |
| An omitted alpha comes back visible | Defaulting transparency to zero would make things silently invisible — the same class of failure again. |
The numbers
What it measures
Two measurements, each with a tolerance, both taken from the game's own weapons rather than chosen.
| Check | Expected | Band |
|---|---|---|
| Grip anchor, long guns | 0.278 m behind the hand | ±0.010 |
| Grip anchor, handguns | 0.075 m | ±0.010 |
| Muzzle offset | 1.2275 × how far the mesh reaches forward of the grip | ±2.5% |
The muzzle rule is a ratio, not a distance, which is what lets one number cover a pistol and a rifle. It is a least-squares fit through the six weapons the game ships, and what it says is worth knowing on its own: the game spawns the shot roughly 22–23% beyond the barrel tip, scaled to the gun — not at the tip, and not at a fixed clearance.
The grip anchor is the opposite kind of number: absolute, not a fraction. The four long guns sit between 0.2706 and 0.2790 — an 8 mm spread across weapons that differ by 565 mm in length — while as a percentage of length those same six span 16.8% to 52%. There is no invariant in the percentage at all. What is anchored is anatomical: how far the weapon extends behind the hand. The 10 mm tolerance is simply that 8 mm spread with a little room.
The idea worth stealing
The denominator
★★ The gate walks every weapon definition the mod ships and resolves each one's mesh. It does not walk the folder of meshes.
That inversion is the whole design. If you count assets, a weapon whose mesh is missing or unresolvable simply is not in the list, and a hole in your coverage reads as a clean pass. If you count definitions, that weapon is a failure, because a definition that cannot reach its mesh is exactly the thing you needed to hear about.
The gate also reports the other direction: meshes no definition references. Those are not coverage, they are dead weight — files your mod ships and never uses. Reporting both numbers means neither can be mistaken for the other.
Self-check
The control group
Before it judges your weapons, the gate measures the game's own six and asserts that they pass.
| The game's weapon | Grip | Muzzle vs expected |
|---|---|---|
| AssaultRifle | 0.2786 | 0.825 vs 0.8287 (−0.44%) |
| HuntingRifle | 0.2790 | 1.000 vs 1.0062 (−0.61%) |
| DoubleBarrelShotgun | 0.2706 | 0.768 vs 0.7549 (+1.73%) |
| PDW | 0.2783 | 0.315 vs 0.3147 (+0.09%) |
| Revolver | 0.0783 | 0.477 vs 0.4776 (−0.13%) |
| TacPistol | 0.0714 | 0.310 vs 0.3112 (−0.38%) |
Those six are not there to be tested — they are known good by definition, being the things the convention was measured from. They are there so that if the gate ever starts failing them, you know the instrument has drifted rather than your mod being wrong.
Look at the spread: the shotgun sits 1.73% off and still passes, the PDW is within a tenth of a percent. That range is exactly why the band is ±2.5% and not ±0.5%. ★ The tolerance is derived from the ground truth's own scatter, not picked — a tighter band would reject one of the game's own weapons, which is a category error in a gate whose entire job is "conforms to how the game does it". The side-by-side shotgun is the only one outside 1%; drop it and the constant tightens to 1.224 with all five inside 0.5%.
In practice
Reading a real run
Pointed at the mod the adapter built for the definitions ladder, the gate passed all six control weapons and failed both of the mod's own.
| Line | What it means |
|---|---|
FAIL ProbeCudgel: mesh Wood.glb exists |
The weapon resolves to Models/Items/Wood — the placeholder the exporter substitutes when a weapon has no model of its own. |
FAIL ProbeSidearm: mesh Wood.glb exists | The same. |
denominator: 2 WeaponDec with a mesh, 1 distinct meshes, 0 .glb referenced by no dec |
Two definitions, sharing one mesh between them, and no unused assets. |
This is the two ladders meeting. The definitions tutorial showed the exporter reporting that it had substituted a placeholder mesh; here is the gate refusing to let that ship. One tells you, the other stops you, and the mod in question is a test fixture that was never meant to pass.
Note also what the denominator line reveals for free: two weapons resolving to one distinct mesh. In a real mod that is a red flag on its own — not an error, but almost certainly not what you intended.
Limits
What it cannot tell you
The gate checks that a weapon is positioned to the convention. It has no opinion on whether it is any good.
It says nothing about balance — and as the definitions ladder established, a ranged weapon's damage is not on the weapon anyway. It says nothing about how the mesh looks, whether the silhouette reads at game distance, or whether the thing is fun. And it cannot confirm the game loads your mod; that needs the game.
What it does is remove one entire class of defect from the list of things that might be wrong, which is exactly what a gate is for. Run it before you ship, and the forty-gun morning cannot happen to you.
Course complete
What you now know
Check yourself
- Vault owns the library; the Mod Kit reads it and never writes it. One writer means "which copy is true" is never a question.
- The two applications agree on a folder and a format, not a shared type — they could not share one.
- Use in This Mod copies the asset into your mod's
assets/, filed by what it is. The bytes match; the original is untouched. - Your mod is self-contained afterwards — no library needed to build or ship it.
- The button is disabled with no mod open, and enabled once a mod and an asset are both selected.
- An empty library reads as empty, not as an error, on purpose.
- Asset lineage travels with the asset — which the shipping tutorial will come back to.
- Godot structs keep components as fields; the JSON writer takes properties. A raw struct writes nothing of itself, successfully.
Vector3writes{}— the loud failure. Props reload at the origin with zero scale.Colorwrites its derived properties and omits R/G/B/A — a payload full of plausible numbers that cannot restore the colour. Readers fall back to white.- The two fail differently, which is why both are asserted; a vectors-only check would have passed throughout the colour bug.
- It has cost twice: Atlas prop placements, and the Mod Kit's own importer, where a sensible-looking fallback hid it through review.
- The fix is scalars on the model, the struct as an ignored computed value.
- A successful save proves nothing. Round-trip it, or open the file.
- A weapon has three facts: length, grip origin, muzzle offset. The incident behind this gate was a check that covered one.
- The muzzle rule is a ratio (1.2275× the mesh's forward reach), which is how one number covers pistols and rifles.
- Long guns anchor at 0.278 m behind the hand, handguns at 0.075 m, both ±0.010.
- ★★ The denominator is the decs, not the assets. Count definitions, or a weapon broken enough to drop out of the list will read as a pass.
- Meshes no definition references are reported separately — dead weight, not coverage.
- The gate measures the game's own six weapons first, so a failure there means the instrument drifted, not your mod.
- The ±2.5% band was chosen to admit the game's own spread (−0.61% to +1.73%), not picked from the air.
- It checks geometry, not play — and it cannot confirm the game loads anything.
That is the assets lane. ★★ The idea worth stealing from it: the denominator is the decs, not the assets — count definitions, or a thing broken enough to fall out of the list will read as a clean pass.
