The Project File
fantasm.toml holds the settings a build needs, so they live with the source instead of in whatever shell script or just recipe happens to invoke the assembler. A project with one is built by typing fantasm and nothing else.
The file is optional. Everything in it has a command-line equivalent, and a source file assembles perfectly well without one.
Getting one
fantasm init asks four questions — project name, target machine, output format, entry-point filename — and writes both fantasm.toml and a stub main.asm. It refuses to overwrite either unless you say so, or pass --force.
Answer the questions on the command line to skip the prompts:
fantasm init --name starfield --target zxnext --format nex --main src/main.asmSetting FANTASM_INTERACTIVE=false suppresses the prompts entirely and takes the defaults for anything not given, which is what you want in a script.
Finding it
fantasm build in a directory containing fantasm.toml uses it. That is the ordinary way to build.
⛔ The bare word fantasm prints a usage summary and builds nothing, project file or not. Any argument at all is enough — fantasm build, fantasm -n, fantasm --all — but no arguments means no build.
Point at one elsewhere with -p/--project:
fantasm build -p ../shared/fantasm.tomlAuto-detection only happens when you name no source file. fantasm build one.asm in a directory with a fantasm.toml ignores the file — pass -p if you meant to use both.
A complete example
[project]
name = "starfield"
version = "0.3.1"
main = "src/main.asm"
output = "dist/starfield.nex"
[assembler]
target = "zxnext"
format = "nex"
include_dirs = ["src", "lib"]
warnings = true
gc_modules = true
[defines]
DEBUG = 1
MAX_STARS = 64
BUILD_ID = "nightly"
CHEATS = false
[artifacts]
labels = "dist/starfield.labels"
sld = "dist/starfield.sld"
[tasks]
pre_build = ["mkdir -p dist"]
post_build = ["echo built"]Every table is optional, and so is every key in it.
[project]
| Key | Meaning |
|---|---|
name | The project’s name. Also defined as __NAME__ in your source. |
version | The version. Also defined as __VERSION__. |
main | The entry-point source file. |
output | Where the assembled image is written. |
__NAME__ and __VERSION__ are text your program can assemble, not just labels for the build:
banner:
dz __NAME__, " v", __VERSION__SIZEOF answers their length in bytes, and EQU can join them with +:
BANNER equ __NAME__ + " v" + __VERSION__
dz BANNERUsing text where a number is required is an error (E1075) rather than a silent zero, so ld a, __NAME__ is refused.
[assembler]
Everything here maps to a command-line flag. Anything omitted takes its default.
| Key | Values | Meaning |
|---|---|---|
target | zx16, zx48, zx128, zxnext | The machine. zxnext enables Z80N automatically. |
ram | 2mb, 1mb, 512k, or a bare number in KiB | RAM variant. Defaults to the target’s maximum. |
format | bin, hex, sna, nex | Output format. |
origin | address | Where assembly starts, if the source has no ORG. |
max_code_size | bytes | Refuse a build emitting more code than this. |
include_dirs | list of paths | Where INCLUDE and INCBIN look. |
z80n | bool | Z80N extended instructions. Implied by target = "zxnext". |
cspect | bool | CSpect’s exit and break pseudo-ops. |
gc_modules | bool | Discard MODULE code the program never reaches. |
case_insensitive | bool | Treat Loop and loop as one label. |
warnings | bool | Report unused labels and the rest of the W series. |
verbose | bool | Per-file progress and timings. |
ram narrows the paging range rather than describing the hardware. Setting ram = "512k" on a Next makes a bank beyond that range an assembly error — 512K is 32 banks, so bank 32 is refused — and a program that must run on a 512K machine fails at build time instead of on the desk of whoever has one.
max_code_size limits how much code there is, not the address it reaches. A program that ORGs at $8000 and emits 2 KB uses 2 KB of the budget, not 34 KB of it. The machine’s own ceiling still applies on top — zx16 refuses anything above $8000.
An unknown target is a warning, not an error. target = "zx81" builds, having told you once. Check the spelling if a build behaves as though the setting never took.
[defines]
Constants available to every file in the build, exactly as -D would define them.
[defines]
DEBUG = 1
MAX_STARS = 64
BUILD_ID = "nightly"
CHEATS = false- Numbers are defined as written.
- Strings become text constants, assemblable with
dbanddz. - Booleans become
1and0, soCHEATS = falsegives you0— usable withIFand withld c, CHEATS.
Names are case-sensitive unless case_insensitive is on.
[artifacts]
| Key | Meaning |
|---|---|
labels | Write a symbol list here. |
sld | Write Source Level Debugging data here, for a debugger to consume. |
labels only lists symbols marked GLOBAL. The file is empty until something is exported, which is intended rather than broken — the .sld file is what a debugger reads. A GLOBAL constant holding text is written as NAME = "text" rather than as an address.
[tasks]
Shell commands run around the build.
[tasks]
pre_build = ["mkdir -p dist"]
post_build = ["nexdeploy dist/starfield.nex"]Each string is handed to sh -c (or cmd /C on Windows). A command exiting non-zero stops the build and reports its exit code, so a failed generator does not quietly produce an image built from stale inputs.
-v prints each command before it runs.
⛔ Do not drive a second fantasm run from pre_build. It restates flags this file already holds, and the two drift apart silently. Use Building Several Images instead.
Paths
Every path in the file resolves against the directory holding it, not against wherever you happened to be standing. fantasm -p ~/projects/game/fantasm.toml from anywhere builds the same thing.
Paths given on the command line resolve against the current directory, as you would expect.
Resolving a conflict
- The command line beats the file. Any flag you pass overrides the same setting in
fantasm.toml. - Every valueless flag has a
--no-form —--no-z80n,--no-cspect,--no-case-insensitive,--no-warnings,--no-verbose,--no-gc-modules— for turning off something the file turned on. Without it,gc_modules = truecould only be undone by editing the file. The last flag given wins, so--gc-modules --no-gc-modulesis off. - Saying nothing is not saying
false. A key left out takes its default; one writtenfalseis a choice you made. The same holds on the command line:-O 0is the origin zero and beats anoriginin the file, rather than reading as “unset”. include_dirsaccumulates.-Iadds to the file’s list rather than replacing it, with command-line directories searched first. Everything else replaces.formatbeats the output extension, and-fbeatsformat. An output named.binwithformat = "nex"is written as a NEX.
Adding your own tables
fantasm.toml does not complain about anything it does not recognise, so a project can keep its deployment settings in the same file:
[assembler]
target = "zxnext"
[spectnext]
deploy = "dist/starfield.nex"⛔ The cost is that a typo is silent. gc_modues = true is accepted and does nothing. A setting that appears to have no effect is nearly always spelt wrong — check it against the tables above before looking anywhere else.
Several images
A project that builds more than one file — a payload and a loader that incbins it, a release and a debug variant — declares each as a [[target]]. See Building Several Images.