Keeping docs in sync
Nova Protocol documents itself across several surfaces, each aimed at a different reader. None of them updates itself, so a code change is not finished until the docs it invalidates are fixed in the same task. This page is the map: what the surfaces are, what to touch when you change code, and what to do when you cut a release. It is the overview; the detailed, command-level steps live in Building and running under “Versioning and release” and “Writing the release news post”.
The documentation surfaces
| Surface | Where | Audience | Kept in sync when |
|---|---|---|---|
CHANGELOG.md | repo root | everyone (terse, complete) | any user-visible change |
| News | web/src/news/*.md -> /news/ | players + modders | each feature release |
| Player wiki | web/src/wiki/*.md -> /wiki/ | players | player-facing behavior changes |
| Lore encyclopedia | web/src/lore/ -> /lore/ | readers + story authors | approved world facts and published story references change |
| Story library | web/src/comics/ -> /story/ | readers + story authors | navigation, TS page scripts, Python panel art, composition, or publication change; update the comic authoring README and keep drafts out of deployable builds |
| Creator docs | web/src/create/*.md -> /create/ | mod authors | a data format or the modding UX changes |
| Dev book (this book) | docs/ -> /dev/ | contributors | internals, architecture, or the dev workflow change |
| rustdoc | local: cargo doc --open | contributors | with the code (doc comments travel in the same diff) |
The tutorial is not a separate surface: it is the wiki’s Start-here landing
(web/src/wiki/getting-started.md -> /wiki/getting-started/), updated when
the first-flight flow changes.
CHANGELOG.md is the exhaustive machine reference (every version, terse,
grouped by subsystem). News is the story (one rich post per feature release).
The player wiki is the manual; /create/ is the modding manual and the
exhaustive construct catalog. This book is the developer’s map - how to run,
how to extend, where things live - and rustdoc is the API detail underneath
it. They overlap on purpose - the cost of that overlap is that one code change
can carry several doc obligations, which is what the map below makes explicit.
docs/ is the source of this book, not a scratchpad. Everything under it
is a maintained book chapter, listed in SUMMARY.md and built on every deploy.
Transient working files live outside the repo, and task-scoped records live in
tasks/<id>/.
When you change code
Before you commit, ask three questions and act on each “yes”:
- Did user-visible behavior change? Apply the Changelog section of
AGENTS.md; it owns the entry and release-baseline rules. - Did player-facing behavior change (controls, HUD, a verb, a section, a weapon, a scenario primitive, the modding UX)? Update the player wiki page(s) it affects, and the tutorial if the first-flight flow moved. A wiki page drifting behind the game is the exact failure to avoid.
- Did internals, architecture, or a data format change? Update the book
chapter(s) that describe them. A RON, bundle, catalog, or portal format
change must also land in the matching
/create/reference or publishing page in the same task, or every mod author reads a lie.
The dependency map
Which docs to check when you touch a given area. “Check” means read it and fix
it if the change made it wrong - not every change touches every listed page.
Player wiki names are web/src/wiki/ pages; /create/... names are the
creator pages on the site; linked names are chapters of this book.
| Code area (crate / dir) | Player wiki | Dev book / creator docs | Also |
|---|---|---|---|
The crate layout itself: a split, merge, rename, or move (crates/*) | Architecture (crate map + dependency graph + assembly), Project tour (crate map + change-X table), and THIS page’s own row keys | CHANGELOG (Internals) | |
The world’s scale and the quantity types: METERS_PER_UNIT, LOAD_LIMIT, the Meters / MetersPerSecond / Meters3 seam, or any authored field moving between a quantity type and a bare f32 (nova_events/src/units.rs, nova_events/src/scale.rs, nova_ui/src/units.rs) | every page that quotes a figure - glossary.md (the world unit is the build grid’s name and nothing else), combat-weapons.md, gravity-wells.md, sections/* | Architecture (the boundary itself - state it there, reference it elsewhere), Ship sections internals, Scenario engine, both Extending guides, and /create/ wherever a field’s unit is stated | web/src/widgets.ts (each widget either converts or holds SI - it cannot do both), CHANGELOG (breaking - what a field is measured in IS its format) |
Ship sections, integrity, typed damage, ammo (nova_ship/sections, nova_gameplay/integrity) | ships.md, sections.md (+ section children), hud.md | Ship sections internals, Add a ship section, /create/sections/ | CHANGELOG |
How a body WEARS its damage: the two readings and the authored looks (nova_gameplay/integrity/{erosion,carve,spew,chunk}, nova_ship/sections/damage_*) | ships.md, sections.md (+ section children), combat-weapons.md | Ship sections internals, /create/sections/ (damage_effects is a CONTENT format serialized into assets/base/sections/base.content.ron, so rule 3 applies), /create/base-content/ | CHANGELOG (breaking?) |
Asteroid carving: the signed field, its cost model, severing (nova_scenario/objects/asteroid*, nova_gameplay/mesh/field.rs) | combat-weapons.md, scenarios.md | Scenario engine (the mechanism AND what a remesh costs), /create/objects/ (the Asteroid fields, and that radius is durability) | CHANGELOG (breaking?) |
The derived skin, its plate vocabulary and skin styles (nova_ship/sections/shell_*, skin_*, scripts/gen-greebles.py) | sections.md | Ship sections internals (the derived skin), /create/styles/, /create/base-content/ (style ids, greeble assets), /create/objects/ (the ship’s skin / style fields) | CHANGELOG |
Flight: the manual burn, the autopilot legs and the telemetry they publish, controller, camera (nova_ship/flight, nova_ship/input, nova_ship/camera, the maneuver instruments in nova_hud) | flight-autopilot.md, keybinds.md, glossary.md (the Flight terms quote what each verb DOES), hud.md | Architecture, /create/actions/ (StopShip and the other helm orders - a completion contract that moves lands there), /create/events/ (the maneuver-completion events) | CHANGELOG |
Targeting, radar, weapons, turrets, torpedoes (nova_ship targeting/sections, nova_hud) | targeting-radar.md, combat-weapons.md, sections/turret.md, sections/torpedo-bay.md, sections/railgun.md, hud.md | Architecture | CHANGELOG |
Gravity wells, factions, world (nova_gameplay) | gravity-wells.md, factions.md | CHANGELOG | |
Scenario engine: events, filters, actions, variables, objects (nova_scenario, nova_events) | scenarios.md | Scenario engine, /create/author-a-scenario/, Extend the scenario engine, the creator reference (/create/scenarios/, /create/events/, /create/filters/, /create/actions/, /create/objects/, /create/expressions/ - a new/changed construct MUST land there, it is the exhaustive catalog) | CHANGELOG |
The streamed world: the cell generator, the active window, the streaming lifetime (nova_world) | Architecture (crate map + graph - state there whether AppBuilder still leaves the plugin opt-in), Project tour | CHANGELOG (Internals). No /create/ page: WorldConfig is code a caller writes, not authored content | |
Modding data format, bundles, catalog, local cache (nova_mod_format, nova_modding) | /create/mod-files/, /create/base-content/ (overlay rules, dep://base) | CHANGELOG (breaking?) | |
The ship content kind: what a hull IS vs what a spawn is (nova_scenario/objects/ship.rs, nova_authoring/base_content/ships) | sections.md | /create/ships/, /create/objects/ (the Spaceship spawn), /create/mod-files/, /create/reference/ | CHANGELOG (breaking?) |
Generated hulls: the collapse, the tiles, and the WfcPlan that draws them (nova_wfc, nova_editor/generate.rs) | ships.md | Concept index (the row under The ship), Ship layout sense | CHANGELOG. The plan is CODE, not content - a plan field is not a format break, and there is no creator page for it |
Mod portal + generator (scripts/gen-portal.py, nova_modding) | /create/publish-a-mod/ | CHANGELOG | |
The TAB interface and the : command modal: the panes, the CRT emulator, the command catalog, and the dispatcher that runs it (nova_command, nova_interface, nova_console) | interface.md, commands.md, keybinds.md | Architecture (crate map, and the PauseStates named freeze) | CHANGELOG; a new command MUST land in commands.md - that page is the exhaustive catalog, and help is generated from the same registry, so the two disagreeing is a bug |
The process channel and its lanes (nova_channel) | Architecture (crate map), and the command lane’s vocabulary is commands.md, not a second wire reference | CHANGELOG (a lane rename is breaking for every run script) | |
Menus, editor, UI (nova_menu, nova_editor, nova_ui) | hud.md, sections.md | Add a ship section | tutorial, CHANGELOG; theme tokens: web/design/nova_ui_rework_poc.html is the source for BOTH nova_ui/src/theme.rs and web/src/style.css; the site draws the PHOSPHOR skin only |
The training handbook: lessons as content, the practice ranges, the progress record, the field notes (nova_training, nova_menu/training.rs, nova_menu/training_store.rs, nova_authoring/base_content/lessons.rs, nova_authoring/base_content/scenarios/drills.rs) | getting-started.md (the way in), and every page a lesson links to | /create/lessons/ (the Lesson item), /create/scenarios/ (the roles and the practice-range contract), Architecture (the crate map and the graph), Concept index | CHANGELOG; RENAMING A WIKI PAGE OR RETITLING A HEADING BREAKS A LESSON’S LINK - crates/nova_authoring/tests/lesson_wiki_links.rs resolves every authored wiki_path against web/src and fails until the lesson is repointed |
Automation drivers, the env contract, the completion protocol (nova_autopilot, nova_debug/harness.rs) | Automation harness | CHANGELOG (env rename? breaking for every run script) | |
ANY environment variable: a new one, a rename, a removal (NOVA_* anywhere in crates/ or src/) | Environment variables - the index of the whole set, and the rule for adding one | the roster in tests/env_contract.rs (it fails until you do), CHANGELOG (breaking for every run script) | |
The probe harness: capabilities, run grading, the report (nova_probe, nova_probe_cli, nova_perf_web) | Building and running (“Run verification”), Measuring performance, and the nova_probe rustdoc knob table | CHANGELOG (Internals) | |
What a frame COSTS, or how it is measured (nova_probe/capabilities/{frametime,framecost,census}, nova_core render setup, anything with a millisecond in its justification) | Measuring performance - and re-read the Xvfb section before quoting an absolute number anywhere | CHANGELOG (Performance) | |
App assembly, plugin order, states, the game binary’s own flags (nova_core, nova_assets, src/main.rs) | Architecture, Project tour, Building and running (“Launching a scenario from the command line”) | CHANGELOG | |
Content CLI: gen/lint subcommands, the base content builders (nova_authoring, the game binary’s content subcommand) | /create/author-a-scenario/, /create/sections/, Add a ship section, /create/publish-a-mod/, /create/mod-files/, Scenario engine, Ship sections internals, /create/base-content/ (the id/asset catalog - a builder change that adds, renames or rebalances an id lands there) | CHANGELOG | |
Packaging: the nix package, the desktop entry, the icon (flake.nix) | Building and running (“The nix package”, “The desktop entry”) | README.md quick start, CHANGELOG (Internals) | |
The website itself (web/) | Building and running, this page | ||
Comic/site deployment (deploy-comic.yaml, deploy-page.yaml, scripts/deploy-pages.py, webpack targets) | Building and running, web/src/comics/README.md publishing procedure | Preserve separate /story/ ownership, tag/commit provenance, and draft exclusion; keep the two publishers on one lock | |
Local dev servers (scripts/serve-web.sh, scripts/serve-mods.sh, scripts/preview-web.sh, web/webpack.config.js, Trunk.toml) | Building and running (“Local web preview”), /create/publish-a-mod/ (“Preview the repository portal”) | README.md quick start + scripts table |
“Check” means re-derive, not grep
A name-level sweep - “does the page mention the new crate?” - passes while every claim BETWEEN the names goes stale: a dependency graph drawing a crate with the wrong consumers, a crate map missing four members, a command attributed to the crate it moved out of. Someone then reads one of those and answers a question wrong, which is the failure the sweep was supposed to prevent.
So when a row above says “check”, it means: re-derive every crate name, module
path, command, symbol and dependency direction the page asserts, against the
current tree. A page that names the new thing can still describe the old
one. Grep the page’s own file:line and symbol claims and open each one - a
type that no longer exists is an unambiguous defect and needs no judgement.
Two structural traps this map cannot catch by itself:
- The map’s own row keys are crate/dir names, so a structural refactor invalidates the MAP too - that is what the first row is for.
- A lane-per-change epic has no lane whose job is the cross-cutting sweep.
Give the sweep its own step (or lane) whenever
crates/*changes shape. - A quoted FIGURE is a claim about a UNIT as well as about a number. Change what
a field is measured in and every page quoting it stays readable and becomes
wrong, while a name-level grep finds the pages that mention the field rather
than the ones that quote it. Re-derive the figure from the code; and check the
static fallback prose inside a
data-widgetblock, which no test renders.
When you cut a release
The full command-level checklist is in Building and running -> Versioning and release. At the doc level, a release means:
-
CHANGELOG.md: promote## [Unreleased]to## [<version>] - <YYYY-MM-DD>, leave a fresh empty[Unreleased], merge any duplicate subsystem headings that grew during the cycle, and update the compare links at the bottom. -
News: a feature release (
0.X.0) gets a new post atweb/src/news/<version>.md, registered inNEWS_POSTSinweb/webpack.config.jswith a card added toweb/src/news.html. A patch release (0.X.Y) gets no post of its own - fold it into its parent feature post’s## Point releasessection instead. Full steps: Writing the release news post. Post conventions the 0.7.0 post sets: structure the body as##sections with###subsections - the build derives the sticky TOC sidebar from those headings, so a flat post gets an empty TOC; and use the figure-placeholder format (a.figureblock that auto-upgrades to its screenshot oncescripts/gen-web-screenshots.pypackages the image) rather than inlining an<img>.Name every figure
news-<version>-<subject>and nothing else. A post argues about one release, so its media is frozen: both packagers refuse to overwrite anews-file that already exists (NOVA_UNFREEZE=news-0120re-opens the post being authored), and anews-name is a leaf that nothing else may source from. A figure named for a living asset is re-cut every cycle and silently reillustrates the post; a living page borrowing anews-figure is pinned to an old release and can never update.web/tests/assets.test.jsfails the build on either, so authoring a figure means adding an alias inscripts/gen-web-screenshots.py(stills) orscripts/capture-web-media.sh(loops). -
Wiki and creator docs: sync any player or creator pages the release’s changes touched (use the map above). Do this as you go during the cycle, not in a scramble at release.
-
This book: same rule - chapters stay current through the cycle, and
nix develop --command mdbook buildmust be green (a broken link is a build warning; treat warnings as failures). There is no release-time wipe step:docs/carries no transient content to wipe. -
Build check:
cd web && npm run ci(format check, lint, test, build) must be green; confirm/news/and the new post render, and the section TOC sidebar is populated.
Adding or renaming a page
- A chapter of this book: create
docs/<page>.mdand list it indocs/SUMMARY.md.mdbook buildwarns on a SUMMARY entry whose file is missing - treat that as a failure. - A site wiki, creator, or lore page: add it to the matching section in
web/src/docs-manifest.js, which drives publication, the sidebar, search, and related pages. Lore source and illustration conventions are inweb/src/lore/README.md; it is a website section, not a second mdBook. - A news post: edit
NEWS_POSTSinweb/webpack.config.jsplus a card inweb/src/news.html. - Retiring a URL: add a stub to
REDIRECTSinweb/webpack.config.js.
Verify any of the web/ changes with cd web && npm run ci.