# Orbit — Project Notes Working agreements and conventions for building this game. Read this before adding new systems — these are the rules that keep the project scalable. ## Commits (important) - **Brian makes the commits, manually.** After finishing a change, leave the work staged/unstaged in the working tree with a suggested commit message (in the chat reply, or a note here), and do NOT run `git commit` yourself. This gives Brian the chance to review the code before it enters history. - Amends/reverts are fine if asked explicitly. ## What we're building A procedurally generated space RPG in the spirit of **Privateer**, but in **top-down 2D**: stars/sectors you can jump between, ships to fly, crew, trading/economy, stations, quests — to be scoped as we go. **Hard technical constraints:** - Plain **ES6 modules**, no transpilation, no build step. - **No package managers required to run** — the game must work from any static HTTP server (the browser fetches everything). - Third-party libraries are **vendored** into `lib/` (right now: `lib/phaser.min.js`, Phaser 4.2.1 UMD build, see `lib/PHASER_LICENSE.md`). - Must be served over http(s), not `file://` (ES modules + `fetch` of JSON). ## Config lives in JSON (important) - Tunable data lives in `data/*.json`: dimensions, colors, text, physics, balance, spawn tables, and anything a non-programmer might want to tweak. - `data/manifest.json` lists which files to load. **Add a config file = drop it in `data/` + one line in the manifest.** It is then available as a section named after the file (`ship.json` → `config.get('ship.thrust')`). - Code reads config through the `config` singleton (`js/config/Config.js`): ```js import { config } from '../config/Config.js'; const thrust = config.get('ship.thrust', 900); const menu = config.section('menu', {}); ``` Always supply a sensible fallback so code never depends on a missing key. - **Rule of thumb:** if a value might ever change (balance, layout, copy, colors), it belongs in JSON, not code. Code owns *behavior*, JSON owns *parameters*. - Split config by concern as the game grows: world generation already has `data/galaxy.json` (shape/scale), `data/systems.json` (archetypes), and `data/naming.json` (name pools); next up might be `data/economy.json`, `data/ships.json`, `data/crew.json`, etc. One file per system beats one giant file. ## Code is modular & class-based (important) - One class per file, ES module exports, no globals (except the deliberate singletons: `config`). - Layering: - `js/scenes/` — Phaser scenes: state + orchestration only (thin classes) - `js/entities/` — in-world objects that own their behavior (Ship, NPC, …) - `js/ui/` — reusable UI components (MenuButton, panels, HUD) - `js/visuals/` — decorative, non-interactive visuals (Starfield, …) - `js/galaxy/` — the world model: seeded Galaxy, system archetypes, lazy content generation (Galaxy, SystemGenerator) - `js/utils/` — small pure helpers (Color, Rng, NameGenerator) - `js/config/` — config loading + Phaser game config - `js/vendor/` — shims to pinned third-party libraries - Scenes stay thin: they compose entities/UI and wire input. They don't hold balance numbers or game rules. - Every class should be standalone-constructible: it takes what it needs (`scene`, config values) in its constructor rather than reaching for globals — that keeps things testable and reusable. - Entities drive themselves from their own `update(time, delta)` method, called by the owning scene (e.g. `GameScene.update`). If an entity ever needs the engine's auto-update instead, define `preUpdate` (the Phaser v4 hook — v4 does not auto-call `update`). - Import Phaser only through `js/vendor/phaser.js` — the single place that changes if we swap framework versions. ## World model — the galaxy (important) The galaxy is **seeded and two-level**. The seed is chosen on the main menu (displayed, editable, rerollable; same seed ⇒ same galaxy). 1. **Roster** — `Galaxy.create(seed)` builds every system's *identity* (id, name, type, x, y) up front. 40,000 systems is ~70 ms, so the whole galaxy is always known: the player can never "discover" a layout that wasn't already implied by the seed. 2. **Contents** — planets/moons/belts/**settlements**/hazards are generated **lazily** on first arrival (`galaxy.ensureContent(id)`), then cached. Each system's draw stream is `Rng.derive(seed, 'system', id)` — independent of generation order — so lazy and eager (`galaxy.generateAll()`) results are identical. Pick whichever is cheaper at runtime; correctness never depends on it. **Determinism rules (keep them!):** - All generation draws go through `Rng` (`js/utils/Rng.js`). Never `Math.random()` in a generation path. - Anything that must be order-independent (names, contents) draws from a `Rng.derive(seed, , id)` stream — never from the galaxy-level sequence. The roster loop is allowed to use its own sequence because the loop order itself is part of the seed contract. - Seeds are trimmed; seed → hash → stream is one-way. - Dev tools that assert determinism: `dev/galaxy.test.mjs`. **System archetypes** live in `data/systems.json`. Each type is: - **themable** — `theme.color` etc. for UI; - **attribute-driven** — `attributes` steer the SystemGenerator (star classes, binary chance, planet count spread, planet class weights, moon/belt chances, habitability, hazard). New attribute key = JSON + a few lines in `SystemGenerator.js`; - **distributed** — `distribution.weight` (how common) and `distribution.radiusBand` (first proximity rule: e.g. `void` systems live in the outer rim). Richer galaxy-level rules (clustering, faction borders, adjacency affinity) will slot into `data/galaxy.json` → `distribution.rules[]`, read in `Galaxy._generate()` — the hook is marked in code. The player's **current system** starts at `galaxy.currentSystem()` (`startingSystem.policy`: `center` or `random`). Jumping between systems (the eventual star map / jump drives) will use `galaxy.neighborsOf(id)` and the spatial hash already built for it. **The galaxy is already lived in.** It was settled long before the player arrives. Every system's content can include **settlements**: `colony` (on a habitable world), `miningStation` (over a resource world), `cloudBase` (riding a gas giant), `deepSpaceStation` (adrift in open space), `waypoint` (a small beacon — the faint trace of a crossed galaxy). Not everything is inhabited: many systems report "charted · unclaimed". Model & seams: - **Kinds vocabulary** — `data/settlements.json` (label, description, theme color, population range, anchor type). Add a kind = JSON + naming pool; the generator picks it up by name. - **Per-type rates** — `types..attributes.settlements` in `data/systems.json`: `chance` + optional `needs` (planet classes) per kind. Same attribute-driven pattern as everything else. - **Core→rim gradient** — `galaxy.settlements.gradient` in `data/galaxy.json`: the settled heart is denser (factor 1.0), the rim is thinner (clamped to `floor`). Each record carries `rNorm` (0 = center, 1 = rim) so density is per-system, not global. - **Reserved seam: `owner`** — every settlement has `owner: null`. That's where **factions and pirates** will plug in later (claim, flag, relations). Deliberately absent for now — no factions yet. - **Pure report formatter** — `js/galaxy/SystemReport.js` (`formatSystemReport(content)` → title/subtitle/settlements/summary). The GameScene HUD renders it; future star map / terminal UI reuse it. - Landing/exploration (a future feature) will treat settlements as points of interest: the data already says what's there and where (anchor = planet ordinal or open space). ## Phaser version - Pinned: **Phaser 4.2.1** ("Giedi"), vendored in `lib/phaser.min.js`. - App code is v4-specific where v4 changed things (e.g. `Phaser.Math.Angle.RotateTo`, `banner: false`, `Clamp(value, min, max)`). - To upgrade: replace the vendored file + note the version here. ## Roadmap (working list, intentionally rough) - [x] v0.1 foundation — menu → New Game → click-to-fly ship - [x] Galaxy seed on the main menu (displayed, editable, rerollable; same seed → same galaxy, shown before you commit) - [x] Two-level worldgen: seeded galaxy roster (40k systems) + lazy, order-independent system contents; system archetypes in JSON (theme + attributes + distribution weight/radius band) - [x] The lived-in layer: settlements (colonies, mining stations, cloud bases, deep-space stations, beacons) with per-type rates, a core→rim density gradient, populations, and a `owner` seam reserved for the factions/pirates to come; readable as a HUD dossier (`SystemReport`) - [ ] Factions & pirates: claim settlements (`owner`), flags, borders, and the player's place in a populated galaxy - [ ] Landing & exploration: settlements become points of interest you can approach (the data — kind, anchor, population — is already there) - [ ] Richer galaxy distribution rules (`galaxy.distribution.rules[]`: clustering by type, borders, adjacency affinity) — hook marked in `Galaxy._generate()` - [ ] World model in play: the ship still flies unbounded open space; wire in current-system boundaries, jumps between systems (use `galaxy.neighborsOf`), and a star map scene - [ ] Ship input beyond click-to-fly (throttle/brake keys, manual rotation) - [ ] HUD (speed, fuel/crew) — the current system's dossier (name, identity, settlements) is already shown top-left - [ ] Save/load (the `config` + entity split should make this tractable; a save = seed + player state, since the galaxy regenerates) - [ ] Economy/trading loop (the Privateer heart)