AshForge Refinery · the whole tool, one course
It tells you what is wrong.
It does not stop you.
Refinery builds the asset index the rest of the suite reads. Almost nothing in it is a gate — it warns, records and carries on, and knowing exactly which one switch does refuse is the difference between a clean ship and a surprise at signing.
What it is
An indexer. It walks your mod's assets, records what it finds, and writes the index Motion and Echo both depend on.
Modules 1–4 are the index, 5–8 are what it will and will not stop, 9–12 are the ledger underneath.
The thing to internalise
It is a ledger, not a mirror. It records what happened rather than reflecting what is there now.
That one distinction explains most of its surprising behaviour.
Refinery is a focused tool. Its content is 12 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.
Classification
Type is the extension
An asset's type is decided by its file extension and nothing else — not by inspecting the file, not by what the importer managed to do with it. The whole map is nine entries long.
| Type | Extensions |
|---|---|
| Model | .glb .gltf |
| Texture | .png .jpg .jpeg .tga .exr |
| Audio | .wav .ogg |
| Unknown | everything else — including .fbx and .obj |
Case does not matter, so .PNG is a texture. What does matter is the gap in the
last row: the file dialog offers .fbx and .obj under "Models", and
the model importer accepts both — both verified — but the type detector has no case
for either.
So those two import successfully, appear in the index, and are labelled unknown.
Nothing is broken and nothing warns; they simply will not be found by anything looking for a
model. Convert them to glTF before importing and the problem disappears.
The contract
What an entry carries
Every successful import adds one entry. It is worth knowing each field, because between them they are everything the rest of the suite knows about your asset.
| Field | Holds | Why a consumer wants it |
|---|---|---|
name | The logical name, no extension. | How a mod refers to the asset. |
type | Lowercased: model, texture, audio, unknown. | Filtering — Motion looks for animations here. |
output | Relative to the assets folder. | Finding the file wherever the mod is. |
source | Where it came from. | Audit only. |
hash | A real SHA256 of the emitted file. | Telling whether it changed since last look. |
sizeBytes · preset | Size, and which preset produced it. | Provenance. |
The two doing the most work are output and hash. A relative output path means the index survives the mod being moved or shared; a real content hash means a consumer can tell a changed asset from an unchanged one without opening it. That pairing is how Vault does change detection.
name is what other tools show and what a mod refers to. It is taken at
import, so name your source files the way you want them referred to — renaming later
means re-importing.Omissions
What never reaches it
The index lists what exists, not what you attempted. An asset only earns a line by finishing.
| In the queue | In the index? |
|---|---|
Status done, with an output path | Yes |
Status error | No |
| Still pending or validating | No |
| Finished but with no output path | No |
Verified with a queue of four, of which two finished: the index held exactly two entries. The other two left no trace in it at all — not an entry marked failed, simply nothing.
Which means a queue that looks mostly successful can produce a much shorter index, and the queue is the only place the failures are visible. If a consumer cannot see an asset you are sure you imported, the index is the place to look first and the queue is the place to look second.
Consumers
Who reads it
The index sits at <mod>/assets/assets.index.json, and it is
read by more of the suite than any other file Refinery produces.
| Tool | Uses the index to |
|---|---|
| Motion | List a clip for every animation asset — see Two Sources, One Name. |
| Echo | Find audio to build banks and sounds from. |
| Atlas | Offer props to place. |
| Vault | Browse assets and detect what changed, via the hash. |
| Studio | Report what a mod contains. |
All of them go through the same shared reader, and so does Refinery when it merges — deliberately, so that what the writer sees is exactly what the consumers see rather than two parsers that could disagree.
It also means an asset's absence from the index is felt across five tools at once, which is why the failure modes on this page are worth recognising early.
| Symptom | What it means | Fix |
|---|---|---|
| A model does not appear in Motion or Atlas | It was a .fbx or .obj and is typed unknown. |
Convert to .glb and re-import. |
| An asset is missing from every tool | It is not in the index — it never finished. | Check the queue for its error. |
| A tool shows a stale version | The hash tells it nothing changed. | Re-import so a new hash is written. |
| Nothing was written at all | No valid mod root. | Open a mod folder first. |
Permissiveness
Nothing here is a gate
The word validation suggests a check that can stop something. Here it cannot. A texture that trips every check imports successfully, is emitted into the mod, and appears in the index like any other — verified.
What the checks produce is warnings on a successful import. They are advice attached to a result, not conditions on it, which makes them easy to miss precisely because nothing went visibly wrong.
That is a reasonable design for an asset pipeline — refusing an artist's file because it is 100 pixels wide would be worse. But it means the warnings column is the only place problems appear, and a run of thirty assets that all say "done" can still be carrying thirty warnings.
Power of two
One switch works
Import a 100×100 texture with Validate power-of-two textures on, then off, and compare the warnings.
| Texture | Switch on | Switch off |
|---|---|---|
| 100×100 | Warns about power-of-two | Silent |
| 128×128 | Silent | Silent |
So this one is real: the switch genuinely governs whether the check runs, and the check genuinely notices. Both imports succeeded either way — the switch controls the advice, not the outcome.
There is a second size check that is always on: a texture larger than Max texture size warns at import regardless. Note that the warning and the resizing are different steps — the importer notices the size, and the optimisation processor is what actually changes the pixels. You can get the warning without the resize if resizing is switched off.
The other one
One switch does not
Run the same comparison for Validate meshes have UVs and there is nothing to compare. On and off produce identical results: same success, same warnings, same everything.
The setting is read and the code does enter its branch — and the branch is empty, its one line commented out with a note that a full UV probe is expensive. So the toggle survives in the interface and in every saved preset, and has no effect on any run.
This matters more than a dead switch usually would, because an unwrapped model is a real problem and this is the control that appears to guard against it. It does not. Canvas's own ladder covers what an unwrapped model does once you try to paint it — and that is where you will find out, not here.
Failure
What actually stops an asset
Exactly three things mark an asset as failed, and none of them is a validation check.
| Cause | Meaning |
|---|---|
| The source file is not there | Moved, renamed or deleted since queuing. Verified. |
| The importer failed | The file could not be opened or parsed as its type. |
| A processor failed | Optimisation, LOD generation or emit could not complete. |
All three produce an error status, and — from tier one — an errored asset gets
no line in the index at all. So the queue is the only record that it was ever attempted.
| Symptom | What it means | Fix |
|---|---|---|
| Everything says done, but assets look wrong | Validation warns and never refuses. | Read the warnings; status only means "finished". |
| An unwrapped model imported cleanly | The UV switch does nothing. | Check UVs in your modelling tool or in Canvas. |
| A texture warned about size but was not resized | Warning and resize are separate steps. | Turn on texture resizing. |
| An asset vanished from the run | It errored, so it is not in the index. | Check the queue — source missing, import or processor failed. |
History
The bug this shape exists for
The index used to be rebuilt from the current run's queue alone and written over whatever was there. That sounds harmless until you notice when the queue is empty.
Within one sitting the queue survives, so a second run still held the first run's assets and nothing looked wrong. But the queue starts empty on launch. So importing on Monday and again on Tuesday meant Tuesday's run wrote an index containing only Tuesday's assets — and Monday's files sat untouched on disk, unlisted.
Because five tools read this file, those assets simply stopped existing for all of them at once, and only came back by being imported again. The most ordinary workflow there is triggered it, and nothing failed while it happened.
Merging
How the merge works
Two rules, and both matter when you re-import something you have imported before.
| Rule | Effect |
|---|---|
| Prior entries are carried forward in their original order, and this run appends after them | The file diffs cleanly rather than reshuffling every line. |
| Duplicates are resolved by output path, and this run wins | Re-importing replaces the entry — hash, size and preset with it. |
The key is the output path, not the name. Re-emit the same path and the old entry is superseded rather than duplicated — verified, including that the recorded hash changed to match the new file. That is precisely what a consumer's change detection is watching, so the replacement is the mechanism by which "this asset was updated" travels.
It also means two different sources emitting to the same output path are one entry, with the most recent run's version winning. And the same source emitted to two different paths is two entries.
The consequence
Ledger, not mirror
If entries are only ever added or replaced, then nothing removes them — and nothing does.
Delete an emitted file by hand and run another import: the deleted asset is still listed, verified, alongside the newly imported one. No step walks the assets folder to confirm that listed files still exist.
This is deliberate rather than an oversight, and it is a genuine design question — is the index a mirror of the folder, or a record of everything that was ever produced? The tool takes the second view, consistently: it also tolerates a file it cannot hash rather than failing.
What it means for you is simple. The index is a record of imports, not an inventory of files. If you remove an asset, remove its entry too, or the tools that read it will keep offering something that is not there.
Damage
When the index is unreadable
A corrupt index is the one case where the merge cannot do its job, and the behaviour is a deliberate compromise.
| What happens | Why |
|---|---|
| The run does not fail | A corrupt index must not brick every future import. |
| A fresh index is written with this run's assets | You keep working. |
| It says so in the log, naming what is being dropped | Silence would be worse than the loss. |
| The entries it could not read are gone | They could not be recovered to carry forward. |
All four verified: after deliberately corrupting an index, the next run succeeded, the new asset was listed, and the previously-listed one was not. Nothing failed and something was lost — which is exactly why the log line exists.
Recovery is straightforward and manual: re-import the assets that vanished. Their files are still on disk; only the ledger forgot them.
| Symptom | What it means | Fix |
|---|---|---|
| Assets from an earlier session are missing | A corrupt index was rewritten fresh. | Check the log, then re-import them. |
| A listed asset fails to load | Its file was deleted; the entry was not pruned. | Remove the entry or re-import. |
| Re-importing added a duplicate | It emitted to a different output path. | Dedupe is by path, not name — match the path. |
| A consumer shows a stale version | The hash did not change, so nothing looked updated. | Re-import so a new hash is written. |
Course complete
What you now know
Check yourself
- Refinery's real output is
assets/assets.index.json— the record five other tools read. - Type is decided by extension alone: two model, five texture, two audio extensions,
and everything else is
unknown. .fbxand.objimport but are typedunknown, so consumers filtering by type will not see them.- An entry carries name, type, relative output, source, a real SHA256, size and preset.
- Relative output plus a content hash is what lets a consumer find an asset and tell whether it changed.
- Only assets that finished appear. Errors leave no trace in the index at all.
- A missing or invalid mod root is refused rather than guessed.
- Validation warns and never refuses. A texture failing every check still imports, emits and indexes.
- Warnings appear on successful imports, so "done" answers "did it finish", not "is it right".
- Validate power-of-two textures works — verified on and off, against both a power-of-two and a non-power-of-two texture.
- Validate meshes have UVs does nothing at all. Its branch body is commented out; the switch is live in the UI and in presets and affects no run.
- The Max texture size warning and the actual resize are separate steps.
- Defaults: both validation switches on, resizing on at 2048, and mipmaps, compression, ORM packing, glb re-export and LODs all off.
- Only three things fail an asset: a missing source file, a failed import, a failed processor.
- Each run merges into the index rather than replacing it — which is what makes work across several days add up.
- It used to replace, so importing in two sittings lost the first sitting from the index while the files stayed on disk.
- Carried entries keep their order and this run appends, so the file diffs cleanly.
- Dedupe is by output path and this run wins, replacing hash, size and preset — which is how "this changed" reaches a consumer.
- Entries are never pruned. A deleted file stays listed; the index is a ledger of imports, not an inventory of files.
- A missing index is just a first run; an unreadable one is survivable but loses what it listed, with a log line saying so.
- The merge reads the existing index through the same reader every consumer uses.
That is Refinery. It underpins Motion's and Echo's asset resolution, so a problem in either of those is worth checking here first.
