AshForge Catalyst · the whole tool, one course
Generated content that comes out
the same every time.
Catalyst produces items and quests from seeds and rules. It is a small tool with a handful of surprises, so this is one course rather than a ladder — twelve modules from your first seed to a bundle the game can read, in the order you actually meet them.
What it is
A generator: give it a seed and a set of rules, and it produces content. The same seed always produces the same result, which is the whole point.
Modules 1–4 are the generator, 5–8 are rules, 9–12 are getting it out.
The two things that bite
A rule can fail silently in two different ways, and weight is inverted from what the word suggests.
Both are in modules 5–8. If you read nothing else, read those.
Catalyst is under 2,000 lines. Its content is twelve things worth knowing, not three escalating tiers — splitting them into beginner, intermediate and advanced made the second and third look like they needed the first, and they do not. Read straight through, or jump to the module that matches your problem.
Determinism
Identical, not similar
Press generate. Press it again without touching anything. The second list is not a fresh roll that happens to look alike — it is the same content, name for name, value for value, tag for tag.
Verified by fingerprinting entire runs and comparing them: every field of every output matched, across all four generator types. Change the seed by one and everything changes.
This is the difference between a generator and a randomiser, and it is what makes procedural content something you can ship. A seed is a recipe you can write down. The content does not need storing, because it can always be produced again — and if you lose the seed, no amount of re-rolling will recover that content.
Inputs
The whole recipe
"Seeded" is often taken to mean the seed is the only input that matters. Here it is one of four, and the other three are just as load-bearing.
| Input | Changing it… | Must be recorded? |
|---|---|---|
| Seed | changes everything | Yes |
| Complexity | changes the content, not just its depth | Yes |
| Templates | changes the vocabulary everything is built from | Yes |
| Count | changes how much you get | Yes |
Complexity is the one worth calling out, because its name suggests a dial that makes the same content more elaborate. It does not: the same seed at complexity 1 and complexity 4 produced different content, not the same content at two levels of detail.
So "seed 42" is not a reproducible reference to anything. Seed 42, Loot, count 5, complexity 1, no templates is.
Vocabulary
Templates replace
Templates are your own word list. The important thing about them is a single verb: they replace the built-in vocabulary rather than adding to it.
Supply two templates and every output is built from those two — verified, a four-output run in which every name came from the supplied pair. The stock word list is a fallback used only while you have supplied none.
That makes templates far more powerful than they look, and far easier to over-narrow. Two templates and a count of twenty will give you twenty variations on two words. If you want your words alongside the built-ins, you have to include the built-ins yourself.
Tolerance
What it will not refuse
Catalyst is built not to fail. Every input it does not understand becomes something reasonable rather than an error, which is convenient and occasionally hides a mistake.
| You give it | You get |
|---|---|
Count 0 or negative | One output. It cannot produce nothing. |
| A type it does not recognise | Generic content, no error. |
| No templates | The built-in vocabulary. |
The middle row is the one to keep in mind. A misspelled type does not fail — it falls through to a generic generator and produces perfectly plausible-looking content that is not the kind you asked for. Verified: a type of "Sandwiches" produced three named outputs.
| Symptom | What it means | Fix |
|---|---|---|
| The same seed gave different content | Count, complexity or templates also changed. | All four are the recipe; match all four. |
| Output is suddenly repetitive | Templates replaced the vocabulary. | Add more templates, or remove them all. |
| Content is generic and vague | The type is not one of the four recognised. | Check the spelling; it fell through to the fallback. |
| Asked for none, got one | Count is clamped to at least 1. | Expected — delete the generator instead. |
The shape
What a rule is
A rule has three fields and they are evaluated in a fixed order, once the content already exists.
| Field | Decides | Evaluated |
|---|---|---|
| Weight | whether the rule runs at all | once, for the whole rule |
| Condition | which outputs it applies to | once per output |
| Effect | what it does to them | once per matching output |
Rules run after generation, over the finished list, so they cannot influence what was generated — only edit it. And because the weight roll comes from the same seeded source as the content, rule firing is deterministic too: the same seed makes the same rules fire on the same outputs, verified.
That keeps tier one's promise intact. Adding rules does not make a generator unreproducible.
Failure
The two silent failures
Both halves of a rule are matched against a fixed vocabulary, and both respond to anything outside it in exactly the same way: by doing nothing, and saying nothing.
| What you wrote | What happens | What you see |
|---|---|---|
| A condition outside the vocabulary | Matches no output. | Nothing changes |
| An effect outside the vocabulary | Applies to nothing. | Nothing changes |
| An empty effect | The rule is skipped entirely. | Nothing changes |
Three different mistakes, one indistinguishable outcome. There is no error, no warning and no row highlighted — the run simply looks as though the rule is not there.
TYPE=LOOT works
perfectly. The effect is only trimmed, so Add_Tag:marked does nothing
at all — both verified, in the same rule. One half forgives your capitals and the
other does not, and nothing in the interface distinguishes them.Drill01
Find the broken half
- Start from
always+add_tag:markedand confirm it works. - Break only the condition. Note that nothing changes.
- Restore it and break only the effect. Note that nothing changes.
Done when you always test a rule by changing one half at a time, because the symptom cannot tell you which half is wrong.
Drill02
Prove the case rule to yourself
- Put the condition in capitals. It still works.
- Put the effect in capitals. It stops working.
Done when you write effects in lower case by habit.
Weight
Weight is inverted
Weight looks like a probability, and mostly behaves like one — except at the value everyone reaches for first.
| Weight | Behaviour |
|---|---|
1 | Always fires. |
0.5 | Fires or does not, decided once for the whole rule, by the seed. |
0 | Always fires. The roll is skipped entirely. |
| Negative | Always fires, for the same reason. |
The chance check only runs when the weight is above zero. At zero it is bypassed, so the rule applies unconditionally — verified on every output, and for negative weights too. The value that reads like "off" is the one value that cannot be stopped.
The middle row matters as well. A partial weight is rolled once per rule, not once per
output, so a rule at 0.5 does not tag half your list — it tags all of it or none of
it, depending on the seed. If you want variation across the list, that is what a condition
like index even is for.
Reference
The whole vocabulary
Both lists are short and closed. Anything not on them does nothing, so this is the reference worth keeping beside you.
| Conditions | Matches when |
|---|---|
always | Every output. Also what an empty condition means. |
complexity>N · complexity<N | The generator's complexity passes the test. |
type=X | The generator's type is X. |
rarity=X | That output's rarity is X. |
value>N · value<N | That output's value passes the test. |
index even · index odd | Its position in the list. Splits a run in half. |
| Effects | Does |
|---|---|
add_tag:X | Appends a tag. |
add_step:X | Appends a step, which is what the flow graph draws. |
add_objective:X | Appends to objectives AND steps — it appears twice. |
add_reward:X | Appends a reward. |
set_rarity:X | Overwrites rarity with the literal text. |
boost_value:N | Multiplies the value and rounds it. |
add_detail | The only effect with no argument. |
| Symptom | What it means | Fix |
|---|---|---|
| A rule does nothing at all | Unknown condition, unknown effect, or empty effect. | Change one half at a time against the lists above. |
| A rule works for someone else and not you | The effect has a capital letter in it. | Effects are case-sensitive; conditions are not. |
| A rule you set to weight 0 keeps firing | Zero skips the roll — it means always. | Clear the effect to disable it. |
| A 0.5 weight tags everything or nothing | One roll per rule, not per output. | Use index even to split a list. |
| An objective appears twice | add_objective writes to two lists. |
Expected — use add_step for the graph alone. |
Destination
Two exports, one read
Catalyst writes two files and they are not alternatives — they have different destinations and different lifespans.
| Bundle | Runtime export | |
|---|---|---|
| Lands in | <mod>/export/ | the tool's own output folder |
| Named | catalyst.bundle.json, always | timestamped, one per press |
| Found by discovery | Yes | No |
The stable name is a deliberate choice and worth understanding. A merge wants this tool's current bundle; a folder accumulating dated copies would give it several and no way to choose. So exporting three times leaves one file — verified — and that file is always the latest.
The runtime export is the opposite: timestamped, accumulating, and outside the mod where nothing scans. It is a record for you, not a delivery.
export/catalyst.bundle.json inside your mod.
That one file being present is the whole test, and it is the same test Echo's ladder ends on
— the two tools were wired the same way for the same reason.Freshness
The bundle regenerates
Exporting does not serialise the list on your screen. It runs every generator again and exports what comes out.
Verified by leaving a stale output in the project and exporting: it did not appear in the bundle, and three freshly generated items did. The file always matches the generator's current settings, never a list left over from an earlier press.
This is tier one's promise cashed in. Because generation is deterministic, re-running it at export time costs nothing and guarantees consistency — and it means the seed, not the output list, is the source of truth right through to the file. There is no way for the bundle to disagree with the generator that produced it.
The ceiling
Items travel, quests stop
A Loot generator becomes item definitions; a Quest generator becomes quest definitions. Both land in the same bundle, correctly formed — verified, three items and two quests from one export.
| Content | Becomes | Reaches the game? |
|---|---|---|
| Loot, Terrain, Dialogue, generic | Item definitions | Yes — through an emitter that already exists |
| Quest | Quest definitions | No. The game has no quest definition at all. |
Catalyst's items are unusual in the suite: they are already the shared contract, so nothing has to translate them. That is why its route was described as plumbing rather than work — the content was always the right shape, and only the destination folder was wrong. That is now fixed.
Its quests are the opposite, and the reason is structural rather than unfinished. Among the game's 69 definition types there is no quest, dialogue or lore definition. Not "no clean mapping yet" — none exists. So quests reach a player only through a mod assembly: code that reads your generated quests and implements the behaviour itself.
Which makes generated quests genuinely useful and worth generating — as input to something you write, not as content you ship. Knowing which of the two you are producing is the whole lesson of this tier.
Medium. A quest
is never exported half-built, which matters precisely because something you write will be
reading it.The habit
The order that works
Generator to merged bundle
- Open the mod folder first. the bundle export refuses without one, and the runtime export goes where nothing reads
- Record the whole generator panel, not just the seed. count, complexity and templates are all part of the recipe — tier one
- Check each rule actually fired. an unknown condition, an unknown effect and a mis-cased effect all do nothing silently — tier two
- Regenerate and review, then export. the bundle re-runs generation, so it exports the settings rather than the screen
- Confirm
export/catalyst.bundle.jsonexists, and know which half you shipped. items have a route into the game; quests need code you have not written yet
| Symptom | What it means | Fix |
|---|---|---|
| The export succeeded but nothing merged | The file is outside the mod. | Check for <mod>/export/catalyst.bundle.json. |
| The bundle export refused | No mod folder is open. | Open one — a bundle outside a mod cannot be merged. |
| The bundle differs from what was on screen | It re-ran generation from the settings. | Working as designed; regenerate before reviewing. |
| Generated quests never appear in game | No quest definition exists in the game. | They need a mod assembly to consume them. |
| Only one bundle after several exports | One stable name, deliberately. | Expected — it is always the latest. |
Course complete
What you now know
Check yourself
- A generator is a seeded function: the same inputs produce identical content, field for field, forever.
- All four generator types are deterministic, not just the default.
- The recipe is seed + count + complexity + templates. The seed alone reproduces nothing.
- Complexity changes the content, not the level of detail of the same content.
- Templates replace the built-in vocabulary, so one template means a vocabulary of one.
- Count is clamped to at least 1 — a generator cannot produce nothing.
- An unrecognised type produces generic content rather than an error, which looks like success.
- The thing worth recording is the generator's settings, not its output.
- A rule is weight, condition, effect, run after generation over the finished list.
- Rules cannot add or remove outputs — count is settled before any rule runs.
- Rule firing is seeded and deterministic, so rules do not break reproducibility.
- An unknown condition matches nothing; an unknown effect does nothing; an empty effect skips the rule. All three look identical.
- Conditions are case-insensitive and effects are case-sensitive, in the same row.
- Weight 0 — or negative — means ALWAYS, because the roll is skipped. To disable a rule, clear its effect.
- A partial weight is one roll for the whole rule: all outputs or none, never half.
- The vocabularies are closed: nine conditions, seven effects, and nothing else is understood.
- Catalyst writes two files with different destinations: a bundle inside the mod, and a runtime export outside it that nothing reads.
- The bundle has one stable name, so a merge always knows which file is current.
- The bundle export refuses without a mod folder rather than writing somewhere useless.
- Exporting re-runs generation, so the file matches the generator's settings and never a stale list — the seed is the source of truth all the way to disk.
- Every non-Quest generator becomes item definitions, which are already the shared contract and flow through an existing emitter.
- Quest generators become quest definitions with nowhere to go — the game has no quest, dialogue or lore definition at all.
- Generated quests are still worth making, as input to code you write, and they are exported complete so that code can rely on them.
That is Catalyst. ⛔ One thing to carry away above the rest: its items reach the game and its quests do not — there is no quest definition type for them to become. Generate quests to inform what you build; do not plan to ship them.
