- Define the two-sided unlock contract: research nodes declare `unlocks.builds`/`unlocks.research`, builds declare `requires` as "category/node id" (authoritative gate); ResearchModel exposes `unlocksOf`, `buildDefs`, `unlockIssues`, `buildIssues`
- Add `tether-l2` build entry (planet/station targets, credits+minerals cost, level-2 tether effect) and link it from `exploration/tether_l2`
- Show an UNLOCKS line in the research dossier so the console surfaces what a tech opens
- Extend dev/research-builds.test.mjs to validate both sides of the gate and the new model helpers
- Update data/builds.json, research.json, exploration.json `_comment`s and PROJECT_NOTES to document the contract
- Full-screen RESEARCH window: looping archive feed, category tabs, branching
DAG tech tree (top-down layout), per-tech dossier + RESEARCH button, deck
progress bar
- Pure rules layer (ResearchModel / ResearchState) is Node-testable; the
window is a passive view and GameScene owns effects, toasts, and saves
- First category **Exploration** (Tether Level 1–4, Tether Anchoring, Signal
Amplification) lives in data/research/exploration.json; adding a category
= one JSON file + one registry line + one manifest line
- Effects seam: `tether.level` grows the home tether via TetherField.setLevel;
`capability` flags land on scene.researchCapabilities for future systems
- Save/restore round-trips an in-flight project (remainingMs captured at save,
startedAt rebuilt on load); old saves without research still load
- Dev tooling: dev/cdp-shot.mjs (CDP screenshot with readiness wait),
dev/research-shot.html/.mjs (boots the console + starts a run for a shot)
- Assets: gasgiant planet frame 03, takeoff/landing clips, research-computer
feed; planets.png/.psd updated
- SaveManager.latest() picks the filled slot with the newest savedAt
(unparseable timestamps count as oldest; ties break to the higher slot)
- MenuScene wires a Continue button above New Game, grayed out while the
bank is empty, that stages the restore via prepareLoad and cuts into
GameScene without a confirmation beat
- Shift menu layout down to make room and park the Reroll button fully
outside the seed field so its edge no longer bites the field
- Cover latest() behavior in dev/saves.test.mjs and update README and
PROJECT_NOTES to reflect the finished save/load feature
- Add MineralHud component (js/ui/MineralHud.js) showing "MINERALS n / N"
plus a fill bar in the upper-right corner, replacing the old tether
stack. One tween drives both the counting number and the bar so they
never drift; a cyan flash ripples on landings; amber at capacity.
- Wire Mining to emit an onOre(gained, hold) seam per extraction tick and
per fragment landing so the HUD fills live as the rock shrinks, not
only at the batch 'absorbed' event.
- Remove refreshTetherHud / fmtRange from GameScene; instantiate MineralHud
in create(), feed it via refreshMineralHud() from ore seams and mining
events, destroy on shutdown.
- Add data/mineralhud.json config (corner pad, bar geometry, count-up and
flash tuning) and register it in the manifest.
- Tests: new dev/mineral-hud.test.mjs covering layout, count-up/bar sync,
superseded-tween guard, capacity/clamping, teardown; extend
dev/mining.test.mjs to assert onOre ticks; drop tether-HUD checks from
dev/system-hud.test.mjs; add setOrigin/setScrollFactor no-ops to the
Phaser stub.
- Extract minerals from the latched asteroid at ratePerSec × miningSpeed; each mineral shrinks the rock by 1 px so the beam stays latched to its rim.
- Break rocks that lose ~1/3 of their original size: large ones halve in place (beam keeps mining one half), small ones shatter into pieces that fly home and load the hold.
- Add ship.minerals hold with addMinerals/setMinerals capped at stats.mineralStorage; a full hold ends the run instead of grinding the rock away.
- Persist the hold in save/restore (captureState writes ship.minerals, applyRestore restores it) and keep legacy saves working when the field is absent.
- Play new mining_split sfx on each break and surface split/absorbed/storageFull events as console toasts.
- Expose AsteroidCluster rock surgery (setSize/addRock/removeMember) for runtime splits, and add headless Phaser stub + loader plus a mining test suite covering extraction, halving, shattering, full-hold stop, and the hold API.
- New shared audio voices: js/utils/Sfx.js (playSfxOn/stopSfxOn/sfxPlayingOn)
and js/utils/Music.js (loop voice + shuffle playlist), both with v4 guard
rails (cache.audio.has, stopByKey, isPlaying) so missing assets never throw.
- data/music.json: menu loop, shuffled deep-space soundtrack for GameScene,
and per-world surface loops keyed by planets.png frame; new mp3 assets in
assets/music/.
- data/sfx.json: add ui_hover/ui_click/ui_window/ui_close/mining_loop and a
dedicated scan track; wire UI components (buttons, panels, popups, save
vault) to the shared voice via scene.playSfx seam.
- GameScene: start/stop the shuffle on create/shutdown, stop both soundtrack
and mining hum on 'sleep' (landing launch+sleep) and resume on 'wake';
mining hum loops while beam is live and stops quietly on 'stopped'.
- SurfaceScene: preload and loop the world's track from landing through
takeoff; fix landing frame hand-off to pass sheetFrame instead of the
texture Frame object.
- MenuScene: preload SFX + menu music, start the menu hum in create, stop it
on the 'shutdown' event (v4 never calls shutdown() on transition).
- data/ship.json → stats: hullIntegrity/shields/cargoHold/mineralStorage,
exposed as ship.stats for future combat/trading/mining systems.
- Tests: dev/sfx.test.mjs and dev/music.test.mjs cover the shared voices,
guards, and data contracts; ship-behavior and system-hud tests extended
for base stats and the mining hum lifecycle.
- New js/ui/SignalCompass.js renders a faint ring around the ship with soft radio-signal markers at each undiscovered in-region object's bearing, with full→linear-fade envelope and per-id pulse phase
- Signals track live ship position each frame, drop on discovery, and re-emit on rescan; scene drives state, renderer is stateless
- data/signalCompass.json configures radius, lifetime (20s full + 5s fade), colors, signal/ripple/pulse/ring geometry with enabled gate
- dev/signal-compass.test.mjs unit-tests the pure signalAlpha envelope, bearingAngle math, and config sanity via Node
- dev/signal-compass-flow.{html,mjs} headless browser flow test boots real GameScene, presses SCAN, and verifies emission, fade, expiry, and discovery-drop behavior
- Swap the command deck's "Build" slot for a "Scan" slot that fires an
omnidirectional sonar pulse from the ship across the tether region.
- The wavefront is clipped to the union of tether circles: it is absorbed
at the rim (bright absorbing arc + TetherField.excite shiver) instead of
flying into empty space.
- Objects inside the region ring as the front crosses them (shock rings,
RGB ghost, scale pulse); the starfield ripples and the camera thumps /
rolls with the wave, then restores itself on completion or cancel.
- New modules: js/scan/ScanPulse.js (rendering + lifecycle),
js/scan/ScanGeometry.js (pure circle-circle window math).
- All tuning lives in data/scan.json; the one SFX is the new "scan" key
in data/sfx.json. Results are a seam for now — finishScan logs the
in-region objects and reports the count in the console slot.
- Tests: dev/scan.test.mjs (geometry unit tests) and
dev/scan-test.html/.mjs (headless flow test driving the real scene).
- Add Terran shop placeholder assets (two PNGs + one MP4).
- GameScene now passes the world's type label and tether level into the SurfaceScene launch payload; new tetherLevelFor() resolves a world's anchored tether (by label or anchor position) for that line.
- SurfaceScene builds/decodes a two-line HUD (Ethnocentric name in the menu title shade with black stroke, Centauri line below) while the surface clip loops, driven by the shared scramble decode; it is torn down on take-off and shutdown.
- landing.json gains a hud config block for margins, fonts, spacing, strokes, and colors.
- Added dev-only harnesses (surface-hud.html/.mjs test page and surface-hud-shot.html/.mjs screenshot probe) to verify the HUD styling, decode settle, payload, and teardown in headless Firefox.
- Introduce a `takeoff` video entry in landing.json for each world; missing/
null entries leave the surface instantly (no regression)
- SurfaceScene.takeOff now plays the one-shot take-off clip full-screen,
hides the deck, and only then stops the scene and wakes GameScene
- Guard against a clip that never starts or runs past its duration so the
player is never stranded; finishTakeoff is idempotent
- Update surface-flow/surface-shot dev harnesses to assert and screenshot
the new take-off stage
- Re-map gas giant frames in planets.json from [0,1,2] to [3,4,5] to match
their sheet positions
- New SurfaceScene overlays GameScene when the player requests landing:
plays a one-shot landing clip, then a looping surface clip behind a
dedicated command deck (Shop/Build/Ship + Take Off + Menu).
- Wire comms panel "Request Landing" / "Land" buttons to launch the
surface; TAKE OFF stops SurfaceScene and wakes GameScene so the run
resumes exactly where it left off.
- ActionBar accepts an optional `buttons` override so scenes can supply a
different slot list while sharing layout/colors/animation.
- SavePanel gains a `stateScene` option so saves from the surface read
state from the paused GameScene underneath.
- Add data/landing.json (per-frame video map) and four placeholder
surface clips; CommsPanel now passes its full target payload through to
onAction so landing metadata survives the round-trip.
- Add dev harnesses: headless-Firefox screenshot helper, click-path test
for the real user flow, and a surface-flow assertion suite with parked
screenshot pages.
- New CommsPanel UI: world-anchored rusty-metal/green-phosphor console that opens on planet or station click, showing name decode, reputation bar (41 marks, -20 to +20), and REQUEST LANDING / LAND / CANCEL buttons
- New Station entity with procedural rendering (deep-space station ring + solar wings, or waypoint beacon), solid collision, and comms-target support
- data/stations.json configures station kinds, sizes, ship clearance; added to manifest
- GameScene wires click-to-fly + panel open together, handles button/outside/ESC close race, ESC priority, and commsAction seam for future landing logic
- Reputation.marksFor() computes lit-mark count for the gauge; covered by new unit tests in dev/reputation.test.mjs
- Dev tooling: comms-click (real-input interaction test), comms-shot (screenshot states), wdshot setup-step logging
- Introduce Reputation module (js/reputation/Reputation.js) tracking player standing on planets and space stations on a −20…+20 scale, with the home world pinned at +20
- Add data/reputation.json config for scale bounds, neutral default, and home-world standing
- Assign stable seed-deterministic ids to planets (<systemId>-p<ordinal>) and settlements (<systemId>-s<n>) in SystemGenerator so saved standing lines up with regenerated galaxies
- Wire reputation into the save system (captureState/prepareLoad/resetRunState) with backward-compatible loading of pre-reputation saves as all-neutral
- Add faction-check placeholder: standingFor() resolves home → stored → owner/faction → neutral, with factionStanding() stubbed for the upcoming factions layer
- Register a single Reputation instance in GameScene via the shared registry (same pattern as Discovery)
- Add dev/reputation.test.mjs covering scale clamping, home pinning, serialization round-trips, legacy/corrupt record handling, save integration, and place-id stability
- Introduce `Ship.state` ('normal' | 'mining') with `setState()` and an
`onStateChange` callback so the scene can react to state exits.
- Any player-initiated movement (world click or compass autopilot) now
drops the ship back to 'normal', which ends the mining sequence via the
scene's handler; Mining.update() enforces the gate as a belt-and-braces.
- Rename `Mining.isLocked` to `isActive` and update GameScene click logic:
clicks no longer hold during extension — they move the ship and abort the
arm's reach.
- Tighten the popup-button swallow window from 300 ms to 20 ms and add a
`containsScreen()` hit-test so only the exact button press is consumed,
not subsequent deliberate clicks on the panel area.
- Expand the headless mining test to cover the new movement-abort contract:
world click mid-extension breaks the arm, autopilot retracts the beam,
and ship state transitions are asserted at every stage.
- Widen the data-driven base width and clamp the panel to at least the widest button plus padding, since MenuButton measures itself in the live web font and won't shrink below its label.
- Rewrite layout() with a top-edge-relative p() helper so the header/MINE/CANCEL order stays consistent whether the popup opens above or below the anchor.
- Add dev/popup-shot.html/.mjs to boot the game headless, await the typefaces (same contract as js/main.js), open the mining menu on a deterministic cluster, and report panel/button geometry plus fit booleans.
- Extend dev/wdshot.mjs with an optional settleMs argument so pages with top-level awaits (font loading) can finish their async boot before the setup script runs.
- Click a rock to open a world-anchored mining popup (Mine/Stop/Cancel)
- Mining state machine locks the ship during the ~1.5 s arm extension, then fires an animated beam with ore motes, shock rings, sparks, and periodic surges
- ESC breaks the beam or closes the menu; outside clicks dismiss without flying
- Generalize the top-center console toast so discovery, tether, and mining calls share one slot
- Add a "mining" SFX entry (system-scan.mp3) and update preload/test to cover all configured sounds
- Ship a headless browser test (dev/mining-test.*) that drives the full arm sequence through the real input pipeline
- Pin DOWNLOAD ALL button's right edge to the panel inner boundary using its actual rendered width instead of a fixed 210px assumption that let the label spill past the window
- Drive title/subtitle decode animations through both 'opening' and 'shown' states so the scramble completes rather than freezing mid-glyph when the crack-open settles
- Set `card.locked` (no underscore) to match what SlotCard.press() actually gates on, preventing stale `_locked` writes from leaving cards inert
- Add a real pointer-press helper in the dev test that maps canvas coords through the FIT-mode zoom and dispatches mouseover/down/up to exercise the full click pipeline end-to-end
- Replace the no-op `ignorePointer` idiom with a new `setInteractiveEnabled` helper that toggles Phaser v4's `input.enabled`, so hidden/inert UI elements (sub-bar buttons, save cards, scrims, confirm buttons) stop swallowing clicks
- Set explicit depths for the menu sub-bar (60) and save panel (70) so they paint above the command deck, HUD, and world objects as intended
- Add a headless-browser z-order + input-state probe (`dev/zorder-test.*`) that boots the real engine and asserts both paint order and hit-testing behavior across open/close transitions
- Add gas giant landing video asset
- GameScene now closes the menu sub-bar for any pointerdown outside it
instead of only swallowing clicks that land on the panel, so world, deck,
HUD, and compass taps all dismiss without triggering fly-here, dossier,
or navigation side effects.
- autopilotTo() bails while the bar is open or closing, preventing a
racing compass chip click from navigating after it just dismissed the bar.
- Add MenuSubBar.closing getter so callers can detect the close animation
state and treat the bar as input-dead during that window.
- Ship a headless CDP test (dev/clickout-test.html/.mjs) that drives the
real Phaser input pipeline to verify every dismiss path, button firing,
re-openability, and the autopilot guard.
- SaveManager: 10-slot localStorage bank (put/get/clear/list/export), corrupt-bank recovery, failed-write surfacing, bulk JSON export
- SaveData: captureState / prepareLoad / consumeRestore / resetRunState — seed + player state round-trip through a shared registry
- MenuSubBar: fold-up drawer off the deck's MENU button (Save Game / Load Game / Return to Main Menu) with animated open/close
- SavePanel: 10-slot pop-up in save or load mode; overwrite + load confirm dialogs; "download all" bulk export; toast feedback
- ConfirmOverlay: split-open confirm dialog for destructive actions
- SlotCard, Toast: per-slot cards and inline feedback strips
- MenuScene: Load Game button (grayed while bank is empty) opens the same panel in load mode
- GameScene: menu sub-bar + save panel wired into deck action, ESC handling, input blocking while modal, applyRestore on scene boot
- Tether HUD moved from above the MENU button to upper-right corner, stacking downward
- MenuButton: disabled (grayed) state and screenFixed option for scrolling-camera UI
- New SFX assets (ui-click, ui-hover) + updated existing FX; gasgiant planet image
- Dev tooling: cdp-firefox.mjs, wdshot.mjs (headless Firefox via geckodriver), saves-ui-test.html/.mjs (integration driver), saves.test.mjs (Node unit tests), system-hud test updates
- Add sound effects (construct, deconstruct, discovery) with config in data/sfx.json; play on dossier arrival/fold and new-object discovery; silently no-op when disabled or asset missing
- Rework the system dossier into a full lifecycle: open-by-default on arrival, one-shot auto-fold at 10 s, click-to-toggle (name + caret), reverse deconstruction order, state caret that swings with the open/closed state
- Add ScrambleDecode `reverse` mode so lines can deconstruct (value → static → '') for the fold animation
- Reposition the tether readout to bottom-right above the deck's MENU button, right-aligned and bottom-anchored; fall back to left column when the deck is disabled
- Expand decode and system-hud tests to cover reverse decode, the full dossier lifecycle (toggle, auto-fold cancellation, mid-animation guard), tether HUD layout, and SFX wiring
- New `js/utils/Decode.js` with a pure `ScrambleDecode` class plus
`DECODE_CHARS`, `DECODE_DURATION`, and a per-line `decodeDur()` helper,
so one effect serves both scenes.
- MenuScene now delegates its seed scramble to the shared utility instead
of inlining the reveal math and alphabet.
- GameScene's system dossier types itself in on arrival: each line starts
empty and decodes left-to-right, staggered top-to-bottom, with the
timeline anchored on the first update frame; state self-clears once all
lines settle so `update()` pays nothing afterward.
- Add Node dev tests for the shared effect contract (`dev/decode.test.mjs`)
and for the real GameScene dossier decode against actual galaxy/report
data with a stubbed scene (`dev/system-hud.test.mjs`).
- Document both new test commands in the README and update the planets
PSD asset.
- Replace the old per-rock placement retry loop with a deterministic pairwise relaxation pass (relaxRockGaps) that enforces a gapFactor x (r1+r2) separation between every pair, so rocks keep a subtle visible gap instead of interpenetrating
- Add cluster.gapFactor (default 1.12) and asteroids.compassColor (#c8d2e0) config keys, with updated comments in data/asteroids.json and SystemGenerator.js
- Tint the DiscoveryCompass arrow per target: bake the arrow texture white and tint it to the target's color (worlds keep theme neon cyan, clusters use their light gray)
- Extend dev/asteroids.test.mjs to assert every rock pair respects the gapFactor separation
- Generate 4–8 rock groups per system with seeded placement, spacing, and slow tumble/drift motion driven by data/asteroids.json
- Add AsteroidCluster entity (Phaser container) with halo, dust motes, per-rock spins, and ship keep-out collision reusing Planet.resolve
- Integrate clusters into GameScene: load spritesheet, constrain ship post-physics, include in click-to-fly/autopilot/discovery/compass
- Synthesise unique cluster names via NameGenerator.asteroid (syllables + field suffix)
- Add dev test suite and ?seed= param for deterministic galaxy generation
- Replace the annular-band scatter (minOrbit/maxOrbit/minEdgeGap) with a ring-based placement: objects sit on one or two orbits around the home world, governed by center-to-center `minSpacing` and `maxNeighbor`
- Free-space stations (deep-space stations, waypoints) are now layout objects alongside planets; planet-bound settlements stay features of their planet
- Handle the N=11 case (9 planets + 2 stations) with an inner 3-ring + outer 8-ring; chain outward defensively for any larger N
- Update tests to verify the new spacing invariants, the two-orbit case, and determinism including station positions
- Refresh PROJECT_NOTES and the data-file comment to describe the orbit model
Two small naming/labeling fixes:
1. The home planet was always the generic "Terra". It now takes the first
name off the STARTING system's planet-name deck (SystemGenerator), so it
reads as a real place and can never clash with one of that system's
planets. Stored as content.homeName (only the starting system has one);
the old planets.homeName stays as a fallback. Used for the compass
home chip and the tether anchor label.
2. Confirmed/sharpened the compass name tag: each discovered off-screen
object's arrow chip already renders TYPE + NAME — now the home world
shows its bank name too (e.g. "HOME WORLD · ESHKAELURA").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Planets and space stations were previously made by stitching a root word to
a kind noun ("Kest Skyspire", "New Natick IV"), which read like labels, not
names. They now draw from deep, hand-picked name BANKS in data/naming.json:
- banks.planet — colonial "New Denver"/"Port Tucson" worlds mixed with
alien "Klaxoria"/"Vexithunhal" worlds (269 names).
- banks.station — official designations ("Deep Space SC-145", "Nav Relay
V-77") mixed with smuggler hangouts ("Hell's Hideout",
"The Rusty Anchor") (165 names).
Assignment is a seeded shuffle of the whole bank (NameGenerator.planetDeck /
stationDeck), dealt out per-system in order, so a system never repeats a
name until the pool is exhausted (impossible at these sizes). The decks come
from dedicated Rng.derive(...) streams, keeping generation order-independent
(lazy === eager). Stars and the galaxy still synthesize from syllable pools
(unbounded), and the home world keeps its configured name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The player starts with a level-1 tether anchored on the home world:
a circular zone 5120 px from the planet's center. The ship may fly
anywhere in the UNION of its tethers' zones — where zones overlap there
is no line and no wall — and the union's boundary is a hard barrier,
rendered as a thick cyberpunk glitch dotted line (marching dashes,
additive glow + RGB fringe, ambient glitch bursts with dash flicker,
radial displacement and data drops, contact shudder + camera kick +
throttled console toast where the ship bumps it).
- data/tether.json: level radii (5120 x 1.25^lv), the starting tether,
barrier line/look, glitch and contact tuning (manifest-registered)
- js/tether/Tether.js: pure range math (union membership, clamp to the
nearest union boundary point + normal, visibleArcs = each tether's
share of the union boundary) — Node-tested
- js/tether/TetherField.js: scene-facing field — add/remove/setLevel
seam for future builds/upgrades, per-frame ship constraint, and the
barrier renderer (on-screen dot culling only)
- js/scenes/GameScene.js: home tether at the system origin, HUD
readout (level / range / anchor per tether), click-to-fly and
autopilot targets clamped to the union boundary, contact feedback,
destruction on shutdown
- dev/tether.test.mjs: 29 checks incl. the two-direction boundary
property (no line in overlaps, no gaps in the line) and a numeric
nearest-boundary cross-check (worst 0.79 px)
- dev/smoke-game.mjs: ?tethers / ?homeLevel / ?report dev hooks for
manual and headless verification
- README + PROJECT_NOTES: the system, its rules and its layering
Verified in headless Chromium: clean boot (menu + game), barrier
rendering (calm and glitch-burst frames), union boundary with two
tethers (arcs meet at the rim crossings, lens is line-free), ship
clamped exactly onto the rim from outside, contact toast fired.
- Load `assets/images/ships-player.png` as a Phaser spritesheet via
`data/ship.json` (`texture`, `frameWidth`, `frameHeight`, `frame`)
- Add `artFacing` config; Ship applies a constant `artOffset` so heading
math is unchanged while the frame art (facing north) renders correctly
- Scale sprite as `(size * scale) / frameWidth` to preserve the 46 px world
hull size and collision radius regardless of frame dimensions
- Fall back to the built-in procedural dart when the sheet is missing or
fails to load, with a console warning in GameScene.create()
- Update ship-behavior tests to account for `artOffset` in heading checks
- Add source art (`ships-player.psd`, landed-ship reference) and a demo
video; document the new pattern in README and PROJECT_NOTES
- DiscoveryCompass name tags are now interactive buttons with hover/press feedback; firing onSelect hands the target id to the scene, which retargets the ship via GameScene.autopilotTo (aim at the keep-out rim on the approach side).
- Compass layout reserves the command-deck strip (reserveBottom) so chips/arrows are never buried under the deck.
- Click-to-fly now ignores clicks that land on a compass chip (DiscoveryCompass.contains), keeping autopilot and fly-here from colliding.
- Tests cover the chip hit test, onSelect seam, and reserveBottom clamping; docs updated to describe the new autopilot behavior.
- Port the menu's GlitchText chromatic fringe to ActionBar: additive
magenta/cyan icon ghosts, label ghosts with per-slot phase, and a panel
outline ghost that activates during glitch bursts.
- Add a tiled-scanline overlay (1 dark row per pitch) matching CyberOverlay,
configurable via actionbar.scanline with theme.overlay.scanline defaults.
- Replace idleGhost with animation.rgb (idleOffset/burstOffset/idleAlpha/
burstAlpha); level now decays ~90 ms after triggerBurst for the fast-snap
GlitchText feel while slice bars linger.
- Update README, PROJECT_NOTES, and research-builds tests to cover the new
scanline and rgb config keys.
- New ActionBar component (js/ui/ActionBar.js) renders a procedural cyberpunk
bottom bar with six slots (Research, Build, Ship, two reserved, Menu),
boot flicker, rail comet, glitch bursts, and RGB shimmer; fully driven by
data/actionbar.json
- Data contracts for the player's progression loop: research.json (time-based,
one project at a time, gates builds/research) and builds.json (credits +
minerals, ship/planet/station categories), both with _template docs and
empty maps; registered in manifest.json
- GameScene wires the deck in, routes clicks over it away from fly-to, pins
the hint above the bar, and drives per-frame update/shutdown
- New dev/research-builds.test.mjs pins the data contract (manifest entries,
rule knobs, template fields, six-slot order)
- cdpshot.mjs: cross-platform temp dir + --no-sandbox for container/CI
- README and PROJECT_NOTES updated with the new loop, deck docs, and a Phaser
v4 scrollFactor-on-screen-fixed-container quirk
- Discovery rule: ship within `discovery.distance` (540 px) of a world's edge discovers it once per system; state is pure, save-ready (`Discovery.toJSON/fromJSON`)
- Off-screen compass (`DiscoveryCompass`): themed screen-edge arrows with type/name chips point back at discovered worlds; geometry helpers (`edgeAnchor`, `circleInView`, `lerpAngle`) exported for Node tests
- Solar-system layout: `layoutSystemPlanets` places generated worlds in a deterministic annular band around the home world, enforcing edge-to-edge separation and per-class scale/tint from `data/planets.json`
- All system planets are solid to the ship (collision + click-clamp), not just home
- Rim ping + HUD toast on discovery (`celebrateDiscovery`)
- New planet class pools (rocky/gas/ice/lava) with frames, `classScale`, `classTint`, and `typeLabels`
- Smoke-game dev hooks: `?ship=x,y` and `?near=px` for screenshotting discovery/compass states
- Node test suite (`dev/discovery.test.mjs`) covering rule boundaries, per-system state, JSON round-trip, layout bounds/determinism, compass geometry, and class pools
- Allow planet kinds to specify multiple sheet frames and pick one with a seed-derived Rng so the same galaxy always gets the same home world.
- Add Ethnocentric and Centauri font assets.
Adds the player's home planet to the starting system, rendered from the new
planets.png spritesheet. The planet is a solid disc: the ship may come within
shipClearance of its rim but can never cross it, and clicks on the planet
clamp to the keep-out rim so the ship always has a reachable destination.
- js/entities/Planet.js: solid-disc entity with a circle keep-out constraint
(constrainShip/resolve, pure and scene-free) plus edgePoint/aimPoint helpers
- data/planets.json (+ manifest entry): texture, frame size, scale, homePlanet,
shipClearance, spawnDistanceFromEdge
- Ship.js: exposes a radius (half the hull width) for the keep-out math
- GameScene.js: loads the spritesheet, spawns the home world at the origin,
starts the ship ~150 px off its rim in a seed-derived direction, and applies
the constraint each frame after the ship moves
- dev/planet.test.mjs: Node tests of the constraint against real config;
dev/test-game.html gains a <base href> so asset paths resolve from /dev
The galaxy was settled long before the player arrives. Systems now
host colonies on habitable worlds, mining stations over resource
worlds, cloud bases riding gas giants, stations adrift in open
space, and beacons — or report "charted · unclaimed" when empty.
- data/settlements.json: the shared kind vocabulary (label, theme,
population range, anchor type); add a kind = JSON + naming pool.
- Per-archetype rates in data/systems.json (chance + required
planet class) and a core→rim density gradient in data/galaxy.json
(falloff/floor), keyed off each record's new rNorm.
- Draws stay in a stable order on the system's own Rng stream, so
lazy and eager generation still produce identical content.
- Every settlement keeps owner: null, a reserved seam for the
factions/pirates that will claim them later.
- NameGenerator.station() names from anchor + kind ("Kest Skyspire").
- SystemReport.js is a pure, testable formatter (dossier lines,
"charted · unclaimed" for empty); GameScene's HUD renders it and
Node tests cover structure, mix, gradient, naming, lazy === eager.
- Main menu Galaxy Seed panel: displayed, editable, rerollable; the menu
previews what the seed builds (galaxy name, scale, archetypes) before
committing. Same seed always generates the same galaxy.
- js/galaxy/Galaxy.js: seeded roster of 40k systems (disk + core + spiral
arms, type weights, radial bands) with spatial hash for neighbor queries;
contents generated lazily on arrival — identical to eager generateAll()
because each system draws from Rng.derive(seed, 'system', id).
- js/galaxy/SystemGenerator.js + data/systems.json: six themed archetypes
whose attributes (star classes, planet count spread, class weights,
moons, belts, habitability, hazard) steer content generation.
- data/galaxy.json layout/distribution knobs, incl. distribution.rules[]
hook for future proximity/clustering rules; data/naming.json name pools.
- GameScene: current system name/identity HUD (lazy content generation
on arrival); dev boot without a menu falls back to a dev galaxy.
- dev/galaxy.test.mjs: determinism, distribution, bands, lazy==eager,
neighbor brute-force checks (40k roster in ~70ms).
- Docs: world model, determinism rules, roadmap update.
- Boot the game from a static server with no build step (Phaser 4 vendored in lib/)
- Main menu (New Game button) and GameScene where clicking makes the ship fly there
- All tunables live in data/*.json (game/menu/ship), loaded via manifest + Config singleton
- Ship behavior driven by arcade physics (thrust, drag, maxSpeed, arrival) with a Node test harness (dev/ship-behavior.test.mjs) and a dev smoke page that boots straight into GameScene
- Project conventions documented in docs/PROJECT_NOTES.md; README rewritten to describe layout, run instructions, and dev tools