diff --git a/data/galaxy.json b/data/galaxy.json index d823f24..2d03d69 100644 --- a/data/galaxy.json +++ b/data/galaxy.json @@ -1,5 +1,5 @@ { - "_comment": "Galaxy shape + scale (js/galaxy/Galaxy.js). systemCount = total star systems (the starting system is one of them). layout.field = the galaxy is a seeded WIDE (2:1) field of stars (center at the world origin, ±width/2 in x, ±height/2 in y — the 2:1 shape matches the map plate, js/ui/MapWindow.js at the 1280×720 design size, so the fully-zoomed-out galaxy fills the plate): Bridson Poisson-disk sampling keeps every pair of systems at least minSpacing·√(width·height/systemCount) apart — an even, organic field (no clumps, no voids, not a grid). startingSystem: policy 'corner' puts the player's home in the star NEAREST the configured corner (corner: NE/NW/SE/SW, screen orientation — SE = lower right); 'center' (nearest the origin) and 'random' still work. distribution.zones slices the HOME→FAR diagonal into named zones (d: 0 at the home corner, 1 at the opposite corner); distribution.zoneMix multiplies each type's distribution.weight PER ZONE (the old per-type radiusBand is gone — type flavor is regional now). settlements.gradient = free-space settlement density by diagonal position (d 0 = home corner, 1 = far corner): chance = 1 − d×falloff, floored at floor (js/galaxy/SystemGenerator.js → settlementDensity).", + "_comment": "Galaxy shape + scale (js/galaxy/Galaxy.js). systemCount = total star systems (the starting system is one of them). layout.field = the galaxy is a seeded WIDE (2:1) field of stars (center at the world origin, ±width/2 in x, ±height/2 in y — the 2:1 shape matches the map plate, js/ui/MapWindow.js at the 1280×720 design size, so the fully-zoomed-out galaxy fills the plate): Bridson Poisson-disk sampling keeps every pair of systems at least minSpacing·√(width·height/systemCount) apart — an even, organic field (no clumps, no voids, not a grid). startingSystem: policy 'corner' puts the player's home in the star NEAREST the configured corner (corner: NE/NW/SE/SW, screen orientation — SE = lower right); 'center' (nearest the origin) and 'random' still work. startingSystem.type FORCES the starting system's archetype (default 'main' — a Main Sequence star, data/systems.json → types.main: the player's home is an ordinary star; the record's zone-mixed roll is overridden at roster build, js/galaxy/Galaxy.js). distribution.zones slices the HOME→FAR diagonal into named zones (d: 0 at the home corner, 1 at the opposite corner); distribution.zoneMix multiplies each type's distribution.weight PER ZONE (the old per-type radiusBand is gone — type flavor is regional now). settlements.gradient = free-space settlement density by diagonal position (d 0 = home corner, 1 = far corner): chance = 1 − d×falloff, floored at floor (js/galaxy/SystemGenerator.js → settlementDensity).", "systemCount": 90, "layout": { "field": { @@ -21,7 +21,7 @@ "far": { "main": 0.85, "redDwarf": 0.55, "binary": 1.1, "habitable": 0.25, "nebula": 1.8, "void": 2.6 } } }, - "startingSystem": { "policy": "corner", "corner": "SE" }, + "startingSystem": { "policy": "corner", "corner": "SE", "type": "main" }, "settlements": { "gradient": { "falloff": 0.7, "floor": 0.22 } }, diff --git a/dev/galaxy.test.mjs b/dev/galaxy.test.mjs index 1e47b9f..86eccad 100644 --- a/dev/galaxy.test.mjs +++ b/dev/galaxy.test.mjs @@ -196,6 +196,14 @@ let big; 'corner policy: the home system sits in the NEAR zone (d ≈ 0 at the home corner)', big.currentSystem().zone === zoneCfg[0]?.name, ); + // The starting system is ALWAYS a Main Sequence system (data/galaxy.json + // → startingSystem.type, default 'main' — the player's home is an + // ordinary star): whatever the zone mix rolled, the record is overridden + // to 'main' at roster build. + check( + 'starting system is always a Main Sequence system (type "main")', + big.currentSystem().type === 'main' && config.get('systems.types.main.label', '') === 'Main Sequence', + ); // Lazy contents: the global OBJECT-COMPOSITION rule across the WHOLE // galaxy (data/systems.json → objectCount: 0/2/3/4/5 objects, ≈ 10% @@ -295,6 +303,13 @@ let big; .slice() .sort((a, b) => (a.x + halfNWx) ** 2 + (a.y + halfNWy) ** 2 - ((b.x + halfNWx) ** 2 + (b.y + halfNWy) ** 2))[0].id; check('corner policy honors the configured corner (NW)', nw.currentSystemId === trueNW); + + // The Main Sequence home rule holds for EVERY starting policy (the + // default 'main' applies — none of these override startingSystem.type). + check( + 'every starting policy still yields a Main Sequence home', + [randomPolicy, center, nw].every((gg) => gg.currentSystem().type === 'main'), + ); } // ---------------------------------------------------------------------- diff --git a/docs/PROJECT_NOTES.md b/docs/PROJECT_NOTES.md index 5413fa5..a107b3d 100644 --- a/docs/PROJECT_NOTES.md +++ b/docs/PROJECT_NOTES.md @@ -152,7 +152,10 @@ menu (displayed, editable, rerollable; same seed ⇒ same galaxy). The player's **current system** starts at `galaxy.currentSystem()` (`startingSystem.policy`: `corner` (default — the home corner, SE), -`center`, or `random`). Jumping between systems +`center`, or `random`). Its **type is fixed**: the starting system is +always a Main Sequence system (`startingSystem.type`, default `'main'` +— data/systems.json → types.main), so the player's home is an ordinary +star. Jumping between systems now has its NETWORK (see "Jump gates" below — `data/gates.json` + `js/galaxy/JumpNetwork.js`, built on `galaxy.neighborsOf(id)` and the spatial hash); the in-flight jump drive is the next mechanic on top. diff --git a/js/galaxy/Galaxy.js b/js/galaxy/Galaxy.js index 5d0cb69..37452d6 100644 --- a/js/galaxy/Galaxy.js +++ b/js/galaxy/Galaxy.js @@ -39,7 +39,10 @@ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v)); * - the player's HOME system sits in the star nearest the configured * corner (startingSystem: policy "corner", corner NE/NW/SE/SW, * screen orientation — SE = lower right); "center" / "random" still - * work; the HOME→FAR diagonal is the galaxy's progression axis: + * work; its TYPE is always "main" (Main Sequence — + * startingSystem.type, the player's home is an ordinary star; the + * zone-mixed roll is overridden at roster build); the HOME→FAR + * diagonal is the galaxy's progression axis: * each record carries `d` (0 at the home corner, 1 at the opposite * corner) and its `zone` (near/middle/far, distribution.zones); * - types mix PER ZONE (distribution.zoneMix × the type's global @@ -200,6 +203,23 @@ export class Galaxy { // the player from here on (jumps, saves); the home rules must not. this.homeSystemId = this.currentSystemId; + // The starting system is ALWAYS a MAIN SEQUENCE system (data/galaxy.json + // → startingSystem.type, default 'main' — data/systems.json → types.main, + // "Main Sequence"): the player's home is an ordinary star. Its type was + // rolled from the zone mix above like every other record; override it + // here — before the frame passes read it — so the dossier ("Main + // Sequence system · star G"), the map plate, and the generated content + // all agree. + const forcedType = String(start.type ?? 'main'); + const homeRec = this.byId.get(this.currentSystemId); + if (homeRec && this.typeDefs[forcedType]) { + if (homeRec.type !== forcedType) homeRec.type = forcedType; + } else { + console.warn( + `[orbit] startingSystem.type "${forcedType}" is not a defined system type — the starting system keeps its rolled type`, + ); + } + // Spatial hash for fast neighbor queries (jump ranges, proximity rules, // the eventual star map). const area = W * H; diff --git a/js/galaxy/SystemGenerator.js b/js/galaxy/SystemGenerator.js index 34ac54a..c9fecb1 100644 --- a/js/galaxy/SystemGenerator.js +++ b/js/galaxy/SystemGenerator.js @@ -55,7 +55,11 @@ const DEG = Math.PI / 180; * station: five objects (the home world + four) cannot sit 6400..10240 px * apart — the tightest 5-point spacing needs a max/min ratio ≥ φ ≈ 1.618, * which the home band 10240/6400 = 1.6 cannot give — so the home system - * stays a ≤ 4-object configuration (see layoutSystem). + * stays a ≤ 4-object configuration (see layoutSystem). Its TYPE is also + * fixed: always 'main' (Main Sequence — the player's home is an ordinary + * star; Galaxy.js forces it at roster build, data/galaxy.json → + * startingSystem.type), so its star class rolls from the main-sequence + * table (G/K/M). * * LAYOUT (data/planets.json → solarSystem) — the SOLAR SYSTEM BAND: every * PAIR of layout objects (planets and free-space stations — the central