- Kills grant the hostile type's own `xp` (the roster sets XP-per-threat;
the curve lives in data/progression.json, 100 x n^2, capped at level 80)
- Each crossed level fires one `levelup` event: +3 hull / +2 shields with
a full heal, cyan burst + a distinct voice, and a kill-feed line — the
placeholder toughness the skill layer will subscribe to later
- Combat bar gains an XP row below the orbs (level, live gold fill,
banked / next cost; hidden when the snapshot carries no xp, so the
range bar keeps its old face)
- Kills show a gold "+N" reward floater; the level-up's SFX is the new
8th combat event (solar_flare_01)
- XpManager is pure and tested headless (node dev/xp.test.mjs); the
ledger saves as { level, xp } and old saves load fresh at level 0
- dev hooks (xp/grantXp), sfxcheck (range seven, main eight), shot
captures, and README all follow along
- Add patrol category rules: target selection (own system or faction neighbor via jump gates), deterministic waypoint route builder inside tether regions with anchor clearance and separation constraints, completable fallback for degenerate fields
- Extend SideQuestState ledger with active slot, claimed marks, and per-contract route records (waypoints + visited indices); save/restore round-trips all new fields with invariant re-establishment
- Wire GameScene seams: acceptSideQuest establishes the patrol route on the ledger; setActiveSideQuest / claimSideQuest manage the active slot and pay out exactly once; updatePatrol checks in waypoints per frame and completes the contract; _drawPatrolMarkers paints in-world beacons; patrolCompassTarget drives the yellow off-screen compass arrow (waypoint or gate)
- JobBoardWindow walks the full door state machine: ACCEPT → SET PATROL ACTIVE → active plate → CLAIM REWARD → claimed plate, with destination and route-progress lines in the detail readout
- Quests console side tab is now live: accepted contracts appear in questSnapshot with waypoint checklist, progress, reward, and claimable from the same CLAIM REWARD / SET PRIORITY doors as story quests
- SurfaceScene exposes activateSurfaceJob / claimSurfaceJob and routes side ids through the existing claim/priority seams
- Add capital-neutral-shop video + image assets; update landing.json to reference them
- Update all headless tests (sidequests, jobboard, quests) to cover patrol target/route/ledger/scene-seam behavior
- Introduce js/quests/SideQuests.js: seeded posting generation (patrol/delivery/bounty/escort), placeholder resolution, reward scaling, and a SideQuestState ledger with save round-trip + job-space validation
- Wire the feature into SaveData capture/prepareLoad/resetRunState so accepted/completed contract ids ride saves while postings regenerate from the galaxy seed
- Replace JobBoardWindow placeholders with live AVAILABLE JOBS rows and JOB DETAILS readout (contract copy, payout, ACCEPT door) driven by scene seams; refresh() repaints list/detail/status off those seams
- Add GameScene.sideQuestPostings/acceptSideQuest and SurfaceScene.acceptSurfaceJob so the accept lands on the sleeping game scene's ledger with a surface console note
- Ship data/sidequests.json (category gates all OFFLINE, description pools split neutral vs faction, reward bands, UI copy) and register it in manifest.json
- Add dev/sidequests.test.mjs (gate, determinism, voice, rewards, ledger) and extend dev/jobboard.test.mjs with live postings/selection/accept/refresh coverage
- Introduce js/ui/JobBoardWindow.js: nearly full-screen (depth 80) build-console-style window with a muted looping 2:3 feed on the left, and AVAILABLE JOBS / JOB DETAILS placeholder sections on the right; open/close state machine, boot reveal, ambient glitch/sweep/cursor effects, ✕ close door
- Wire the Job Board slot through SurfaceScene: toggleJobBoard/leaveJobBoard, setServiceLocked (generalized from setBarLocked) so deck + drawer stand down while the board is up with the jobBoard slot kept live as a second door; pause/resume the surface clip under the console
- Preload assets/videos/jobboard.mp4 in GameScene via data/actionbar.json → surface.explore.video (2:3 aspect), adding a NO SIGNAL fallback when missing
- Update data/actionbar.json explore drawer with the jobBoard item (isHomeSystem:false, no labelActive) and the shared video block
- Add headless test dev/jobboard.test.mjs covering config, build, geometry, open/close, update ticks, and destroy; extend phaser-stub with setVisible/setDepth
- Add dev screenshot harness dev/jobboard-shot.{html,mjs} and reference shots for drawer/board/left stages
- The player's home system is safe space (no enemies), so no work should be posted on its board.
- worldContext now exposes isHomeSystem from GameScene; matchesWhere handles boolean values with list any-match.
- data/actionbar.json gives jobBoard a { isHomeSystem: false } clause instead of showing everywhere.
- explore.test.mjs covers home/non-home worlds, stations, and boolean-clause matching.
- Added the jobboard.mp4 bar clip asset; updated SurfaceScene/GameScene comments to reflect the new rule.
- Add inView() pure geometry helper to test if a craft's visual circle is at least partly within the camera's visible rect
- Gate enemy fire, auto-fire target selection, currentTarget lock, and bullet collision checks behind craftVisible() so off-screen hostiles hold lances, can't be engaged, and are immune to tracers until they fly into view
- Extend pickTarget() with an optional qualifies predicate to narrow the candidate pool (used for the on-screen rule)
- Document the rule in data/combat.json and CombatSystem class comments
- Add OAC "enemies detected" / "enemies defeated" visual comms with random voice pools, fired on CombatSystem active-flag flips
- Deny jumping or landing while a fight is live so the wave run can't be escaped or hidden on a surface
- Move the 'Pirate Problems' ambush trigger to the midpoint of the entry-gate → capital-world stretch, persisting lastJumpFrom through saves for a stable midpoint
- Prevent overlapping hit flashes from stacking, which captured the in-flash
state as the restore value and left crafts permanently white
- Track flashed crafts in a Set so the expiry path can always settle them
- Add purgeCraftTints(), called every ~250 ms from update(), that re-asserts
normal colors on any craft (tracked or roster) not holding a live flash
- Wire purgeRoster in both CombatScene and CombatSystem so the purge covers
the full player + enemy pack, not just Fx's own flashes
- Prune destroyed crafts from the tracking set to avoid leaked references
- Add tests covering overlap extension, kill-seam settle, periodic purge,
roster coverage, and dead-craft pruning
- Add Fx.releaseCraft() to settle a craft's pending white tintFlash
immediately on kill/destroy, so sprites never die mid-flash
- Add exported settleCraftTint() helper that resets tint mode and tint
to identity; call it on revive (EnemyShip), respawn, and reuse seams
- Guard restore callbacks against destroyed sprites with try/catch so a
half-restored craft can't leak white state
- Update combat screenshots
- Replace dot tracers with elongated glowing bolts (trail band + core streak + head bloom)
and a directional muzzle flare in both the range and main-game contexts.
- Add enemy impact reads: core flash, directional sparks along the travel vector,
cyan arc ripple on shield hits vs orange shards on hull hits, white craft hit-flash,
and a polished damage-number pop.
- Add player shield-sphere strike effect: full-sphere outline + bright arc centered on
the laser's incoming bearing, radial ripple, spark burst, and a shield-collapse cue
when shields break to hull.
- Add subtle world-camera shake on player hits, enemy kills, and shield-break while
keeping the HUD/combat bar perfectly still via the fx-ui camera split.
- Expose new visual knobs (bolt size/trail, muzzle, arc span, shake intensity) in
data/combat.json; fix Fx.update destroying ships on tintFlash expiry.
- New main-story quest granted after 'Special Delivery' is claimed AND its payoff comms are ACKed all the way through (unlocks.acked gate)
- Three-step checklist: speak with Trestle (trestleQ4 warning beats), defend from a rolling wave of six raiders at the gate-to-capital midpoint, return to the Neutral Capital World
- CombatSystem.startWaves gains an origin option so ambush enemies materialize in a ring around a specified point rather than off-screen around the ship
- CommsHub fires a new onAck hook before onEnd so the scene can record acknowledgments and run its grant pass in the same tick
- QuestState.isUnlockMet / grantUnlocked accept a gates.acked set; GameScene supplies it from the comms acked-ledger
- SaveData captures/restores comms.acked, combat.done, and story.capitalReturned; resetRunState clears all three on New Game
- Combat is now built for every scene (not just ?combat demo); the hostile pack spawns only in the sandbox boot
- Test suites updated: quest unlock gating, ambush wave shape, roster count 25→28, save round-trip of new ledgers
- Replace vertical gauges with draining hull/shield/energy orbs
(hull with overlapping shield on the left, energy on the right).
- Share the main game's CombatBar in the combat range and add
range energy tracking with optional regen.
- Make gauge hit flashes shape-aware: circle orbs get a soft bloom,
rects keep the legacy white pulse.
- Update combat data and assets for the new orb-strip storyboard.
- Waves.js: pure wave runner (rolling/fixed) + CombatSystem.startWaves
for quest-step combat; named scripts (probe/skirmish/raid) ship in
combat.json, driven from the console until the quest hook lands
- EnemyTypes.js: named hostiles (raider/bomber) deep-merged onto the
base craft, with per-type textures preloaded via enemySheetList()
- Hostiles now materialize off-screen (camera edge + game.spawnLead)
and fly in on their chase AI instead of popping into view mid-fight
- Wave ships are run-owned: no self-respawn, kills feed rolling
replacements, and the combat bar stays up through the lead and gaps
A shared readability layer (js/combat/CombatHud.js) for both combat
contexts — the training range and the main game's ?combat:
- Target/lock indicator: rotating brackets on the current target (the
auto-fire target — one rule, one target, js/combat/Combat.js#pickTarget).
- Threat cues: pulsing markers above each live hostile.
- Off-screen arrows: edge arrows toward hostiles outside the camera
(zoom-aware via a context-supplied worldToScreen).
- Kill feed: newest-first fading entries, top-right.
- Gauge flash: the combat bar's shields/hull gauge flashes on the hit
it took (CombatBar.gaugeRect feeds the shared CombatHud.flash).
Pure rules stay in Combat.js (pickTarget, feedPush, offscreenArrow),
covered by dev/combat.test.mjs. The main game re-anchors the pack
around the ship once its start position settles (CombatSystem
.resyncAroundShip + a short settle window in GameScene), so the demo
pack sits in auto-fire range instead of kilometers off.
All combat rules tests pass (0 failures); CDP-verified in both
/combat and index.html?combat.
- js/combat/CombatSfx.js (new): ONE event→voice map + playback path for
both contexts, using ONLY existing data/sfx.json files:
fire=scan · impact=ui_click · shield=solar_buzz · hull=mining_split
kill=thunder_01..04 (one at random) · down=deconstruct ·
respawn=discovery
plus combatSfxEnemyHit() (shield/hull decision in one place: a hit
that breaks through to the hull plays both — the shield-pop read),
loadCombatSfx() for the range's preload (the main game already loads
all of sfx.json), and a capped dev log on window.__COMBAT_SFX__ so
the wiring is verifiable headless (no audio manager) in both.
- js/combat/CombatScene.js: fire/enemy-fire/wall-clip/ram/lance/kill/
down/respawn sites call the shared path; the player-hit voices
(impact + shield/hull from applyDamage) funnel through damagePlayer.
- js/combat/CombatSystem.js: the same seven call sites, identical
wiring (no per-context copies).
- dev/combat.test.mjs: sfx block — seven events mapped, voices exist in
data/sfx.json, pairwise distinct, vol scaling, thunder pick, the
shield/hull decision, the dev log, the master switch (0 failures).
- dev/combat.sfxcheck.mjs (new): CDP check that drives real events in
/combat AND index.html?combat and asserts all seven events pass
through the shared path — PASS in both.
- js/combat/Combat.js: new pure handleFeel() step (smoothed turn
velocity, ramped/decaying throttle, instant brake) with EXACT
analytic integration of the eased turn — frame-rate independent by
construction (dev/combat.test.mjs: feel block, 0 failures).
- data/combat.json: combat.player tuning surface — thrust 2000, drag
1.8 (now actually overrides ship.drag in the range), rotSpeed 6.4,
rotApproach 20, thrustRamp 18, thrustDecay 30, brake 0.9.
- js/combat/CombatScene.js: updatePlayer runs the shared feel step.
ALSO FIXES WASD: this Phaser build's addKeys() object form only
resolves single KeyCodes names, so {up:'W,UP',...} put every name on
ONE shared Key with keyCode undefined and isDown polling could never
see a key-press (WASD was dead). Now keys drive off the working
keydown-/keyup- event API (the main game's pattern) with real Key
objects registered for capture; window-blur clears held keys.
- dev/combat-flycheck.mjs: CDP flight-feel check — thrust 60% in ~300
ms, 30° turn in ~150 ms, sustained spin ≥ rotSpeed, stop from full
in ~300 ms (headless ~24 fps budgets; PASS).
- Add CombatSystem: owns the hostile pack, tracers, player armor/energy,
auto-fire (nearest-in-range), swept-hit collisions, rams, respawn — all
driven by data/combat.json `game.*` / `energy` sections
- Add CombatBar (Diablo 4 style): vertical shields + hull gauges on the
left, energy gauge on the right, eight skill seats (keys 1–8) in the
middle; shown in place of the action deck while combat is live
- `?combat` now boots the normal game with the pack spawning when the
player starts (mouse steers, cannon auto-fires); the standalone WASD
range remains at `/combat` / `combat.html`
- GameScene wires the system into its update loop, solid constraints, and
click-through gate; exposes `window.__ORBIT_COMBAT__` for DevTools
- Extend combat tests to cover CombatSystem lifecycle (spawn, auto-fire,
hits, rams, armor, respawn, cast stub, destroy)
- New `combat.html` + `/combat` dev-server route + `index.html?combat`
boot a fixed arena where the player ship fights a pack of enemy craft;
all tuning lives in `data/combat.json` (thrust, hulls, shields, lance,
rams, respawns).
- Add `js/combat/` modules: CombatScene, EnemyShip (standoff-ring AI +
lance), Bullet, Fx, StarDrift, and pure rules in Combat.js (armor
damage, orbit step) exercised by the new `dev/combat.test.mjs`.
- Extract shared `awaitFonts` into `js/utils/Fonts.js` so both the game
and sandbox boots use one copy.
- Preload the enemy spritesheet in GameScene so the texture is ready when
combat ships in; add `ships-enemy.png` (+ `.psd`) art.
- Expose `window.__COMBAT__` (reset, spawnEnemy, player, kills) for
devtools tuning; R resets the range, P freezes it.
f' = 0.25 + f/2 on every band (x1.0/1.8/3.2/5.0/7.0): with the origin buy
fixed at x0.5, per-unit profit lands at exactly ~half the original in every
band (30->17, 67->35, 130->66, 218->110, 382->192 cr for the average
product) while distance stays the dominant term and the curve's shape is
preserved. Data-only change (economy.json); docs/ECONOMY.md table + example
updated. All invariants re-verified (anti-loop, monotone distance, far pays
more than buy, 1-hop viable) and the economy/cargo/shop-UI/scout suites
pass. dev/economics-report.mjs gains an ECON_BANDS=... A/B override and
dev/economy-bands-audit.mjs sweeps the band invariants on any band set.
- Rebalance the three cargo hold upgrades (8/12/16) from a credits-only sink to a mixed cost of credits and minerals, so expanding the hold draws on both trade income and the mining economy.
- Update research descriptions, build data, tests, docs, and code comments to reflect the new two-resource costs.
- Introduce data/research/cargo.json: a 3-node chain (8 → 12 → 16 bays) where each node is a blueprint that unlocks the matching ship-hold build
- Add `build` gate field to ResearchModel.js (attachBuildGates) so a node can require a named build to be completed before it becomes researchable, composing with the existing quest gate
- Wire both gates in GameScene and ResearchWindow so node availability, lock notes, and the RESEARCH button reflect quest + build state
- Gate cargo_bay_8 on the special_delivery quest; cargo_bay_12/16 on Improved/Advanced Mining Storage builds
- Update builds.json to require the corresponding research nodes for cargo-bay-8/-12/-16
- Add comprehensive tests covering the cargo tree structure, pure DAG availability, live gate composition, and build-side lock-step
- Split each row into a title line (name, "N left"/"SOLD OUT" strip, price,
stepper with `stock.gap` padding between blocks) and a full-width desc line.
- Add `buyList.stock` config (w, gap, fontSize, color) for the inventory strip
and use it for sizing/truncation so the name never collides with the strip.
- Update `_btnXs`, `_nameMaxW`, `_descMaxW` layout helpers to place the right
block on the title line and give line 2 the row's full text width.
- Refresh shop-ui tests to assert title-line padding, truncation budget, and
desc wrap within the full row width.
- Replace producer/demand class pricing with strict 1:1 origin assignment;
each settled system sells exactly its signature good at a discounted origin
price, and the catalog remainder becomes a hub pool resold by stations/capitals
- Make banded hop distance the dominant sell-price factor (x1.0 to x7.0),
with planet-type demand as a secondary +10% modifier, and cap every shop's
sell price at its own buy price to eliminate the v1 infinite-credit loop
- Add Stock.js for the time-gated daily batch (6 units per origin per wall-clock
day) — the economy's only stateful piece; wire it through GameScene,
SaveData capture/restore/reset, and the shop UI stepper clamp + SOLD OUT strip
- Update Economy.js with assignOrigins pass, new buyPrice/sellPrice/shopFor
logic, tradePreview stock-cap backstop (buyback exempt), and distanceFactor
- Rework shop UI: BuyList shows "N left"/"SOLD OUT" per batch-limited row and
clamps the stepper; ScoutPanel shows MADE IN (origin system) instead of GROWN ON;
ShopView wires stockRemaining/stockCapsFor/spendStock into preview and confirm
- Update all dev tests (economy, cargo, shop-ui, saves) to assert model v2
invariants including the anti-loop sweep, batch clamp/backstop, and save round-trip
- Add an optional node quest field so research techs can require a named quest's reward to be claimed before becoming available.
- Wire the scene's live questClaimed predicate into ResearchModel.attachQuestGates and the research tree loaders so node states, the RESEARCH button, and LOCKED readouts all honor the gate.
- Gate mining premium_arm and advanced_storage on the Special Delivery quest, and add data/test coverage for the quest-gate behavior.
- Every `dev/*.test.mjs` now imports the Phaser stub loader, so plain
`node dev/<name>.test.mjs` works without `--import`.
- New `dev/run-all.mjs` runs the whole suite in isolated Node processes
with filtering, concurrency, timeouts, and aggregated failure output.
- Update README and adjust dynamic imports/paths for headless test reliability.
- Waypoints were comms-only beacons with no landing or trade; a second
deep-space station is a more useful destination in their place
- The old waypoint chance in systems.json becomes
deepSpaceStation.second, so a system can roll 0–2 stations
- StationFrames now assigns a distinct sprite variant per station
(Galaxy.stationFrames is id → [frame, …]) and a two-station system
always wears two different variants
- Rename the route-gate pulse from "destination's waypoint" to
"route's next stop" so the term no longer refers to a removed kind
- Add `premium_arm` and `advanced_storage` research nodes (Tier 3) to the mining tech tree, each gated by its Tier-2 parent.
- Add `mining-arm-premium` (4.0 minerals/s) and `mining-storage-advanced` (500 capacity) ship builds in data/builds.json.
- Update existing Tier-2 nodes to declare their new children in `unlocks.research`.
- Extend test suites for the two new research nodes, their build wiring, cost/affordability, DAG layout (3 rows), and availability gating.
- Arm `oac-research-new` (trigger `researchNew`) from `_commEnded` when the `oac-research-complete` sign-off finishes and `researchAnyAvailable()` is true, so the hub FIFO plays it right behind the sign-off
- Register the new audio-only comm in `data/comms.json` and move `researchNew` out of the reserved trigger list
- Update roster count to 25 and add node checks for the beat's fields, clip presence, and zero delay in `dev/comms.test.mjs`
- Add headless browser harness (`dev/new-research-shot.html`/`.mjs`) that verifies the seam, gating (in-flight vs available), and FIFO ordering through the real `onEnd` hook
- Ship `assets/videos/arid-shop-03.mp4`
- New ShimmerCue module: pure shimmerPhase/drawShimmer cycle (yellow band sweeps face, comet runs cut-corner edge), shared by both surfaces
- ActionBar gains setShimmer + a dedicated additive layer; the RESEARCH slot wears the full face+edge recipe while a tech can start and the console is closed
- ResearchWindow._paintCues paints the edge-only shimmer on every category tab whose tree still holds an available tech, plus the ACK pulse on the "Unlock Jumpgates" plate (or SYSTEM tab when off-screen)
- GameScene._applyResearchCue switches one cue at a time: deck slot when console closed, console cues when open; rests while a project runs (maxConcurrent 1), under standby, or under the tutorial finger
- ResearchModel.hasAvailable and SystemCategory.gatesReady/gatesNodeId encode the ready rules
- data: actionbar.json animation.shimmer, research.json fx.tabShimmer (#ffd84d, comm-pulse yellow family)
- dev: Node test (research-cue.test.mjs) pinning the pure cycle model + slot/tab behavior; browser verify page (research-cue-shot.html/.mjs) with CDP capture poses
- assets: arid-surface-03 / arid-takeoff-03 videos
- New TutorialGuide.js beat table picks which control to pulse based on live comms/research records, so a loaded run resumes the same beat
- ActionBar and ResearchWindow gain setHighlight() with pulsing rims (ACKNOWLEDGE-style sonar rings in the console) that hide under deck standby/locked slots/closed window
- GameScene applies the highlight per frame before surfaces draw, tracks scanPressed one-shot, and reads targets from data/quests.json → tutorial
- Add tutorial section to quests.json plus tests for beat table, ActionBar rim gating, and data consistency (slot ids, console node matches starter research check)
- New `js/visuals/EngineFlare.js` drives nozzle glow, ignition burst, and a short fading exhaust plume from the two side-pod engines, driven by the ship's live throttle so braking dims it and Shift+click lights it up full
- Configurable via new `thrustFx` block in `data/ship.json` (enabled flag, core/plume colors)
- Hooked into GameScene lifecycle: created per scene restart, updated from the scene clock, cleaned up on landing sleep and scene destroy so nothing survives frozen mid-glow
- Add dev harnesses for visual verification: `dev/engine-flare-test.mjs` (headless Chromium CDP walk through idle → ignite → burn → coast → settled with screenshots) and `dev/particle-probe.{html,mjs}` to confirm particle velocity follows the emitter's heading
- Add new arid planet art asset
- New `js/galaxy/PlanetClasses.js` deals non-home planets from one shared deck so every class lands at ~1/N of the galaxy while star type still flavors local systems
- Config knob `systems.planetClassBalance` (enabled + bias) blends an even split with the star-mix preference; bias 0 = exactly even, 1 = old free preference
- Stamp `galaxy.planetClasses` is read back by content generator, frame pass, economy, and type preview so lazy/eager generation agree
- `rollSystemComposition` accepts optional stamped classes; falls back to per-type weighted draws when balance is off or stamp absent
- Fix arid-surface video path in landing.json (was pointing at ice surface)
- Update shop-ui and trade-scout tests to pick rows the current system actually wants, and handle home-world exit resolution
- Register Carol as a comm sender with her video clip and role
- Add four ack beats (carol-delivered-1/2/3, oac-delivered-1) under the new 'deliveredLaunch' trigger, fired when the player launches after selling the package
- Arm the chain in GameScene.onSurfaceDeparture once story.delivered is set, with the once-ledger keeping it a one-shot
- Update comms tests to cover Carol's registration, the four-beat sequence ordering, and re-arm deduplication
- Stage the payoff audio (carol q3 05-07, oac q3 01) and video assets
(arid-surface-02, carol character) backing the launch-after-sale beat.
- Add dev/delivery-launch.test.mjs pinning GameScene.onSurfaceDeparture:
arms 'deliveredLaunch' once on post-delivery departure from any world,
stays quiet pre-delivery, preserves the capital leg (launchedFromCapital,
home routing, quest refresh), and keeps the arm firing on the defensive
already-home no-op.
The Shop button on the deck is now always live — the shop sub-bar opens on every world. The "Special Delivery" capital-landing witness now gates only the shop's Buy/Sell tab (ShopView reads GameScene.isTradeUnlocked, which replaces isShopUnlocked), so trading stays dark until the player lands on the neutral capital while the rest of the shop UI is reachable everywhere.
- Rename GameScene.isShopUnlocked → isTradeUnlocked and update all references (SaveData, SurfaceScene, quests.json comments)
- Remove the deck-slot enable guard in SurfaceScene.toggleShop / setBarLocked; the slot now re-arms with the rest of the deck
- Add ShopView.isTradeUnlocked + gate checks in setTab / _onTabTap; gray the Buy/Sell tab via _refreshTabs until the gate lifts
- Update dev probes (scout-*, shop-*) to lift gs.story.capitalLanded so the trade panel is reachable
- Extend shop-ui.test.mjs with a TRADE GATE case covering closed/lifted states
- Ship now starts with cargoHold 0; the bar vignette's DONE press builds
out 4 bays (effects.cargoHold) and then places the special-package,
making the parcel aboard the "cargoUpgraded" step's witness instead of
a build-state check.
- Remove the old capital-landing handoff and SurfaceScene package flag;
onVignetteComplete in GameScene applies effects + handoff in order and
refreshes the quest checklist.
- Persist cargoHold through saves and gate jumps so the upgraded hold and
its goods survive a scene restart or load (old saves fall back to the
carried goods count).
- Add arid-land/takeoff 02 assets, fix landing.json slot 3 mapping, and
extend dev tests (jump-cargo probe, saves round-trip, vignette flow) to
cover the new beat ordering and persistence.
- Introduce VignetteHub/VignetteWindow/VignetteModel to play quest-gated
dialogue scenes over location clips on the surface (bar/shop)
- Ship first scene: Special Delivery step 1 (Trestle + Carol at the bar),
replacing the old capitalLanded check with a vignettePlayed witness
- Add q3 speech clips for Carol and Trestle; remove retired q2 Carol clips
- Wire save/restore of the played ledger, deck lock while a scene holds,
per-sound music ducking under voices, and window sizing to each line
- Add dev flow test (dev/vignette-flow.html/mjs) and model unit tests
- Point landing.json ice-land-01 entry at the new arid-* video assets
- Replace the surface deck's SHIP slot with an EXPLORE drawer that folds up a full-width secondary bar (MenuSubBar span mode) carrying per-world service items resolved by a `where` clause matcher (world / planetClass / faction, case-insensitive, list any-match)
- Add the neutral capital's BAR clip toggle: swaps the surface loop for the world's bar video, relabels the slot to LEAVE BAR, and locks every other deck + drawer button until pressed back
- Record quest checklist steps as DONE on the ledger (QuestState.completed set) so a step that stands on the home world keeps standing after the ship leaves; save/load round-trips the witness and old saves degrade cleanly
- QuestWindow fresh open now lands on the run's priority quest (tracker HUD seam) with its tab activated, falling back to the active tab's first quest when no priority is set
- MenuButton.setLabel re-fits the panel and hit rect in place for the LEAVE BAR relabel; ExploreBar.js provides the pure worldContext / matchesWhere / resolveItems item logic
- Add dev tests: explore.test.mjs (deck, items, where-clause, span, relabel) and quest-window.test.mjs (open landing + priority), plus updates to quests/research-builds tests for the new surface deck layout
- New main quest granted after claiming "Leaving the Nest": land at the neutral capital (the bar), upgrade cargo bays, then deliver the sealed package to the home world.
- Introduce a non-tradeable `special-package` good (sheet frame 114) that no shop stocks or buys back; `Economy.isTradeable` filters it out of every floor and the sell grid refuses to queue it.
- Record story witnesses (`capitalLanded`, `launchedFromCapital`, `lastLandedWorld`, `delivered`) in a registry-backed ledger, persisted via SaveData so progress survives load and reset clears it.
- Gate the surface shop behind the capital landing: deck slot disabled and `toggleShop` refuses until `isShopUnlocked`.
- On capital departure, set the destination back to the home world so the compass marks the delivery leg; delivery proof is monotonic once recorded.
- Surface console note announces the package handoff when it first lands in the hold.
- Extend economy and quest test suites to cover the new check types, non-tradeable exclusion, unlock chain, and save/load of the story ledger.
- Split trestle-arrive into an arrive-1/2 ack pair, both voiced (new q2-04/q2-05 clips)
- Quest now completes on reaching the neutral capital planet instead of entering the neighboring system; add _atCapitalWorld proximity check and arm arrival in _checkDestinationReached
- Gate capital-world landing until the quest reward is claimed, with a new capitalDenyToast denial
- Update tests, save-data comments, and fix lava-shop-02 asset reference
- TradeScout now resolves each exit's specific object (planet of that class, deep-space station, or home world) and exposes objectId/objectName so the destination points at the route's final stop rather than a bare system.
- SurfaceScene dialog and toast show "WORLD · SYSTEM" and pass the object id to GameScene.setDestination; null falls back to system level.
- ScoutPanel exit rows display the world name + hops instead of the class label, drop the per-section tap hint and footer (they collided with the last row in a short panel), and rename "SIGHT ON" to "SOUGHT ON".
- Save/restore now carries ship.credits and ship.cargo across jumps; GameScene restores both and refreshes the credits HUD. Saves tests cover capture, prepare, and legacy records without these fields.
- trade-scout tests assert every exit has objectId/objectName and that resolved ids are real objects of the exit class; scout-dest test verifies the confirmed destination names a real world.
- Add dev/scout-overlap probe (html + mjs) to screenshot the scout panel and numerically report any overlapping text boxes.
- Ship lava-planet-03 art and lava-land-03 video assets.
- Trade scout best-exit rows now open a "SET DESTINATION" confirm dialog
(SurfaceScene.askSetDestination + ConfirmOverlay), confirming sets the
GameScene destination; local-system exits are rejected with a note.
- Surface scene builds a corner credits/minerals readout over the shop clip,
refreshed after trades and torn down on leave.
- Sell-bay selection restyled: green stroke + tinted fill + upper-left
checkmark badge (data/shopui.json slot config), replacing the cyan ring.
- Add lava/capital-neutral music, videos, and landing data entries; remove
tresle.png in favor of capital-neutral-surface.png.
- New dev probes: scout-dest, shop-hud, shop-select-shot, map-dest-shot,
cdp-cast; extend shop-ui.test.mjs for selection visuals and exit taps.
- New shared RoutePulse entity draws a steady ring + two radar ripples
scaled to the host's keepout radius, with configurable alpha/period
- JumpGate wears the full pulse (route.pulse) when it is the route's next
stop; Planet and Station wear the subdued variant (route.objectPulse)
when they are the destination object in the destination system
- GameScene marks exactly one entity per frame via setRouteTarget() and
drives its update loop; all others are cleared
- Add data/gates.json sections for pulse and objectPulse geometry/alpha
- Rename laval-surface-02.png to lava-surface-02.png and add the
corresponding video asset
- Extend jumpgate, planet, and station test stubs with Phaser Display.Color
and recording graphics; add dev/route-pulse.test.mjs unit tests and
dev/route-pulse-live.{html,mjs} browser integration checks
- New js/ui/PerimeterShimmer.js: constant amber base rim + glow, and two comets in counter-streams (amber CW, neon cyan CCW) tracing the cut-corner plate perimeter; pure arc math (perimeterPath / arcPoint / arcPolyline) exported for bare-Node tests
- QuestTrackerHud promotes READY rows to the console's cut-corner plate (CyberShape.rectPoints), drives PerimeterShimmer per-frame on the absolute clock so repaints never reset comet phase, and fires a one-shot rim flash when a row is claimed; shimmers are torn down with each paint
- data/quests.json gains tracker.shimmer config (enabled, notch, periodMs, dashFrac, phaseOffsetPerRow, claimFlashMs, base, comets)
- CyberShape.rectPoints added for absolute-coord cut-corner polygons
- New dev/perimeter-shimmer.test.mjs covering arc math, counter-streams, absolute-phase invariance, degenerate configs; quest-tracker tests extended for shimmer lifecycle and claim flash
- Pressing the open tab now closes the panel (back to entry state) instead of
rebuilding it; pressing again re-opens with the selection restored, so hiding
the panel no longer loses the player's pending trade.
- Save the SELL/BUY maps onto ShopView (_pendingSell/_pendingBuy) on toggle-close
and tab switch; _restorePendingSelection() re-applies them when the panel is
rebuilt. The sub-bar stays live while closed: NET shows the delta, Clear
cancels the saved trade, Confirm executes it.
- preview()/clear()/_afterTrade()/_hasAnySelection() now treat a null tab with a
pending selection as an active trade, so the buyback path is no longer shadowed.
- Rename ScoutPanel.data to report: Phaser's GameObject reserves .data (its Data
manager), and a plain object there crashed GameObject.destroy(). Update all
callers in ShopView, tests, and dev/shop-shot.mjs.
- Simplify the scout price line to credits only ("9 CR") and drop the now-unused
priceSell style from shopui.json; update goods.png/psd.
- Add setSellMap()/setBuyMap() to TradeGrid/BuyList to restore a saved selection
onto freshly rebuilt panels, and cover the toggle flow in shop-ui.test.mjs plus
a toggle probe in dev/shop-shot.mjs.
- Tap a BUY-list row's box (outside the stepper) to swap the cargo grid for a strategy report showing the good's identity, producer/demand classes, and top-N charted exits ranked by sell price with per-unit profit.
- New pure TradeScout module computes the report from galaxy data and visited systems; new ScoutPanel renders it over the SELL grid area.
- BuyList gains tap detection (distinguishing taps from drags), a focus glow on the scouted row, and an onRowTap hook.
- ShopView wires open/close/swap semantics: same box toggles off, different box swaps report, dead-space or panel tap closes and restores cargo grid; sell-grid input is disabled while scout is shown.
- Add Trestle character art, Carol Q2 speech lines, shopui.json scout config section, and tests for both the pure logic and UI integration.
- Widen the tab sub-bar from a button-width pill to the full deck span (left/right edges of the main action bar), centering it on that span.
- Pack tabs at the left edge and Clear/Confirm + NET readout at the right edge, compressing gaps rather than button widths when the deck is narrow.
- Right-anchor the NET value (origin 1,0.5) and reserve its widest slot so growing totals never shift the label or overflow the bar.
- Extend SurfaceScene.shopAnchor to expose left/right edges for the sub-bar layout.
- Add sub-bar probe assertions in dev/shop-shot.mjs and unit tests in dev/shop-ui.test.mjs covering span, alignment, non-overlap, and NET math.
- Add carol character image and lava-land video assets.
- Rows that spill past the viewport edge used to render whole, ghosting over
the BUY title or onto the Confirm bar; now only elements fully inside the
window render (this Phaser build can't crop glyphs), and row plates draw
just their in-window slice for a clean cut.
- Clipped-out steppers are no longer tappable: hit-testing gates on element
containment, not just row visibility.
- Add a right-edge scrollbar (track + drag/jump thumb) that tracks scroll
position and hides when content fits the viewport.
- Extend shop-shot/dev tests to verify the clip invariant, dead-click
behavior, and scrollbar geometry; add a geckodriver screenshot helper.
- Center the row background plate on its own local origin so it no longer double-offsets with the object's world position, eliminating the right/down drift and stretched spacing.
- Switch stepper glyphs to a centered (0.5, 0.5) origin and recompute the tap area live inside hitAreaCallback against the current glyph size, so the box stays centered on the visible button even as qty text re-measures.
- Add dev/shop-shot probes that assert bar fillRect coords and verify plus/minus clicks register on the glyph while a point in the old mis-anchored gap does not.
- Extend landing.json with additional land/surface/takeoff/shop video entries.