Open Northland is a TypeScript reimplementation of Cultures - 8th Wonder of the World. Read this
file before editing. Package-local AGENTS.md files add narrower rules.
CLAUDE.md and client command files are adapters. Durable project rules belong here or in the
nearest package contract.
The repository may sit beside an owned game installation and the CulturesNation DataCnmd/ folder.
They are pipeline inputs only. Never commit original files, decoded content, binary probes, or
reference captures from the original.
This is an independent GPL-3.0-or-later implementation. Do not copy or translate another engine's
code. Base format and behavior work must use the sources allowed by docs/SOURCES.md. The canonical
legal wording is in docs/LEGAL.md.
- The sim is pure and deterministic.
packages/simcannot useMath.random, wall-clock time, DOM, I/O, Pixi, render, or app imports. Same seed and input must produce byte-identical state. - Sim state is fixed-point. Rendering may interpolate floats. Create
Fixedvalues only throughfx.*. - Content is data. Goods, jobs, buildings, tribes, graphics bindings, and balance belong in validated content or the committed fallback catalog. Systems must not grow id-specific rules.
- Use the strongest readable source. Prefer mod
.ini, then base plaintext.ini, then decoded.cif. Binary claims need byte-level evidence from the owned copy and synthetic tests. - Name approximations. A passing test proves internal behavior, not fidelity. Record whether a mechanic, constant, timing, or visual choice comes from extracted data, readable semantics, byte-level evidence, a published standard, observation, or an approximation.
- Treat scale as a budget. Per-tick simulation work scales with active work, not entity pairs.
Per-frame rendering and audio work scales with the visible screen, not the map. Measure that claim
instead of asserting it:
docs/DEVELOPMENT.mdsays which tool answers which question, and what voids a measurement. - Keep durable context small. Current tasks live in
docs/tickets/. Stable rules live in anAGENTS.md. Completed investigation belongs in Git history.
- Prefer names and structure that explain the code without PR context.
- Prefer no comment when names, types, and tests already state the contract. A useful comment records one otherwise-hidden fact: a unit, invariant, ownership boundary, non-obvious constraint, or source basis. Do not narrate control flow or restate the implementation.
- Comments describe the current contract, never its history. Never put calendar dates, user/author
attribution, conversation, plan, ticket, or PR references, or labels such as "user decision",
"feedback", or "revised" in code comments. State source basis impersonally (
manual,.inikey, byte evidence, observation, or approximation); keep decision history in the ticket or commit. - JSDoc is not required for every export, interface member, or local helper. Do not document a symbol when its name and type already state the contract.
- Write one direct sentence about one fact. Most comments fit in one to three physical lines; treat anything longer as a structural problem and shorten the comment, improve the code, or move the detail to a test, ticket, or focused document. Only indivisible protocol layouts, security/legal boundaries, and byte-level format evidence justify a longer block.
- Do not write mini design documents above modules or exports. Avoid phase and caller inventories,
{@link}chains that restate the import graph, repeated examples, rhetorical asides, emphasis through capitals, and chains of parenthetical remarks. - When behavior changes, rewrite or delete its comment so only the new invariant remains; never append a dated correction or revision note. Leave unrelated historical comments to a dedicated comment pass instead of widening a feature diff.
- Give each fact one durable home. Investigation, benchmarks, caller inventories, and decision history belong in tests, tickets, or the completing commit, not repeated in production JSDoc.
- When extracting or moving code, review comments across the old and new modules as one budget. Moving prose is not an improvement, and a behavior-preserving refactor should not grow that budget unless it exposes a previously unstated invariant. Do not add a module header or JSDoc to every new export by default.
- Refactor structure before adding a long comment about phases, branch purpose, or ownership.
- Group by feature. When a file passes roughly 300 lines or mixes concerns, extract the relevant concern into a feature folder and preserve public imports through a small barrel when useful.
- Delete dead code and commented-out blocks. Git is the archive.
- Deduplicate when a second real caller proves the shared concept. Do not add speculative helpers.
- Leave touched code cleaner, but do not turn a bounded task into a repository rewrite.
- Do not add another responsibility, narrative section, or longer orchestration path to an already overgrown file. Extract the concern related to the task; an existing or newly filed cleanup ticket does not permit making the file worse.
- Enforce boundaries through package structure, types, and existing lint or hygiene checks where possible. Do not add a one-off regex source scanner to prove a local refactor; reserve source scans for repository-wide syntactic contracts that cannot be expressed by those mechanisms.
Use strict TypeScript deliberately: no any, narrow unknown, prefer discriminated unions with
exhaustive switches, use string-literal unions rather than enum, mark stable data readonly, use
import type, and prove absence cases instead of using non-null assertions.
Before changing extraction or a content join, inspect all three:
- the real source file in the owned copy;
- the decoder or extractor;
- generated
content/ir.json.
Schema names, fixtures, and tickets are not source evidence. .ini keys are case-sensitive, list
shapes vary, and numeric ids may be scoped.
Decoded maps already carry final ground-pattern choices. The current observed projection is a
staggered raster with 68 px cell width, 38 px row step, elevation lift TILE_HALF_H / 32, and
pre-lift depth sorting.
The sim uses the original half-cell lattice: 2W x 2H, with cell (c, r) at node
(2c + (r & 1), 2r). Integer sim commands, footprints, and navigation use half-cell nodes.
Fixed-point positions use fractional visual-tile coordinates. nav/halfcell.ts is the conversion
seam; cell grids pass through halfCellMapFromCells before becoming a TerrainGraph.
One ticket under docs/tickets/ describes one actionable task. Verify its claims before executing
it. Delete a completed ticket in the completing commit; rewrite a partial one to the exact remaining
work.
A ticket is a compact task specification, not an investigation transcript. State the verified problem, bounded scope, and verification path; omit development history, large code excerpts, and exhaustive caller inventories unless they are necessary to execute the task safely.
File deferred work only when it is verified, actionable, valuable enough to schedule, and not already covered. Group closely related findings. Minor observations can stay in the current report instead of becoming permanent tracker noise.
/worktree is the primary isolated workflow: create a worktree, implement one requested task, verify
and review it, update the ticket, ask for approval, then fast-forward merge. Other workflows are
documented under .claude/commands/.
- Prove behavior at the lowest useful layer: unit, integration, headless scenario, then browser scene.
- Normal code expects
npm run check,npm run build, andnpm test. - Pipeline and schema changes also need
npm run test:pipelineagainst the owned copy. - Real-content loaders and joins need
npm run test:contentwhen local content exists. - Golden hashes move only for intentional behavior changes.
- Visual and audio changes need human review. Automated checks can prove data decisions, stability, and absence of obvious crashes, not final pixels or sound.
- Player-visible mechanics should have a registered acceptance scene when it adds useful state and presentation coverage.
Commands and local tools are listed in docs/DEVELOPMENT.md. Test modes are explained in
docs/TESTING.md.
Load the relevant file when working in that area:
packages/sim/AGENTS.mdpackages/render/AGENTS.mdpackages/audio/AGENTS.mdpackages/app/AGENTS.mdpackages/data/AGENTS.mdpackages/content-resolver/AGENTS.mdpackages/vfs/AGENTS.mdpackages/installer/AGENTS.mdpackages/desktop/AGENTS.mdpackages/web/AGENTS.mdtools/asset-pipeline/AGENTS.md