Writing pages
The wiki is the reference for how these games work. Write it as an engine team writes its own documentation: what the engine does and why, then the exact detail, with every statement marked by how surely it is known.
Where a page goes
Section titled “Where a page goes”src/content/docs/ engines/<engine>/index.md the engine's overview engines/<engine>/<system>/<topic>.md a topic of one system (archives, geometry, ... see How this wiki works) games/<game>.md a game's hub concepts/<concept>.md an idea across engines methods/<method>.md a way of finding things out- By engine, never by game. A fact about Sekiro’s meshes goes in
engines/fromsoft/geometry/, with:only[sekiro]if Elden Ring differs. Two games that do one thing two ways get one page with both, not two pages. - One topic per page, named for the thing (
flver.md,vertex-formats.md,bhd5-bdt.md), not for a task or a date. A page that passes ~600 lines splits by sub-topic. - The engine and system come from the folder. Folders under an engine are its systems, from the fixed list in
src/data/engines.yaml. A new game or engine is a line insrc/data/games.yamlorengines.yaml. - File and folder names are lower-case words joined by
-.
Frontmatter
Section titled “Frontmatter”---title: BHD5 and BDT archivesdescription: "One sentence: what the page covers."games: [sekiro, elden-ring] # optional: defaults to every game of the enginesources: # outside work the page relies on (tools, their source, community docs) - title: SoulsFormats url: https://github.com/JKAnderson/SoulsFormats note: BHD5 reader; the field names used hereimplementations: # optional: tools that implement what the page describes (shown small at the end) - name: fromsoft-formats (bhd.rs) url: https://github.com/Kalmarv/fromsoft-formats/blob/main/src/bhd.rs---The build checks every value: unknown game ids, engines, systems and malformed fields fail it. Quote a value that
contains : (YAML reads it as a nested mapping otherwise).
How a page reads
Section titled “How a page reads”- Start with what the thing is for and how the engine uses it, in a short paragraph a newcomer can follow.
- Then what the games share: structure, layouts, rules, values, each with its badge. One layout for a structure
the games share; a field that differs is limited to its games inside it (
gameson the field), not split into a second layout. - Then a section per game (
## Elden Ring,## Sekiro), in release order, for what is that game’s alone: anything longer than a sentence or two, such as another container version, an extra stage or a different file set. A game’s section holds only how it differs from the shared part and what only it has; it never repeats the shared part. A game with nothing of its own has no section. - Open questions last, or beside the section they belong to.
Where each kind of difference goes:
| Difference | Where |
|---|---|
| One sentence or one table row (another constant, a flag one game uses) | Inline, where it belongs, with a scope chip or games on its badge |
| Values that line up field by field across the games (versions, limits, counts) | A table in the shared part with a column per game |
| A field one game adds or reads differently | games on that field in the shared layout |
| More than a couple of sentences, or a structure only one game has | That game’s section |
A reader looking for “what is special about Sekiro” reads the shared part once and then Sekiro’s section, not every paragraph for its chips.
Write in plain sentences and tables. State facts in the present tense about the game (“the record is 40 bytes”), not as history (“we found that…”). No first-person narrative, no dates of discovery, no task status.
What the wiki does not say
Section titled “What the wiki does not say”- Who found something, or which internal tool, repository, test or agent did. Say how it is known (the method
and scope), not where in our code it lives. Our tools may appear under
implementations:and nowhere else. - Local paths, machine names, account names, keys or secrets. Say where a file lives inside the game
(“
regulation.binbeside the executable”) and describe where to get a key (“published by UXM”) without printing it. - Plans, progress, to-dos and conversion choices. The wiki records how the game works, not what a converter decided to do with it.
Shader disassembly, byte values, constants and formulas are fine: they document how the engine works. Do not paste whole game files or large blocks of game data.
Confidence badges
Section titled “Confidence badges”Put a badge after every statement of fact, at the end of its sentence or table cell. See Confidence for what earns each level.
| Write | Shows |
|---|---|
:proven |
Proven |
:observed |
Observed |
:sourced[SoulsFormats] |
Sourced · SoulsFormats |
:inferred |
Inferred |
:unknown |
Unknown |
:observed{games="sekiro"} |
Observed · Sekiro |
:proven[every entry]{games="sekiro elden-ring"} |
Proven · Sekiro, Elden Ring · every entry |
The text in [...] is a short scope note. {games="..."} limits the badge to those games; without it, a badge counts
for every game the page applies to. Every badge feeds the coverage page.
A paragraph that makes one claim needs one badge. A table of facts takes a badge per row (a Confidence column).
Which games a statement applies to
Section titled “Which games a statement applies to”| Write | Shows | Means |
|---|---|---|
:only[elden-ring] |
only in Elden Ring | Elden Ring does this; the others don’t |
:since[sekiro] |
since Sekiro | From this game on (in release order) |
:not[sekiro] |
not in Sekiro | Every game of the engine but these |
:in[sekiro elden-ring] |
in Sekiro, Elden Ring | These games, when the page covers more |
These say where a statement applies; the badge says how surely. They are separate on purpose.
Byte layouts
Section titled “Byte layouts”A structure’s fields go in a fenced block with the language layout, written in YAML. It is drawn as a byte grid and
a field table, checked (no overlapping fields, sizes that fit, known levels and games), and counted on the coverage page.
```layouttitle: BHD5 headersize: variable # a byte count, or a description when it variesgames: [sekiro] # optional: the games this layout is for (default: the page's)note: Little-endian when the flag at 4 is 0xFF.fields: - {at: 0, size: 4, type: "char[4]", name: magic, value: "BHD5", level: proven} - {at: 4, size: 1, type: u8, name: endianness, level: proven, note: "0xFF little-endian, 0 big-endian"} - {at: 5, size: 1, type: u8, name: unknown, level: unknown, note: "0 or 1"} - {at: 28, size: "salt length", type: "char[]", name: salt, level: sourced}```atandsizeare numbers (bytes) or, for variable ones, a short description.levelis the confidence in the field’s meaning. A field whose value is always the same but whose purpose is not known isunknownwith a note giving the value.gameson a field limits it to those games; fields for different games may then share offsets.- Bytes inside the stated size that no field covers are drawn dotted as “not described”. Describe them as
unknownfields when you know they are there but not what they are: that counts as accounted for.
Open questions
Section titled “Open questions”:::open[What is the boolean at offset 5?]It is 0 or 1 in every index; nothing seems to depend on it.:::Pictures
Section titled “Pictures”Pictures of what a game draws (a pass’s render targets, a frame’s stages) go in a folder beside the page whose name
starts with _ (_frames/), as web-sized files (webp, jpg, png or avif, under 600 KB each). Never put game files there:
no textures, models or shader bytecode, only pictures rendered from a frame. Each picture is a paragraph: the image,
linked relatively with alt text, then its caption, which says what is shown and how it was made displayable (the
tone-mapping operator for HDR, how depth was linearised, how packed channels are shown).
:::slides steps through pictures one at a time (the buttons, the range or the arrow keys; without JavaScript they
stand one under another). :::pass shows a pass’s inputs, a --- line, then its result:
:::slides[The G-buffer, target by target]Target 0, RGBA8 sRGB: albedo, shown as stored.
Target 1, RGB10A2: normals, mapped from -1..1 to 0..1.:::
:::pass[The lighting pass]Albedo.
Depth, linearised.
---
The lit HDR result, tone-mapped with the stated operator.:::A picture with no alt text or caption, a missing file, or one over 600 KB fails the check.
Link to other pages with absolute site paths: [DCX](/engines/fromsoft/archives/dcx/). Relative links and links to
pages that don’t exist fail the build. Link to outside sources with full URLs.
Bringing in existing notes
Section titled “Bringing in existing notes”Notes from investigations, code comments and older documents are the raw material. When moving a fact in:
- Re-state it about the game, in the wiki’s voice, without the history of how it was found.
- Give it the level its evidence supports today, using the mapping in Confidence. A note with no evidence is at most Inferred; a retired or archived note is a lead, not a fact.
- Keep numbers exact and say what they were counted over (“over the 147,289 meshes of build 25394987”).
- If two sources disagree, write both with their levels and an open question.
Finding what is already written
Section titled “Finding what is already written”Search before writing, and add to the page that exists rather than starting a near-duplicate.
pnpm wiki search "forge header" # pages ranked by title, description, headings and textpnpm wiki show engines/anvil/archives/forgepnpm wiki game riders-republic # every page about a game (or an engine), by system, with badge countspnpm wiki open sekiro # the open questions, by pageEach takes --json; pnpm wiki export prints every page and the registries as JSON for indexers.
Checking your work
Section titled “Checking your work”pnpm installpnpm check # every page: frontmatter, links, badges, scopes, layouts, public text (seconds, writes nothing)pnpm check src/content/docs/engines/fromsoft # only these files or folderspnpm build # the whole site, as it deployspnpm dev # http://localhost:4321 to read itA change is ready when pnpm check and pnpm build pass and the page reads well in the browser. pnpm check is
safe to run while others edit the same checkout; pnpm build is not (one at a time). The check also refuses local
paths, and, when WIKI_PRIVATE_NAMES_FILE names a file of private names (machines, accounts), those names on whole
pages; a name that is also a file or folder name in the repository counts as the wiki’s own word.
Publishing
Section titled “Publishing”Commit, then pnpm wiki publish. It rebases the committed HEAD onto origin/main, checks every line the commits add
and their messages for local paths and private names, runs pnpm check and the full build of that exact commit in a
clean worktree (.wt/head, so uncommitted pages in a shared checkout can’t hide a broken link), and pushes it to main,
which deploys the site. If main moves meanwhile it rebases and checks again. Nothing is pushed unless all of it
passes. It works from any branch, worktree or clone, one publish at a time per repository; when the working tree is
clean the branch is brought level with what was pushed. --dry-run does everything but the push.
Writers working at the same time each use their own worktree or clone; publish puts their commits in a line.