Force starting system to be a Main Sequence star

- Add `startingSystem.type` config (default "main") that overrides the zone-mixed roll at roster build in Galaxy.js
- Update SystemGenerator doc comment to note the home star class now rolls from the main-sequence table (G/K/M)
- Add tests asserting the starting system is always type "main" across all policies
- Update data/galaxy.json comment and PROJECT_NOTES.md to document the new rule
This commit is contained in:
Brian Fertig 2026-09-08 21:58:34 -06:00
parent 6dc0e99dee
commit f470250721
5 changed files with 47 additions and 5 deletions

View File

@ -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 }
},

View File

@ -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'),
);
}
// ----------------------------------------------------------------------

View File

@ -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.

View File

@ -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 HOMEFAR 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 HOMEFAR
* 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;

View File

@ -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