Format Spec Authoring Guide¶
How to write and restructure the format specs in docs/fa/formats/.
Every spec follows one template — general reference for modders at the top, deep
technical spec below — and carries machine-readable YAML front-matter that feeds the
generated status matrix. tools/check_status.py enforces
everything on this page in CI (--check); the canonical vocabularies (section names,
enums) are constants in that script, and this page documents them.
python3 tools/check_status.py --self-test # checker's own test suite
python3 tools/check_status.py --check # what CI runs
python3 tools/check_status.py --write-matrix # regenerate STATUS.md after edits
The template¶
---
format: XXX # uppercase token == filename stem
name: Human Name # short; the H1 must match
extensions: [".XXX"]
category: graphics # see category table below
endianness: little # little | big | mixed | none (plain-text formats)
spec:
status: partial # complete | partial | stub
gaps: # required unless status: complete
- kind: re-static # re-static | re-gameplay | unrecoverable
issue: 54 # tracking issue (omit only for unrecoverable)
note: "short label shown in the matrix"
codec:
direction: round-trip # none | read | write | round-trip
byte_identical: true # round-trip only; true requires a proving test
lib: [lib/src/xxx.cpp] # every path is existence-checked by CI
commands: [xxx] # fx subcommand tokens (dispatch literals)
tests: [tests/test_xxx.cpp]
fuzz: []
fixtures:
synthetic: true # tests build their own inputs
real_manifest: false # extension appears in fa-extract.sha256
related: [LIB] # tokens; each must be linked in the body
---
# XXX — Human Name (.XXX)
Intro prose (required, no heading): what the format is, which .LIBs or install
paths contain it, container notes. This is the modder-facing overview.
## Tools
### fx
fx command examples. Required section whenever codec.commands is non-empty.
### Other Tools
External tools, free ones plain, paid ones marked `$`.
## File Layout
All multi-byte integers are little-endian unless noted. <- first line, binary formats
| Offset | Size | Type | Description |
|--------|------|------|-------------|
| `0x00` | 4 | u32 | ... |
Free H3 structure below (### Header, ### Records, ### Calibration, ...).
Text/DSL formats describe grammar and record structure here instead.
## File Inventory
Known instances in the game, counts, per-file tables. Optional.
## Engine Notes
Confirmed engine functions (VAs + FA.SMS symbols), runtime behavior. Optional.
## Round-Trip Notes
What re-encoding canonicalises, one-way rationale prose, parity caveats. Optional.
## Open Questions
Required iff spec.gaps is non-empty; forbidden when status: complete.
### 1. Short gap title
Evidence so far, what was tried, what would close it.
*Status: open — re-static (#54)*
## Related
**Formats:** [LIB](LIB.md) — ...
**Engine:** [architecture.md](../architecture.md) — ...
Only the intro prose, ## File Layout, and ## Related are unconditional.
A trivially small format needs nothing else — no empty boilerplate sections
(see FBC.md). H2s must come from the canonical set above,
appear at most once, and keep that relative order. LIB.md
is the full-complexity exemplar.
Vocabularies¶
Categories (front-matter category → section of
formats/README.md the spec must be indexed under):
| Token | README section | Token | README section | |
|---|---|---|---|---|
archive |
Archive | mission |
Mission & Campaign | |
graphics |
Graphics & Images | typedef |
Type Definitions (BRF DSL) | |
terrain |
Terrain & Maps | ui-overlay |
UI & Win32 Overlays | |
3d |
3D & Scene | system |
System & Config | |
audio |
Audio | installer |
Installer | |
video |
Video & Cutscenes | text |
Text |
Confidence markers (three levels, used everywhere in docs/fa/):
- confirmed — decompile evidence with the VA cited, or proven by a byte-identical round-trip
- inferred — consistent with all observed data, but not traced in the game executable
- unknown — unmapped
Region headings take a suffix (### Identity block (0x00–0xAF) — confirmed);
unknown fields get Type ? and a Description starting **Unknown**, optionally
followed by the hypothesis. Do not use the retired spellings (Status: unmapped,
unresolved, TBD, requires trace, High/Low).
Gap taxonomy (spec.gaps[].kind, from the roadmap):
re-static— Ghidra static analysis can answer it; issue under epic #54re-gameplay— needs the running game on the Windows bench; issue under #56unrecoverable— provably lost (document why in the Open Questions entry)
Every ## Open Questions entry is a numbered H3 ending with a status line that
names the gap's issue: *Status: open — re-static (#123)*.
Codec claims (codec.*): direction is the fx_lib state — none (no codec;
give issue pointing at the Phase-4 coverage work, or a rationale if none will
ever exist), read/write (one-way; rationale if by design, else issue for
the upgrade), round-trip (+ byte_identical). Once a codec is byte-identical
round-trip, rationale/issue are forbidden — there is nothing left to track.
All lib/tests/fuzz/gui paths and commands tokens are verified against
the repository; once all specs are converted, the reverse also holds (every
codec, test, fuzz harness, CLI command, and GUI editor must be claimed by a spec).
Offset tables — one style, markdown tables with backticked hex offsets:
| Offset | Size | Type | Description |. Record-relative offsets use +0x0E.
Types: u8 u16 u32 s8 s16 s32 f32 char[N] ?. State endianness once, in the
File Layout preamble; per-field suffixes (u16 BE) only for deviations.
BRF-DSL annotated listings of file content stay as fenced code blocks — they
are examples, not layout tables.
Explanatory diagrams (format specs)¶
Format specs may embed explanatory SVGs — byte layouts, mode trees, pipelines
(epic #341, issue
#345). They are editorial:
unlike the subsystem flow diagram below, nothing enforces their presence. When you
add one, it lives in docs/fa/formats/diagrams/, uses the same theme-aware CSS
recipe as the flow diagrams, and follows the shared byte-layout grammar so
diagrams read uniformly across specs (exemplar:
diagrams/lib-layout.svg):
- a vertical stack of field boxes, top = offset 0; a monospace offset gutter on
the left (
0x00; bracketed[offset]for positions known only at runtime); - each box: field name on the left, type · size on the right; variable-length regions are taller and state their size formula; unknown/reserved fields get a dashed outline;
- record expansions (one directory entry, one span) pop out beside the stack, connected by an arrow;
- color semantics match the flow-diagram palette: neutral = fixed header fields, amber = directory/index/selector structures, blue = payload data, green = engine-side consumption notes.
Subsystem docs (reconstruction program)¶
The FA.EXE reconstruction program (epic #209) documents
whole engine subsystems rather than file formats. These docs live directly in
docs/fa/ (not formats/), carry no YAML front-matter — their metadata is the
row in db/subsystems.csv —
and share the confidence markers and offset-table style above. check_status.py
enforces the structure once a subsystem's manifest status is active or
complete. Each doc has:
- a one-paragraph intro and a
> **Provenance:**blockquote (source dump, a link to the symbol database, and the confidence-marker reference — see objects.md); - a
## Functionstable (| VA | Symbol | Role |) whose VAs and names are checked against the subsystem'sdb/symbols/<slug>.csv(docs curate a subset; the DB is the full record, so the table cannot drift); - at least one theme-aware
flow diagram — copy the@media (prefers-color-scheme: dark)+:root[data-theme="dark"|"light"]+fill="context-stroke"recipe fromdiagrams/shape-selection.svg; ## Open Questions(numbered H3s ending in a*Status: …*line) and## Related.
Naming recovered symbols: names not in FA.SMS follow the subsystem's existing
prefix and casing (OBJ…, T_…, Setup…) so decompiled code reads uniformly;
provenance lives in the DB source column (sms/re), not the name. Definition
of done (enforced at status=complete): every code-referenced function in the
subsystem's VA ranges is named, and every referenced global is named or carries a
waiver row explaining why — see db/README.md.
Migration map (restructuring a legacy spec)¶
| Old section | Goes to |
|---|---|
| Overview / Location / Found in | intro prose |
| Format / Structure / File Layout / Header | ## File Layout |
| Calibration (BRF family) | ### Calibration under File Layout |
| Applications / fx commands | ## Tools |
| Confirmed Engine Functions / Playback Architecture | ## Engine Notes |
| Pending Trace / gap-status blocks | ## Open Questions + spec.gaps |
| Toolkit Roadmap | deleted — file the items as issues under the epic they serve |
| Related / Related Formats | ## Related |
Restructuring is content-preserving: no sentence is deleted unless it is
duplicated or migrated to a quoted issue. Review such diffs with
git diff --color-moved=dimmed-zebra. All 44 specs conform as of epic #44;
every spec (and any new one) must carry valid front-matter, and
--write-matrix must be re-run whenever front-matter changes — CI fails
otherwise, in both directions.
Worked front-matter for the odd cases¶
Every irregular shape in the corpus, so no conversion has to invent policy:
- Multiple extensions, one format — 11K:
extensions: [".11K", ".5K", ".8K"],commands: [audio]. - Multiple variant files, one format — CFG (
variants: ["EA.CFG", "IP.CFG"]) and DAT (variants: ["NET.DAT", "MODEM.DAT", "SERIAL.DAT"]); a variant's layout is an H3 under File Layout, never a second H1. - Sub-format variants — PIC:
variants: ["dense", "sparse", "jpeg"]. - Two specs, one codec — M and MM both claim
lib: [lib/src/mission.cpp]andcommands: [mission]; MM additionally claims its aliasmm. Shared paths are expected; claims are a many-to-many mapping. - Family umbrella — BRF:
extensions: []plusfamily: BRF; the seven member specs (OT NT PT JT SEE ECM GAS) each setfamily: BRF, claimlib: [lib/src/brf.cpp, lib/src/ot.cpp], their own dispatch token (commands: [pt]), andtests: [tests/test_brf.cpp]. - CLI-only handler — MUS:
direction: read,lib: [],commands: [mus](the parser lives incli/cmd_mus.cpp; lifting it into fx_lib is #101). - GUI-only viewer, no codec — FBC:
direction: none,issue: 107,gui: [gui/src/editors/vdo_editor.cpp]. - One-way by design — SH:
direction: read,rationale: "OBJ export is intentionally one-way (#48: no OBJ→SH encoder planned)". - Compiler pair —
directiondescribes fx_lib's ability on this format: AI isread(fx ai compilefully parses.AI; nothing writes it until the #102 decompiler), while BI isround-tripwithbyte_identical: false(fx ai compilewrites it,fx bi dumpreads it, dump→recompile is not byte-exact — #102). - Big-endian — XMI:
endianness: big(IFF-style chunk sizes); the File Layout preamble states it. - Project artifact, not game data — SMS (the recovered symbol map):
documented like any other format;
category: system. - No codec yet — the uncovered formats reference their Phase-4 issue: TXT/CFG/MNU/DAT → #104, DLG → #105, XMI → #106, FBC/BIN/CAM → #107, MT/PTS/RGN → #108, SSF/MC/HGR → #109, VDO → #55.
Front-matter YAML subset¶
The checker parses front-matter with a deliberately small stdlib-only parser.
Stay within: 2-space indentation, at most two nesting levels, scalars
(bare token, "quoted string", integer, true/false), inline lists of
scalars ([a, b]), and block lists of flat maps (the gaps shape). No
anchors, flow maps, multiline scalars, tabs, or inline comments. Full-line
# comments are fine. GitHub renders the block as a table at the top of the
spec; the docs site consumes it as
page metadata.