12. Meet the simulator¶
You will: leave the abstract stack behind and put your first words on a (simulated) Mindustry message block — using
PRINTFLUSH, and a.world.tomlsidecar that binds a name in your source to a block in the game. About ten minutes.You will need: the first nine chapters. You should be comfortable with definitions (
: … ;),S" …"strings, and
Part I taught you Forth as a language: a stack, words that consume and produce, branching, looping, state, factoring. Every exercise so far ran in a vacuum — numbers in, numbers out. That was the point. You now think in stacks.
Part II spends that fluency on the thing mforth is for: controlling
blocks in Mindustry. Nothing about the language changes. The stack is
still the stack; IF still branches; a definition is still a definition.
What changes is that some words now talk to a world — a simulated
Mindustry processor with blocks wired to it. This chapter introduces that
world and gets one string onto one block. The next two chapters teach you
to read the world (chapter 13) and act on it
(chapter 14).
The MockWorld¶
When you run an mforth program, it executes against a MockWorld: a
Python stand-in for a Mindustry logic processor and the blocks linked to
it. The MockWorld is deterministic — it behaves the same way every time —
which is exactly what lets mforth check grade simulator exercises the
same way it graded the arithmetic ones.
A real processor has links: message blocks, switches, vaults, drills,
anything you've connected to it in-game. The MockWorld models those
links too. But your .fs source never hard-codes an in-game block name.
Instead it refers to a stable mforth-name, and a side file binds that
name to the real block. That side file is the sidecar.
The .world.toml sidecar¶
Every <name>.fs may have a sibling <name>.world.toml — same basename,
same directory. It declares the blocks your program links to. Here is the
smallest useful one:
Read it as: "the name display in my source is a message block; in the
game it shows up as message1." The left side of [links.display] —
display — is the name your Forth code uses. The target = "message1"
is the in-game label. You bind by name, so re-linking blocks in a
different order never breaks your program. (There is a second, fragile
addressing mode by slot index; tutorials never use it. The
sidecar schema reference covers both.)
Why a side file instead of writing message1 directly in your .fs? So
the source stays portable: the same program drops onto any processor,
and only the one-line sidecar changes to match that processor's wiring.
Your first on-screen output¶
Two words put text on a message block:
PRINT ( v -- )queues a value into the processor's print buffer. It does not show anything yet — it's building up a message.PRINTFLUSH ( block -- )flushes the whole buffer onto a message block, replacing whatever was there.
So the pattern is always queue, queue, …, flush. Here is a complete
program. Save it as hello.fs:
and hello.world.toml beside it:
Run a single pass through the simulator:
Nothing prints to your terminal — the text went onto the simulated message block, not stdout. That's faithful: in-game, a message block shows text on the block, not in a console. To see what would land in-game, compile it instead:
You get exactly two mlog instructions — print "reactor online" and
printflush message1 — paste-ready for a logic processor. (Compiling is
chapter 17's topic; here it's just a peek.)
Buffer, then flush: the two-line readout¶
PRINT queues one value at a time, and you can queue several before a
single flush. A label plus a value is the everyday shape:
@mapw is a built-in that reports the map's width — in the simulator it
is a deterministic stand-in value (40), so the readout is reproducible.
(@mapw is one of many @-prefixed built-ins; the next chapter is all
about the ones that read blocks.) Two PRINTs, then one PRINTFLUSH:
the block ends up showing map width = followed by 40.
One thing worth pinning down now, because the checker depends on it: each
PRINT is its own unit of output. mforth check sees a list of
printed pieces — here "map width = " then "40" — not one concatenated
string. When an exercise expects two pieces, you write two PRINTs.
Exercises¶
Write each solution in its own .fs file, starting with the
\ @exercise <id> marker line shown in the prompt, then run
mforth check <file>. A green ✓ means your word behaves exactly as
asked. Stuck? mforth check --scaffold <id> writes a starter stub, and
mforth check --solution <id> reveals the reference answer.
These two exercises bundle their own sidecar — you don't write a
.world.toml; the checker supplies one that binds display to a message
block.
Exercise 12.1 — greet ( -- )¶
PRINT the string online and flush it to the message block display.
Exercise 12.2 — readout ( -- )¶
PRINT the label width=, then PRINT the map width @mapw, then flush
to display. Remember: two separate PRINTs.
You can now get text onto a block. But a controller that only writes
is half a controller — the interesting programs react. Next:
chapter 13, Reading the world, where SENSOR pulls a
block's properties onto the stack so your IFs have something real to
branch on.
(For the polished, end-to-end version of this same material — porting real wiki scripts, with side-by-side mlog — see Writing mforth for Mindustry. This series builds the Forth underneath it.)