From 64af461730437d2896fcce2659462ab0937eefbd Mon Sep 17 00:00:00 2001 From: Ayinkx Date: Sat, 24 Oct 2026 10:02:09 +0100 Subject: [PATCH 1/4] feat: add sector-specific planet survey briefings --- web/game.js | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/web/game.js b/web/game.js index b7cd5d5..03550f6 100644 --- a/web/game.js +++ b/web/game.js @@ -233,6 +233,21 @@ const EXPLORATION_OBJECTIVES = [ { id: "void-pattern", sectorId: "void-crown", title: "Decode the Pattern", detail: "Compare the anomaly's pulses with the beacon.", reward: 40, color: "#d9c4ff" }, { id: "void-origin", sectorId: "void-crown", title: "Find the Source", detail: "Pinpoint the final coordinate in the signal map.", reward: 50, color: "#8dffce" }, ]; + +const PLANET_SURVEY_PROFILES = [ + { sectorId: "sol-frontier", environment: "DUSTY NEBULA", hazard: "IONIZED DEBRIS", + focus: "TRACE THE ANCIENT BEACON", fieldNote: "Old relay fragments still echo the beacon pulse." }, + { sectorId: "ember-reach", environment: "MAGNETIC ASH", hazard: "STELLAR SURGES", + focus: "RECOVER EXPEDITION TELEMETRY", fieldNote: "Red-giant storms can distort survey readings." }, + { sectorId: "silent-expanse", environment: "SUBZERO STATIC", hazard: "ICE SHEAR", + focus: "ISOLATE THE REPEATING ECHO", fieldNote: "Frozen relays repeat a response from beneath the ice." }, + { sectorId: "void-crown", environment: "GRAVITY LENSING", hazard: "SPACETIME DISTORTION", + focus: "ALIGN THE FOUR-PART SIGNAL", fieldNote: "Anomaly pulses match the beacon's navigational rhythm." }, +]; +function planetSurveyProfile(sectorId) { + return PLANET_SURVEY_PROFILES.find(profile => profile.sectorId === sectorId) || null; +} + const freshCampaign = () => ({ version: 3, completed: [], selected: "sol-frontier", discoveries: [], exploration: { "sol-frontier": [], "ember-reach": [], "silent-expanse": [], "void-crown": [], } }); @@ -3013,10 +3028,18 @@ class Game { c.beginPath(); c.ellipse(px, py + 12, radius + 28, 34, -0.18, 0, TAU); c.stroke(); circle(c, px + 158, py - 16, 10, "#e6f7ff"); circle(c, px + 158, py - 16, 22, "rgba(120,220,255,0.16)"); - text(c, "SURVEY PROGRESS " + done + " / " + objectives.length, W / 2, 442, 15, "#b9d7ed", true); + const profile = planetSurveyProfile(sector.id); + if (profile) { + panel(c, 34, 414, W - 68, 48, "rgba(8,18,35,0.9)", "rgba(120,190,230,0.32)", 10); + text(c, profile.environment, 50, 432, 10, "#8deaff", true, "left"); + text(c, "RISK / " + profile.hazard, W / 2, 432, 10, "#ffd783", true); + text(c, profile.focus, W - 50, 432, 10, "#b48cff", true, "right"); + text(c, profile.fieldNote, W / 2, 451, 10, "#8aa6c1", false); + } + text(c, "SURVEY PROGRESS " + done + " / " + objectives.length, W / 2, 482, 14, "#b9d7ed", true); objectives.forEach((objective, i) => { - const y = 482 + i * 164, completed = this.explorationDone(objective, sector.id); - panel(c, 36, y, W - 72, 144, completed ? "rgba(12,42,43,0.9)" : "rgba(8,16,32,0.94)", + const y = 508 + i * 158, completed = this.explorationDone(objective, sector.id); + panel(c, 36, y, W - 72, 140, completed ? "rgba(12,42,43,0.9)" : "rgba(8,16,32,0.94)", completed ? "rgba(141,255,206,0.65)" : "rgba(120,190,230,0.28)", 14); circle(c, 70, y + 39, 13, completed ? "#8dffce" : objective.color); text(c, completed ? "DATA SECURED" : "SURVEY " + (i + 1), 98, y + 28, 11, @@ -3024,7 +3047,7 @@ class Game { text(c, objective.title, 58, y + 59, 19, "#e6f3ff", true, "left"); text(c, objective.detail, 58, y + 85, 12, "#9fb3cc", false, "left"); this.smallButton(c, completed ? "COMPLETE" : "SCAN", - 500, y + 96, 158, 34, completed ? "#477f76" : objective.color, + 500, y + 92, 158, 34, completed ? "#477f76" : objective.color, () => this.completeExplorationObjective(objective.id)); text(c, "REWARD +" + objective.reward + " CRYSTALS", 58, y + 119, 11, "#8dffce", true, "left"); }); @@ -3466,5 +3489,5 @@ if (typeof module !== "undefined" && module.exports) module.exports = { Game, MockAdProvider, AdMobProvider, createAdProvider, Save, Sfx, Social, Leaderboard, CAMPAIGN_SECTORS, STORY_ENTRIES, EXPLORATION_OBJECTIVES, freshCampaign, sanitizeCampaign, DemoIapProvider, DisabledIapProvider, CdvIapProvider, createIapProvider, GEM_PACKS, CONTINUE_BASE_COST, SHIPS, CONTRACTS, MISSION_POOL, BIOMES, METEOR_TYPES, SHOP_ITEMS, - dateKey, weekKey, mulberry32, difficultyProfile, flareWarningText, bossWarningText, comboMilestoneText, shouldRenderScreenFlash, W, H }; + dateKey, weekKey, mulberry32, difficultyProfile, flareWarningText, bossWarningText, comboMilestoneText, shouldRenderScreenFlash, planetSurveyProfile, W, H }; else window.addEventListener("load", boot); From 8fe99de005bcdcecbae0504e65d8ce1b50f9ae27 Mon Sep 17 00:00:00 2001 From: Ayinkx Date: Sat, 24 Oct 2026 10:02:13 +0100 Subject: [PATCH 2/4] feat: add sector-specific planet survey briefings --- web/test/game.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/test/game.test.js b/web/test/game.test.js index 4aa1f23..c36dd3c 100644 --- a/web/test/game.test.js +++ b/web/test/game.test.js @@ -4,7 +4,7 @@ const test = require("node:test"); const assert = require("node:assert/strict"); const { Game, MockAdProvider, AdMobProvider, Save, Social, Leaderboard, MISSION_POOL, CONTRACTS, BIOMES, SHOP_ITEMS, GEM_PACKS, DemoIapProvider, dateKey, weekKey, CAMPAIGN_SECTORS, STORY_ENTRIES, EXPLORATION_OBJECTIVES, freshCampaign, - sanitizeCampaign, difficultyProfile, flareWarningText, comboMilestoneText, shouldRenderScreenFlash } = require("../game.js"); + sanitizeCampaign, difficultyProfile, flareWarningText, comboMilestoneText, shouldRenderScreenFlash, planetSurveyProfile } = require("../game.js"); function localStore() { const store = {}; @@ -602,6 +602,14 @@ test("campaign completion persists its story discovery and archive navigation re }); +test("each campaign planet has a unique survey briefing and unknown sectors are rejected", () => { + const profiles = CAMPAIGN_SECTORS.map(sector => planetSurveyProfile(sector.id)); + assert.equal(profiles.length, CAMPAIGN_SECTORS.length); + assert.ok(profiles.every(profile => profile && profile.environment && profile.hazard && profile.focus && profile.fieldNote)); + assert.equal(new Set(profiles.map(profile => profile.environment)).size, profiles.length); + assert.equal(planetSurveyProfile("unknown-sector"), null); +}); + test("planet exploration is available only for unlocked campaign sectors", () => { const g = newGame(); g.openCampaignMap(); From 621263df61fc72bfc67affc484c48a15414c7454 Mon Sep 17 00:00:00 2001 From: Ayinkx Date: Sat, 24 Oct 2026 10:02:24 +0100 Subject: [PATCH 3/4] feat: add sector-specific planet survey briefings --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 961a11e..dca072f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ with the tutorial and does not change daily or ranked challenge rules. shared schema makes campaign progress portable for future import/export tooling but does not automatically sync files between devices. -### Realism pass +- Planetary surveys show sector-specific environmental conditions, hazards, research focus, and field notes while reusing the existing 12 optional objectives and rewards.\n\n### Realism pass - **Orbital motion:** inner rings orbit faster (Kepler-style), reversing has angular inertia, and ring changes use a spring-damper radial move. From 5ee6b52c9bca325f4f78e818163fbb7167fec25f Mon Sep 17 00:00:00 2001 From: Ayinkx Date: Sat, 24 Oct 2026 10:02:38 +0100 Subject: [PATCH 4/4] feat: add sector-specific planet survey briefings --- web/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/sw.js b/web/sw.js index 8ac9df9..f35388c 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,7 +1,7 @@ // Service worker: caches the game so it works offline once installed. // Bump CACHE whenever the game files change. Navigation is network-first so a // deployed update is picked up as soon as the player is online. -const CACHE = "orbit-runner-v16"; +const CACHE = "orbit-runner-v20"; const FILES = ["./", "./index.html", "./config.js", "./game.js", "./manifest.json", "./icon-192.png", "./icon-512.png"]; self.addEventListener("install", e => {