AshForge Mod Kit · Asset Lane · the whole lane, one course
How your own art gets in,
and why it does not ship as a PNG.
An icon you drew and a model you made, carried into the exported mod and referenced by your own definitions. The mechanism is small and worth knowing exactly — including why shipping the obvious file format costs four times the video memory.
What it is
Two folders in your mod, icons/ and models/, and the lane that carries what they hold into Assets/ inside the exported mod.
Modules 1–4 are references, 5–9 are formats, 10–14 are what ships.
The number that matters
⛔ The game cannot compress a texture at runtime. One 256×256 icon costs 349,524 bytes as a loose PNG against 87,408 compressed.
Four times, silently, per texture, with no symptom but a budget that ran out early.
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.
Sources
Where art lives
Two folders in your mod, and two prefixes in the Definitions forms that point into them.
| Folder | Written in a definition as | Used for |
|---|---|---|
<mod>/icons/ | icon:beacon.png | An item, research node or anything with an Icon field. |
<mod>/models/ | model:beacon.glb | The mesh a thing renders as. |
Both are set from the Definitions workspace, in the same forms you already know — the
art fields were always there, holding these icon: and model: values.
For a long time nothing read them and every exported reference was a base-game constant; the
asset lane is what connects the two ends.
Note the extensions here: you name a real file, with its extension, because you are pointing at something on your disk. What comes out the other end does not have one, which is the next lesson and the single most common source of confusion.
The mechanism
A reference has no extension
★★ A reference is a namespace plus a path, and never a filename. The game builds the filename itself.
When the game wants a texture it appends .png; when it wants a mesh it appends
.glb. So what goes into the definition is <modid>/<path>
with nothing on the end, and the same reference serves an icon and a mesh in different fields
without ambiguity — because the field decides the extension, not the value.
Here is a real exported item, produced for this page. One reference, used three times:
| Field | Value | The game will look for |
|---|---|---|
Icon | AshForgeAssetDemo/beacon | beacon.png |
MeshPath | AshForgeAssetDemo/beacon | beacon.glb |
Material | AshForgeAssetDemo/beacon | the material for it |
.png becomes a request for beacon.png.png, which does not exist, and
you get a placeholder with no explanation.Organisation
Subfolders are kept
If you organise your icons into folders, that structure survives into the exported mod.
icons/ui/crate.png becomes Assets/ui/crate.ashtex and is referenced
as <modid>/ui/crate. The lane could have flattened everything into one
directory and it deliberately does not, for two reasons worth knowing.
First, flattening renames an author's files behind their back, and a pipeline that
quietly changes your names is one you cannot reason about. Second, and more seriously, two
icons called crate.png in two different folders would collide silently
— one would overwrite the other and the mod would ship with the wrong art and no error.
Keeping the structure makes that collision impossible rather than merely unlikely.
icons/weapons/rifle.png reads back as <modid>/weapons/rifle
— which is easier to recognise in a dec than a flat list would be.The result
What actually ships
A demo mod with one item that owns its icon, its mesh and its material was exported for this page. This is everything it produced.
File under Assets/ | Size | What it is |
|---|---|---|
beacon.ashtex | 3,873 B | The icon, compressed. |
beacon.glb | 3,428 B | The mesh, copied as-is. |
beacon.ashmat | 148 B | The material. |
beacon_albedo.ashtex | 159 B | The material's texture. |
.ashforge-provenance.json | 1,106 B | Where every one of the above came from. |
The export reported it plainly: ships 1 icon, 1 mesh, 1 material of its own, and closed with an audit line — 3 references into this mod's own namespace, all of them served. That last number is the one to read: it counts references that point at your mod and confirms each one found a file.
Notice what the mesh did not do: beacon.glb is copied unchanged. Meshes
ship as they are. Textures do not, and that is the whole of the next tutorial.
The reason
Why the PNG can't go
A PNG is compressed on disk. It is not compressed in video memory, and those are completely different things.
When a texture is uploaded to the GPU it has to be in a format the GPU can sample directly. Most engines will compress one for you on the way in. This game cannot — the capability is checked every launch and is unavailable — so a PNG lands as raw pixels, four bytes each.
The arithmetic for a single 256×256 icon:
| Shipped as | In video memory |
|---|---|
A loose .png | 349,524 bytes — raw, four bytes per pixel |
A .ashtex | 87,408 bytes — compressed, with its mipmaps |
That is one icon. A mod with fifty of them is paying for two hundred that it does not have. And the cost is invisible from inside the game: the art looks right, nothing errors, and the only symptom is a memory budget that ran out sooner than anyone expected.
The trick
The substitution
The game is hard-coded to ask for a .png. The loader answers with
the .ashtex instead.
This is why the two facts that look contradictory are both true. Your definition holds
<modid>/beacon, the game turns that into a request for
beacon.png, and the loader — which sits between the game and your mod's
files — hands back beacon.ashtex. The game never learns the difference.
It also means you cannot get compressed textures into this game any other way. There is no setting, no import flag and no alternative path: the substitution is the mechanism.
.png inside a dec is wrong, and a
.png sitting in your exported Assets/ is waste. Neither will
error. The first shows as a missing texture, the second only as memory.The conversion
What conversion does
A 256×256 image was converted for this page. Here is what came out.
| Property | Value |
|---|---|
| Dimensions | 256 × 256 |
| Mipmaps | Generated — the full chain down to 1×1 |
| Size in video memory | 87,408 bytes |
| Size on disk | 38,800 bytes — deflated on top of the compression |
So the format does two jobs at once. It stores the texture already in the form the GPU wants, which is the part that saves the memory, and it then deflates that for storage, which is why the file on disk is smaller than the memory it will occupy. Your download gets smaller and your video memory gets smaller, from one conversion.
★ A small confirmation worth mentioning, because it is the kind of thing that catches a bad pipeline: the size the file claims it will occupy — 87,408 bytes — matches an independent calculation of what a 256×256 compressed texture with a full mipmap chain should be, exactly, to the byte. The file is not merely well-formed; it is arithmetically consistent with what it says it is.
Materials
Materials carry their own texture
A material is not a texture. It is a small description that points at one, plus the numbers that decide how the surface behaves.
The demo mod's material is 148 bytes and holds four things: a base colour, a roughness, a metallic value, and the path to its albedo texture — which is a separate file, converted the same way as any icon.
That is why an item that ships one mesh can end up with two texture files in
Assets/: the icon you see in a list, and the albedo wrapped around the model in the
world. They are unrelated images doing unrelated jobs, and forgetting the second is why a model
can appear correctly shaped and completely untextured.
The material is read out of your .glb automatically. ⚠ If the mesh cannot be
read, no material is produced and the definition keeps its base-game one — which
loads and renders, so the failure looks like "my model has the wrong texture" rather than like
an error.
The trap
One field, four files
Most things need one mesh. Two kinds of thing need more, from the same single field, and the game does not check.
| What it is | Files required from one mesh reference |
|---|---|
| An ordinary item or building | foo.glb |
| A storage building | foo.glb, foo_Low.glb, foo_Partial.glb, foo_Full.glb |
| A plant | foo.glb, foo_Half.glb, foo_Sprout.glb |
A storage building shows how full it is by swapping mesh; a plant shows its growth stage the same way. One field in your definition therefore stands for four files or three, named by convention, and the game dereferences them without checking they exist. A missing one is not an error — it is a bright magenta placeholder in the world.
The signing gate refuses a mod in this state, which is the right place to catch it but a late one. If you are modelling a stockpile or a crop, plan for the whole set from the start, because discovering it at signing means going back to your modelling tool.
By design
When a placeholder is right
The lane deliberately leaves a definition alone when its art is unset or is a built-in preview name. Three reasons, and each one matters.
| Case | Why it must keep working |
|---|---|
| Your mod uses base-game art on purpose | Legitimate and common. A mod that adds a recipe for an existing item needs no art at all. |
| You are part-way through pipelining | You must still be able to export and test. A pipeline that refuses to run until every asset exists is one nobody can iterate in. |
| The reference points outside your mod | The gate deliberately does not judge foreign namespaces — it has no way to know what another mod ships. |
So "placeholder" in the report is a statement of fact, not an accusation. The question it asks you is simply: did you mean this one? Nothing else in the pipeline can answer that, which is why the report lists them for a person to read rather than failing the export.
The defaults
The four substitutions
When art is missing, four specific base-game assets fill the gap.
| Missing | You get | What it looks like |
|---|---|---|
| A mesh | Models/Items/Wood | A wooden item. Loads and renders. |
| A material | Textures/Items/WoodMat | Wood texturing on whatever shape it is applied to. |
| An item icon | UI/Items/PotatoIcon | A potato. Without any icon an item loads but never draws. |
| A research icon | UI/Items/PotatoIcon | The same potato. |
The potato is doing more work than it looks. An item with no icon at all does not fall back to a blank square — it fails to draw, so the item exists, can be picked up and crafted, and is invisible in every list. Substituting something absurd and visible is far better than that, and it is why these constants exist rather than being left empty.
⚠ If you see wood and potatoes in game, the export told you. It is in the report every time, and it is the most common reason a first mod looks nothing like the author expected.
A principle
Silence is the truth
When your mesh cannot be read, the lane produces no material at all rather than a plausible one.
It would be easy to substitute a generic grey surface and carry on. The lane refuses, and its reasoning is worth quoting because it applies to far more than materials: substituting is for a preview; shipping needs the truth.
So a mesh that fails to load leaves the definition on its base-game material, which loads and renders. The consequence is that the symptom you see is "my model has the wrong texture" rather than an error — but the alternative is worse: a grey material that looks deliberate would have hidden the fact that your mesh never loaded at all.
.glb, so a material problem is usually a mesh problem
wearing a disguise.Shipping
Accounting for every file
Every asset in the exported mod appears in a provenance record. An asset with no origin stops the mod being signed.
The export writes .ashforge-provenance.json beside your assets, listing each file
and where it came from. It fills in the local path automatically; what it cannot know are the
fields that actually matter for release:
| Field | What it is for |
|---|---|
source | Filled in for you — the file on your disk it was built from. |
pack | The asset pack it came out of, if any. |
license | The licence you hold it under. Yours to supply. |
attribution | Who to credit. |
url | Where it came from. |
You supply these by putting a .ashforge-origin.json in the folder each asset was
downloaded into — once per folder, not once per file — and the export carries it
through. Art you made yourself still needs one; "I drew it" is a perfectly good origin, and the
gate only checks that an answer exists.
★ Note what this is and is not. It cannot tell whether you had the right to ship something. It checks that every file is accounted for, which makes an unlicensed asset a deliberate act rather than an oversight. That is the honest limit of what a tool can do here, and doing it well is worth more than pretending to do more.
In practice
Reading your own report
Three lines in UNMAPPED.txt tell you the state of your art, and
the placeholder section tells you exactly what is left to do.
| Line | Read it as |
|---|---|
ASSETS: this mod ships N icon(s) and N mesh(es) of its own |
What actually got carried. Compare against what you expected. |
ASSET AUDIT: N reference(s) into this mod's own namespace, all of them served |
Every reference pointing at your mod found its file. This is the line that confirms the lane worked. |
N shipped asset(s) have no origin record |
A blocker. Fix before you try to sign. |
Each line names how many things were substituted — "material on 5 items/buildings" — and a line appears only when a substitution actually happened. A mod that ships its own icon, mesh and material has no asset lines in that section at all.
So the counts are worth reading as numbers, not just as warnings: if you expected to ship twelve models and the report says a mesh was substituted on three things, you know exactly how much is left to do.
UNMAPPED.txt saying that, check the
dec instead: a <Material> holding <yourmodid>/something
means your material shipped and the warning was not about you.That is the habit this whole ladder comes down to: the report is generated, so read it, and the dec is the truth, so check it. Between those two files there is nothing about your art you cannot find out before a player does.
Course complete
What you now know
Check yourself
- Art lives in
<mod>/icons/and<mod>/models/, pointed at from a definition asicon:file.pngandmodel:file.glb. - Your mod's id is its asset namespace, and the loader maps it to the mod's
Assets/folder. - ★★ A reference carries no extension — the game appends
.pngor.glbaccording to the field. Adding one yourself breaks it silently. - Subfolders are preserved, not flattened, so same-named files in different folders cannot collide.
- Art is opt-in: a definition that points at nothing keeps a base-game placeholder, so an unfinished mod still loads.
- A mesh is copied unchanged; a texture is converted.
- The export prints an asset audit — how many references point into your namespace and whether each was served.
- The game cannot compress a texture at runtime, so whatever you ship is what occupies video memory.
- A loose PNG costs 349,524 bytes for one 256×256 icon against 87,408 compressed — four times, silently, per texture.
- Compress always and ship only the
.ashtex. The PNG is pure download weight. - The game asks for
.pngand the loader answers with the.ashtex. That is the only route to compressed textures, so both facts being true is not a bug. - Conversion saves twice: compressed for the GPU, then deflated for disk — 87,408 in memory, 38,800 on disk.
- A material is a separate small file that points at its own albedo texture, so one item can ship two images. No material is produced if the mesh cannot be read.
- ⚠ A storage building needs four meshes and a plant needs three, from one field, by naming convention, unchecked by the game — a missing one is a magenta placeholder.
- A placeholder is a deliberate outcome with three good reasons behind it — the report asks "did you mean this?", it does not accuse you.
- The four substitutions are wood, wood material and two potatoes. An item with no icon at all loads but never draws, which is why an absurd visible icon beats none.
- A mesh that will not load produces no material rather than an invented one — substituting is for a preview; shipping needs the truth.
- So "wrong texture" is usually a mesh problem in disguise.
- Every shipped asset needs an origin record or the mod cannot be signed. One
.ashforge-origin.jsonper source folder, and art you made yourself needs one too. - The gate checks that you said where a file came from, not that you had the right to ship it — an honest limit, done properly.
- In the report: ships N, audit all served, no origin record — and a placeholder section that lists only real substitutions, with counts, so a mod shipping its own art has no asset lines there at all.
That is the asset lane. Your mod's id is its asset namespace, a reference carries no extension, and an asset with no origin record cannot be signed — the export always succeeds, and signing is where it stops.
