diff --git a/README.md b/README.md index bf17178..141154d 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,10 @@ node dev/server.mjs 8080 (research, tether level); installed ones read **BUILT ✓**. Selecting a build shows its dossier (glyph + description) with the **highlighted cost** and a **BUILD** button that appears only when the build is - available and affordable (one build at a time, `maxConcurrent: 1`). The - home world starts with **Tether – Level 1** already installed; **Tether + available and affordable (one build at a time, `maxConcurrent: 1`). + Every world — planet or deep-space station — gets **Tether – Level 1** + installed automatically the moment you first land (free + instant, no + console step); the home world ships with it from the start. **Tether – Level 2** needs the level-2 tether research plus a world holding a level-1 tether, and installs in **20 s for 200 minerals** — the planet's tether range then grows from 5120 to 10240 px (world state that outlives diff --git a/data/builds.json b/data/builds.json index df223f9..8776686 100644 --- a/data/builds.json +++ b/data/builds.json @@ -46,10 +46,10 @@ "builds": { "tether-l1": { "label": "Tether - Level 1", - "description": "The world's first tether: a level-1 field (range 5120 px) anchoring the ship's travel rim around the planet. The home world ships with it already in place; on every other world this is the build that installs it.", + "description": "The world's first tether: a level-1 field (range 5120 px) anchoring the ship's travel rim around the world. Installed automatically the moment you first land — free + instant, on planets and deep-space stations alike (GameScene.ensureLandingTether); the home world ships with it already in place.", "icon": "tether", "category": "planet", - "targets": ["planet"], + "targets": ["planet", "station"], "cost": {}, "duration": 0, "requires": [], @@ -187,7 +187,7 @@ "icon": "tether | tether2 | tether3 | anchor | signal | mining | mining2 | storage | diamond — procedural glyph (js/research/ResearchIcons.js)", "category": "planet | cargo | mining — which tab of the console (registry above)", "targets": ["planet"], - "_targets": "[\"planet\"] = installed on the planet's surface; [\"ship\"] = a SHIP upgrade (mining arms/storage): built from any planet's console, the effect is ship-wide and it counts as BUILT wherever the ship lands (never re-bought)", + "_targets": "[\"planet\", \"station\"] = installed on the world's surface (a planet / a deep-space station); [\"ship\"] = a SHIP upgrade (mining arms/storage): built from any planet's console, the effect is ship-wide and it counts as BUILT wherever the ship lands (never re-bought)", "cost": { "credits": 0, "minerals": 200 diff --git a/data/map.json b/data/map.json index 54ed37a..dc42f9a 100644 --- a/data/map.json +++ b/data/map.json @@ -1,5 +1,5 @@ { - "_comment": "MAP — the deck's MAP button (to the right of SHIP): the full-screen cartography console, js/ui/MapWindow.js (depth 80, the ResearchWindow idiom). LEFT = the cartography feed (assets/videos/map.mp4, a muted 2:3 loop, the same recipe as the research archive feed). RIGHT = tabs on top (CURRENT SYSTEM — the live system chart, SYSTEM — a CHARTED star's chart, picked from the GALAXY tab; armed until then, and GALAXY — the whole-galaxy chart: glowing, pulsing stars per archetype, the jump-lane web with traveled lanes bright + flow packets, the charted region's outline — js/ui/GalaxyView.js), and the readout on the bottom (per tab: the system's discovery/resources, or the galaxy's charted stats). THE CHART (the magic, drawn to a canvas texture): the whole solar system — every object's extent (planets, stations, gates AND asteroid clusters, discovered or not) + `bounds.padding` px of padding on each edge — fitted to the plate. Only DISCOVERED objects are drawn (planets / stations / gates / rock fields, small representations). The TETHER RANGE is drawn as the union boundary (Tether.visibleArcs) with a soft zone glow inside, and everything OUTSIDE the tether union is grayed out (a fog-of-war dim pass with feathered rim) — so the chart reads as 'here is what you have charted, and here is the room to expand'. A live SHIP marker + hover tooltips + click-to-autopilot (GameScene.autopilotTo) sit on top. All colors/motion are steered here.", + "_comment": "MAP — the deck's MAP button (to the right of SHIP): the full-screen cartography console, js/ui/MapWindow.js (depth 80, the ResearchWindow idiom). LEFT = the cartography feed (assets/videos/map.mp4, a muted 2:3 loop, the same recipe as the research archive feed). RIGHT = tabs on top (CURRENT SYSTEM — the live system chart, GALAXY — the whole-galaxy chart: glowing, pulsing stars per archetype, the jump-lane web with traveled lanes bright + flow packets, the charted region's outline — js/ui/GalaxyView.js, and SYSTEM — a CHARTED star's chart, picked from the GALAXY tab; armed until then), and the readout on the bottom (per tab: the system's discovery/resources, or the galaxy's charted stats). THE CHART (the magic, drawn to a canvas texture): the whole solar system — every object's extent (planets, stations, gates AND asteroid clusters, discovered or not) + `bounds.padding` px of padding on each edge — fitted to the plate. Only DISCOVERED objects are drawn (planets / stations / gates / rock fields, small representations). The TETHER RANGE is drawn as the union boundary (Tether.visibleArcs) with a soft zone glow inside, and everything OUTSIDE the tether union is grayed out (a fog-of-war dim pass with feathered rim) — so the chart reads as 'here is what you have charted, and here is the room to expand'. A live SHIP marker + hover tooltips + click-to-autopilot (GameScene.autopilotTo) sit on top. All colors/motion are steered here.", "enabled": true, "title": "STELLAR CARTOGRAPHY", "meta": "NAV DATA // LIVE FEED", @@ -12,25 +12,25 @@ ] }, "tabs": { - "_comment": "The sockets above the chart (top right), equal widths. `currentSystem` is the live system chart (default). `system` is a CHARTED star's chart (the same painter, no ship/tether) — it wakes from standby the first time the player picks that star on the GALAXY tab; until then it's the armed killswitch. `galaxy` is the whole-galaxy chart (js/ui/GalaxyView.js). `standby: true` on ANY socket is the killswitch — the tab renders dimmed with its standbyTag and a press fires the scene's onLocked (a console toast) instead of switching.", + "_comment": "The sockets above the chart (top right), equal widths, in this order. `currentSystem` is the live system chart (default). `galaxy` is the whole-galaxy chart (js/ui/GalaxyView.js). `system` is a CHARTED star's chart (the same painter, no ship/tether) — it wakes from standby the first time the player picks that star on the GALAXY tab; until then it's the armed killswitch. `standby: true` on ANY socket is the killswitch — the tab renders dimmed with its standbyTag and a press fires the scene's onLocked (a console toast) instead of switching.", "currentSystem": { "id": "currentSystem", "label": "Current System", "accent": "#00e5ff" }, - "system": { - "id": "system", - "label": "System", - "accent": "#a8ffc4", - "standby": true, - "standbyTag": "NO TARGET" - }, "galaxy": { "id": "galaxy", "label": "Galaxy", "accent": "#ffc94d", "standby": false, "standbyTag": "OFFLINE" + }, + "system": { + "id": "system", + "label": "System", + "accent": "#a8ffc4", + "standby": true, + "standbyTag": "NO TARGET" } }, "galaxyLockedToast": "GALAXY MAP OFFLINE — SECTOR DATA NOT ACQUIRED", diff --git a/docs/PROJECT_NOTES.md b/docs/PROJECT_NOTES.md index 2f529c2..e82f745 100644 --- a/docs/PROJECT_NOTES.md +++ b/docs/PROJECT_NOTES.md @@ -396,12 +396,6 @@ the plate: - **CURRENT SYSTEM** — the system chart (canvas-painted: discovered objects, the tether union, the fog of war), hover + ENGAGE AUTOPILOT, wheel zoom / drag pan. Unchanged by the galaxy work. -- **SYSTEM** — the same painter applied to a CHARTED star's system - (no ship/tether): pick a charted star on the GALAXY tab to arm it, then - click an object on its chart to **SET DESTINATION** (see the Route - section below). Locked ("NO SYSTEM TARGET") until a charted star is - picked. The shared `ConfirmOverlay` is label-sized to fit the dialog - (`baseWidth` + measured width, per-show `width`/`height` overrides). - **GALAXY** (live — `tabs.galaxy.standby` is the killswitch; `true` returns it to the old "OFFLINE" toast) — the whole-galaxy chart, `js/ui/GalaxyView.js`, fed by `GameScene.galaxySnapshot()` @@ -431,6 +425,12 @@ the plate: `jumpThroughGate`); dormant lane / no lane → the readouts. The window is a pure view: the galaxy tab polls `getGalaxy()` on the same 500 ms cycle the system chart is. +- **SYSTEM** — the same painter applied to a CHARTED star's system + (no ship/tether): pick a charted star on the GALAXY tab to arm it, then + click an object on its chart to **SET DESTINATION** (see the Route + section below). Locked ("NO SYSTEM TARGET") until a charted star is + picked. The shared `ConfirmOverlay` is label-sized to fit the dialog + (`baseWidth` + measured width, per-show `width`/`height` overrides). **The run's footprint (save-ready):** `GameScene` keeps two registry-backed sets (the `activatedGates` pattern) — `visitedSystems` @@ -905,6 +905,22 @@ must not un-install the home world's level-1 tether. A player resuming a run always has Tether - Level 1 built on home (pinned in `dev/builds.test.mjs`). +**First-landing auto-install (tether-l1):** `GameScene.ensureLandingTether` +runs in `startLanding` — BEFORE the SurfaceScene launch, so the surface +HUD's `tetherLevel` snapshot already reads `Tether Level 1`: the world +(planet or deep-space station) hosts its level-1 tether the moment the +player first lands, with no build-console step (the build is free + +instant — `cost: {}`, `duration: 0`). It applies the `tether-l1` +effects (anchor + toast) and marks the build installed (the console +reads BUILT; the L2 `planetRequires.tetherLevel: 1` gate is met). It is +idempotent — the home seed, an installed record, or an existing tether +all short-circuit; a world that already hosts a tether without a record +is adopted, never anchored twice. The world lookups it feeds +(`_applyBuildEffects`, `tetherLevelFor`, `_rematerializeBuiltTethers`) +include `systemStations`, so station tethers anchor, level up, and +re-anchor across jump cuts like planets' do, and `applyRestore`'s +re-key list knows station names too. + **Ship-scoped builds (the Mining tab) — one-off installs, ship-wide effect:** `targets: ["ship"]` (the mining arms + storage, data/builds.json → the Mining category) are installed from any planet's console but the diff --git a/js/scenes/GameScene.js b/js/scenes/GameScene.js index 177d031..2cfa5cd 100644 --- a/js/scenes/GameScene.js +++ b/js/scenes/GameScene.js @@ -2667,6 +2667,12 @@ export class GameScene extends Phaser.Scene { // the video/music lookups need the number). `station` tells the // surface deck which clip set the frame indexes into (data/landing.json // → videos for planets, stationVideos for station variants). + // FIRST LANDING (data/builds.json → tether-l1): the world hosts its + // level-1 tether the moment the player lands — free + instant, so no + // build-console step (planets and deep-space stations alike). Before + // the launch, so the surface HUD's tetherLevel snapshot already + // reads 1. + this.ensureLandingTether(name); this.scene.launch('SurfaceScene', { frame: Number(target.frame ?? target.sheetFrame ?? 0), name, @@ -2688,6 +2694,7 @@ export class GameScene extends Phaser.Scene { if (!name || !this.tetherField) return 0; const obj = this.systemPlanets.find((p) => p.discoveryName === name) ?? + this.systemStations.find((s) => s.discoveryName === name) ?? (name === this.planet?.discoveryName ? this.planet : null); if (!obj) return 0; const t = this.tetherField.tethers.find( @@ -2696,6 +2703,36 @@ export class GameScene extends Phaser.Scene { return t ? t.level : 0; } + /** + * FIRST-LANDING INSTALL (data/builds.json → tether-l1): the moment the + * player lands on a world — a planet or a deep-space station — the + * world hosts its level-1 tether (5120 px, data/tether.json), with no + * build-console step: the build is free + instant (cost {}, duration + * 0), so the landing simply completes it. The record then reads BUILT + * in the console, and the level-2 build's planet gate (a world holding + * a level-1 tether) is met. Idempotent — the home world's starting + * seed (tether-l1.starting) already records it, and a world that + * already hosts a tether is adopted, never anchored twice. + */ + ensureLandingTether(worldName) { + const state = this.buildState; + if (!worldName || !state) return; + if (state.isBuilt(worldName, 'tether-l1')) return; + if (this.tetherLevelFor(worldName) >= 1) { + // The world already hosts a tether without a build record — adopt + // it (the console reads BUILT, the L2 gate is met) instead of + // anchoring a second field. + state.markBuilt(worldName, 'tether-l1'); + return; + } + const def = defById('tether-l1'); + if (!def?.effects) return; // build system off / the data is gone + this._applyBuildEffects(worldName, def.effects); // anchors + the toast + state.markBuilt(worldName, 'tether-l1'); + this.playSfx('construct'); // the power-up tick — the manual build's voice + this._questRefresh(); // a tether requirement may have just landed + } + /** * Mining phase changes (Mining → onPhase): the scene's share of the * sequence — the ship's STATE, the ship lock, the console calls, the sfx. @@ -3976,6 +4013,7 @@ export class GameScene extends Phaser.Scene { // home world's name). const obj = this.systemPlanets.find((p) => p.discoveryName === planetName) ?? + this.systemStations.find((s) => s.discoveryName === planetName) ?? (planetName === this.planet?.discoveryName ? this.planet : null); const t = this.tetherField?.tethers.find( (tt) => tt.label === planetName || @@ -4402,6 +4440,7 @@ export class GameScene extends Phaser.Scene { for (const [planetName, builds] of this.buildState.built.entries()) { const world = this.systemPlanets.find((p) => p.discoveryName === planetName) ?? + this.systemStations.find((s) => s.discoveryName === planetName) ?? (planetName === this.planet?.discoveryName ? this.planet : null); if (!world) continue; // another system's world — re-anchors on the way back let level = 0; @@ -4467,6 +4506,7 @@ export class GameScene extends Phaser.Scene { const known = [ this.planet?.discoveryName, ...(this.systemPlanets ?? []).map((p) => p.discoveryName), + ...(this.systemStations ?? []).map((s) => s.discoveryName), ...(this.tetherField?.tethers ?? []).map((t) => t.label), ]; for (const [k, v] of [...fresh.built.entries()]) { diff --git a/js/ui/MapWindow.js b/js/ui/MapWindow.js index 7faa65f..1de8282 100644 --- a/js/ui/MapWindow.js +++ b/js/ui/MapWindow.js @@ -6,17 +6,16 @@ * contract. * * Right column, top to bottom: - * • tabs — CURRENT SYSTEM (the live system chart) / SYSTEM (a CHARTED - * star's chart — the same painter; armed until the player picks that - * star on the GALAXY tab) / GALAXY (the whole-galaxy chart, - * js/ui/GalaxyView.js): glowing, pulsing stars per system (colored + * • tabs — CURRENT SYSTEM (the live system chart) / GALAXY (the whole-galaxy + * chart, js/ui/GalaxyView.js): glowing, pulsing stars per system (colored * by archetype), the jump-lane web (traveled lanes bright with flow * packets, frontier lanes mid, unexplored faint), the charted region * (convex hull of visited systems, inflated), HOME + SHIP markers. * Hover = the star's readout + its link state to the current system; * click a CHARTED star = its chart in the SYSTEM tab (onStarOpen); - * uncharted stars are readouts only. Clicking a `standby` socket - * fires `onLocked` (toast). + * uncharted stars are readouts only) / SYSTEM (a CHARTED star's chart — + * the same painter; armed until the player picks that star on the + * GALAXY tab). Clicking a `standby` socket fires `onLocked` (toast). * • the CHART — the current system drawn on an offscreen canvas and * shown as a Phaser image: * - frame = the furthest objects (planets, stations, jump gates and @@ -1299,8 +1298,8 @@ export class MapWindow extends Phaser.GameObjects.Container { : []; if (!tabs.length) { tabs.push({ id: 'currentSystem', label: 'Current System', accent: '#00e5ff' }); - tabs.push({ id: 'system', label: 'System', accent: '#a8ffc4', standby: true, standbyTag: 'NO TARGET' }); tabs.push({ id: 'galaxy', label: 'Galaxy', accent: '#ffc94d', standby: true, standbyTag: 'OFFLINE' }); + tabs.push({ id: 'system', label: 'System', accent: '#a8ffc4', standby: true, standbyTag: 'NO TARGET' }); } let x = rightX; const avail = rightW; @@ -1394,11 +1393,11 @@ export class MapWindow extends Phaser.GameObjects.Container { /** * The tab switch — the plate has three lives: * CURRENT SYSTEM — the live system chart (hover, ENGAGE, zoom, pan) + * GALAXY — the whole-galaxy chart (GalaxyView: stars, lanes, + * the charted region) * SYSTEM — a CHARTED star's chart (the same painter; the ship * marker + course line are hidden — the ship isn't * in that system) - * GALAXY — the whole-galaxy chart (GalaxyView: stars, lanes, - * the charted region) * The plate's input surface moves between mapImg and the galaxy plate — * one at a time (Phaser 4's topOnly input: both catching would * double-fire). Each life is driven from the same 500 ms poll in