Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nihilurk documentation

This is the documentation for people who want to put things in the dungeon: monsters, items, traps, magic. It lives in the repository, in plain text, and reads in a terminal.

cat docs/how-to/add-a-monster.md

Start here

I want to…Read
Add my first item, hand-heldtutorial/add-your-first-item.md
Add my first monster, hand-heldtutorial/add-your-first-monster.md
Add my first spell, hand-heldtutorial/add-your-first-spell.md
Add my first body, hand-heldtutorial/add-your-first-body.md
Give a monster a mind, hand-heldtutorial/give-a-monster-a-mind.md
Add a monsterhow-to/add-a-monster.md
Add a new playable body, like the lurkhow-to/add-a-body.md
Add a potion, wand, weapon, ringhow-to/add-an-item.md
Add an active spellhow-to/add-a-spell.md
Add a traphow-to/add-a-trap.md
Add a property like “fire immune”how-to/add-an-effect.md
Add a way for a creature to thinkhow-to/add-a-rule-set.md
Add one reflex to that thinkinghow-to/add-a-rule.md
Make something rarer, or deeperhow-to/tune-rarity-and-depth.md
Add a whole new kind of itemhow-to/add-an-item-category.md
Put a specific thing on a specific tilehow-to/spawn-a-thing.md
See a change working in the real gamehow-to/play-through-a-pty.md
Look up a field, a type, a defaultreference/content-tables.md
Look up a component, resource, eventreference/components.md
Change a balance numberreference/constants.md
Look up a function I have to callreference/spawn-api.md
Look up a flag or an env varreference/cli-and-env.md
Look up a conduct, or what a win is paidreference/conducts.md
Look up how input and the turn loop workreference/input-and-turn-loop.md
Look up what a monster does with its turnreference/agents.md
Look up how a frame gets to the screenreference/rendering.md
Reach an entity and change ithow-to/work-with-the-ecs.md
Understand why it is built this wayexplanation/data-driven-content.md
Understand how bevy_ecs is used hereexplanation/ecs-in-nihilurk.md
Understand why monsters think in rule setsexplanation/agents.md
Know what an effect should look likeexplanation/the-feel-layer.md
Write or edit a page in hereexplanation/documentation-style.md
Know which pages to touch after a changehow-to/update-the-docs.md
Know what good numbers look likeexplanation/combat-and-balance.md
Know what shape to leave the code inexplanation/code-calisthenics.md
Check it still runs on a Pihow-to/run-the-compat-pipeline.md
Know how portability is testedexplanation/cross-platform-testing.md
Cut a release, one command and a pushhow-to/cut-a-release.md
Publish a release to crates.iohow-to/publish-to-crates-io.md
Publish a GitHub releasehow-to/publish-a-github-release.md
Publish the documentation sitehow-to/publish-the-site.md
Know why package names differ from directoriesexplanation/adr-0002-two-names-per-crate.md

And architecture decision records. They keep the history the other pages leave out: what was tried, what was rejected, and when to reopen it.

explanation/adr-0001-tables-not-raws.md                  content is compiled in, not loaded from JSON
explanation/adr-0002-two-names-per-crate.md              package names differ from library names
explanation/adr-0003-compat-checks-start-not-speed.md    the matrix checks that it starts, not that it is fast
explanation/adr-0004-no-tests-of-presentation.md         presentation is checked by playing
explanation/adr-0005-aim-resolves-before-the-mobs-move.md  a zap and a throw resolve before the mobs move
explanation/adr-0006-effects-saved-by-id.md              effects are saved by string id
explanation/adr-0007-aggravation-is-a-component.md       aggravation sits on top of the tactic
explanation/adr-0008-player-actions-are-queued-intents.md  the player's step is a queued intent

How this is organised

Four kinds of document, kept strictly apart, because a person adding a monster at 1am and a person deciding whether to fork the project need very different pages.

tutorial/ A lesson. Follow it start to finish and you will have added something that works. It teaches; it does not cover every option.

how-to/ A recipe for one real task. Assumes you already know your way around. Short, imperative, no theory.

reference/ The dry facts. Every table, every field, every default, every function signature. Never a narrative.

explanation/ Why the code is shaped the way it is. Read when you are deciding something, not when you are typing.

If a page starts to be two of these at once, split it.

How a page is written — the header block, the width, the voice, the things a page must not do — is explanation/documentation-style.md, inferred from the pages already here.

Who this is for

Every page names its own audience and prerequisites in a header. Broadly:

Content author Adds monsters, items, traps. Edits tables. Needs to know Rust well enough to copy a line and change the numbers. Lives in tutorial/ and how-to/.

Engine developer Adds systems, changes how content is spawned or resolved. Starts at explanation/ecs-in-nihilurk.md and how-to/work-with-the-ecs.md, then lives in reference/ and explanation/. engine/ itself (input handling, rendering) is thinner ground than models/: its tests cover key dispatch (update.rs) and the log gate and help-key remap (view.rs), not what a frame looks like, so a change to a frame is checked by playing the game, not by cargo test. See reference/input-and-turn-loop.md and reference/rendering.md.

The game design document (../gdd.md) is a third thing again: what nihilurk is trying to be. It is not a spec of the code.

The crates

The code is a workspace of small libraries and one binary. Pages name files by their path from the repo root, so this is the map. The directory names are the short ones below; the packages are nihilurk-models, nihilurk-strings, nihilurk-particle-core and nihilurk (for engine/), and cargo -p takes those. See explanation/adr-0002-two-names-per-crate.md.

models/ The game. Rules, content tables, ECS systems, save files. No input, no drawing. Almost everything in tutorial/, how-to/ and reference/ lives here. strings/ Every player-facing sentence, one file per language, chosen by a lang-* feature at build time. particle-core/ The particle arithmetic. no_std, no dependencies. engine/ The binary. Key handling, the turn loop, Screen (the cell grid and its diff) and every layer that paints into it. Also nihilurk-dispatch, which picks a language binary. compat/ A test rig, outside a bare cargo build and cargo test. See how-to/run-the-compat-pipeline.md.

Dependencies run one way, toward the leaves: engine uses models and strings; models uses strings and particle-core. Nothing depends on engine. models re-exports its public API from the crate root, so use models::*; reaches it. helpers is private bar four names (Hit, apply_hit, chebyshev, mob_at), and so is anything marked pub(crate). A page that names one of those says so.

The rule that keeps this true

Documentation ships with the change that makes it true. A pull request that adds a field, renames a table, or changes what a row means is not finished until the pages that describe it say so. Stale documentation is worse than none, because someone will believe it.

Three things make that cheap here:

  1. The docs describe tables, and the tables are short. There is rarely more than one page to touch.

  2. models/tests/it/content.rs enforces the claims this documentation makes about the tables – unique names, reachable rows, depth gating, no catalog row spawning a dud – and models/tests/it/determinism.rs enforces the one claim that protects everyone else’s saved seeds: adding content cannot move a wall. engine/tests/workspace.rs is the third of that kind: it checks that a bare cargo test still covers every crate that ships. If a doc claim can be a test, make it a test and cite it here.

  3. cargo run -p nihilurk -- -content prints the live content list. It reads the tables, so it can never go stale. Prefer pointing a reader at it over pasting a list into a page.

Quick sanity check

./docs_style.sh                           these pages, house style
cargo test                                # everything still works
cargo test --test it content::                 # the tables specifically
cargo test --test it determinism::             # seeds still mean what they meant
cargo test -p nihilurk --test workspace     # the root test run still covers everything
cargo run -p nihilurk -- -content           # what the game knows
NIHILURK_SPAWN="dragon" cargo run -p nihilurk   # put one in front of me

And the compatibility pipeline, which is slower and answers a different question:

./compat_test.sh                          # does it still run on a Pi?