Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ runs keep their existing pause layout and rules.

- The story archive presents the existing sector discoveries as one signal chain, with a recovery meter and the next unrecovered lead. It reuses the campaign discovery IDs and does not add a separate save format.
- The web galaxy map uses the existing sector positions to draw connected routes, distinguish locked and charted systems, and show the selected sector's objective without duplicating campaign progression or save data.
- Planetary surveys show sector-specific environmental conditions, hazards, research focus, and field notes while reusing the existing 12 optional objectives and rewards.

### Realism pass

Expand Down
33 changes: 28 additions & 5 deletions web/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,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": [],
} });
Expand Down Expand Up @@ -3098,18 +3113,26 @@ 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,
completed ? "#8dffce" : objective.color, true, "left");
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");
});
Expand Down Expand Up @@ -3567,5 +3590,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, shouldApplyHitStop, campaignPauseHint, campaignMapPoint, storySignalState, W, H };
dateKey, weekKey, mulberry32, difficultyProfile, flareWarningText, bossWarningText, comboMilestoneText, shouldRenderScreenFlash, shouldApplyHitStop, campaignPauseHint, campaignMapPoint, storySignalState, planetSurveyProfile, W, H };
else window.addEventListener("load", boot);
2 changes: 1 addition & 1 deletion web/sw.js
Original file line number Diff line number Diff line change
@@ -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-v20";
const CACHE = "orbit-runner-v21";
const FILES = ["./", "./index.html", "./config.js", "./game.js", "./manifest.json", "./icon-192.png", "./icon-512.png"];

self.addEventListener("install", e => {
Expand Down
10 changes: 9 additions & 1 deletion web/test/game.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, shouldApplyHitStop, campaignPauseHint, campaignMapPoint, storySignalState } = require("../game.js");
sanitizeCampaign, difficultyProfile, flareWarningText, comboMilestoneText, shouldRenderScreenFlash, shouldApplyHitStop, campaignPauseHint, campaignMapPoint, storySignalState, planetSurveyProfile } = require("../game.js");

function localStore() {
const store = {};
Expand Down Expand Up @@ -627,6 +627,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();
Expand Down
Loading