world: #51 — ringEdges small-n fix + generateRing seeded 1–4 legs with attach
- ringEdges: n=0 → [], n=1 → [[0,1]], n=2 → [[0,1],[0,2],[1,2]]; n≥3 wheel is byte-identical to the legacy output. - generateRing: new params attachCandidates, existingEdgeKeys, anchorId; legCount is seeded-random in [EXPAND_MIN, EXPAND_MAX] (1–4); each leg may attach a known system (closing a loop) with prob ATTACH_PROB instead of minting; frontier guarantee converts the last-drawn attach to a mint when all legs attached. - RingRegion gains attaches: number[] (candidate indices attached). - Remove the EXPAND_RING constant (replaced by EXPAND_MIN/EXPAND_MAX).
This commit is contained in:
+54
-5
@@ -11,7 +11,13 @@ export const START_X = 50
|
||||
export const START_Y = 50
|
||||
|
||||
const START_RING = 6 // neighbours around the starting system on a new game
|
||||
const EXPAND_RING = 4 // new neighbours generated when jumping into an undiscovered system
|
||||
// #51 — the spread limit: jumping into an undiscovered system mints a seeded-random number of
|
||||
// new systems in [EXPAND_MIN, EXPAND_MAX]. Each "leg" can instead attach an already-known
|
||||
// system (closing a loop) with probability ATTACH_PROB, so the chart grows slower and more
|
||||
// circular. EXPAND_RING (the old fixed count) is replaced by this range.
|
||||
export const EXPAND_MIN = 1
|
||||
export const EXPAND_MAX = 4
|
||||
export const ATTACH_PROB = 0.35
|
||||
const MIN_SPACING = 16 // minimum distance between any two systems in the 0–100 space
|
||||
|
||||
// mulberry32 — small fast seeded PRNG; plenty for name/position picking.
|
||||
@@ -95,7 +101,13 @@ function placeRing(
|
||||
// Edges for a ring of n members around an anchor at index 0: anchor↔each member plus the
|
||||
// cycle connecting consecutive members (so every system has ≥2 routes and you can always
|
||||
// get back). Pairs are indices into [anchor, ...members].
|
||||
function ringEdges(n: number): [number, number][] {
|
||||
// #51 — small-n special cases: n=0 is empty, n=1 is the single spoke, n=2 is the complete
|
||||
// triangle (the old wheel formula produced a self-loop at n=1 and a duplicate edge at n=2).
|
||||
// n≥3 is the unchanged wheel.
|
||||
export function ringEdges(n: number): [number, number][] {
|
||||
if (n === 0) return []
|
||||
if (n === 1) return [[0, 1]]
|
||||
if (n === 2) return [[0, 1], [0, 2], [1, 2]]
|
||||
const edges: [number, number][] = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
edges.push([0, i + 1])
|
||||
@@ -121,19 +133,56 @@ export function generateStartRegion(rng: () => number): StartRegion {
|
||||
export interface RingRegion {
|
||||
systems: WorldSystem[]
|
||||
edges: [number, number][] // indices into [anchor, ...systems] — index 0 is the anchor itself
|
||||
attaches: number[] // indices into the attachCandidates array that were attached (loop-closed) rather than minted
|
||||
}
|
||||
|
||||
// Jumping into an undiscovered system grows the chart: a fresh ring of (undiscovered)
|
||||
// neighbours around it. `takenNames`/`takenPositions` are every system already in the save.
|
||||
// neighbours around it. #51 — the ring width is a seeded-random 1–4 legs; each leg may
|
||||
// instead attach an already-known system (closing a loop) with probability ATTACH_PROB.
|
||||
// `takenNames`/`takenPositions` are every system already in the save. `attachCandidates`
|
||||
// are the systems eligible for loop-closing (all save systems except the anchor).
|
||||
// `existingEdgeKeys` is the set of `${min(a,b)}-${max(a,b)}` keys for routes already
|
||||
// touching the anchor, so an attach never duplicates one. `anchorId` is the DB id of the
|
||||
// anchor system (used to build edge keys).
|
||||
export function generateRing(
|
||||
rng: () => number,
|
||||
anchorX: number,
|
||||
anchorY: number,
|
||||
takenNames: Set<string>,
|
||||
takenPositions: WorldSystem[],
|
||||
attachCandidates: (WorldSystem & { id: number })[] = [],
|
||||
existingEdgeKeys: Set<string> = new Set(),
|
||||
anchorId: number = 0,
|
||||
): RingRegion {
|
||||
const systems = placeRing(rng, anchorX, anchorY, EXPAND_RING, takenNames, takenPositions)
|
||||
return { systems, edges: ringEdges(systems.length) }
|
||||
const legCount = EXPAND_MIN + Math.floor(rng() * (EXPAND_MAX - EXPAND_MIN + 1))
|
||||
|
||||
let mints = 0
|
||||
const attachSet = new Set<number>()
|
||||
let lastAttachCi = -1
|
||||
|
||||
for (let leg = 0; leg < legCount; leg++) {
|
||||
let isAttach = false
|
||||
if (attachCandidates.length > 0 && rng() < ATTACH_PROB) {
|
||||
const ci = Math.floor(rng() * attachCandidates.length)
|
||||
const a = Math.min(anchorId, attachCandidates[ci]!.id)
|
||||
const b = Math.max(anchorId, attachCandidates[ci]!.id)
|
||||
if (!existingEdgeKeys.has(`${a}-${b}`)) {
|
||||
attachSet.add(ci)
|
||||
lastAttachCi = ci
|
||||
isAttach = true
|
||||
}
|
||||
}
|
||||
if (!isAttach) mints++
|
||||
}
|
||||
|
||||
// Frontier guarantee: at least one new system must be minted so the chart always grows.
|
||||
if (mints === 0 && lastAttachCi >= 0) {
|
||||
attachSet.delete(lastAttachCi)
|
||||
mints = 1
|
||||
}
|
||||
|
||||
const systems = placeRing(rng, anchorX, anchorY, mints, takenNames, takenPositions)
|
||||
return { systems, edges: ringEdges(mints), attaches: [...attachSet] }
|
||||
}
|
||||
|
||||
// BFS over jump routes: every system within maxHops of fromId, mapped to its shortest hop count.
|
||||
|
||||
Reference in New Issue
Block a user