Cap tether progression at level 3 and remove tether_l4 research node
- Reduce maxLevel from 12 to 3 in data/tether.json - Remove the tether_l4 entry from exploration.json; tether_l3 now has empty unlocks - Update research-builds and tether tests to reflect the shorter chain (3 layout rows, level checks for 1–3 only) - Revise PROJECT_NOTES.md to describe the 3-level progression
This commit is contained in:
parent
cb82da4935
commit
d9fc9df829
|
|
@ -26,17 +26,8 @@
|
||||||
"icon": "tether",
|
"icon": "tether",
|
||||||
"duration": 150,
|
"duration": 150,
|
||||||
"requires": ["tether_l2"],
|
"requires": ["tether_l2"],
|
||||||
"unlocks": { "builds": [], "research": ["tether_l4"] },
|
|
||||||
"effects": { "tether": { "level": 3 } }
|
|
||||||
},
|
|
||||||
"tether_l4": {
|
|
||||||
"label": "Tether Level 4",
|
|
||||||
"description": "Fourth stage: 40,960 m. At this level every object of a ten-body system — all the worlds and stations — lies inside one ring around your anchor, and the barrier stops meaning anything out here.",
|
|
||||||
"icon": "tether",
|
|
||||||
"duration": 300,
|
|
||||||
"requires": ["tether_l3"],
|
|
||||||
"unlocks": { "builds": [], "research": [] },
|
"unlocks": { "builds": [], "research": [] },
|
||||||
"effects": { "tether": { "level": 4 } }
|
"effects": { "tether": { "level": 3 } }
|
||||||
},
|
},
|
||||||
"tether_anchors": {
|
"tether_anchors": {
|
||||||
"label": "Tether Anchoring",
|
"label": "Tether Anchoring",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"_comment": "TETHER — the player's range. A tether anchors on a world or station and defines a circular zone: radius = distance from the anchor's center. Level 1 = 5120 px (design rule); radius(level) = level1Radius × radiusGrowth^(level−1). The ship may fly anywhere in the UNION of its tethers' zones — where zones overlap there is no line and no wall; outside all of them is a hard barrier, drawn as a thick cyberpunk glitch dotted line along the union's boundary. homeId/homeLevel/homeLabel = the tether the player starts with (anchored on the home world, which sits at the system origin). line = the barrier's look & motion (dash/gap px along the rim, thicknesses, march speed = how fast dashes flow, colors/alphas = the neon passes, glitch = ambient bursts: dash flicker, radial displacement, sparks). contact = feedback when the ship bumps the barrier (pulse = the local shudder of the line; toastCooldown = console warning throttle, ms).",
|
"_comment": "TETHER — the player's range. A tether anchors on a world or station and defines a circular zone: radius = distance from the anchor's center. Level 1 = 5120 px (design rule); radius(level) = level1Radius × radiusGrowth^(level−1). The ship may fly anywhere in the UNION of its tethers' zones — where zones overlap there is no line and no wall; outside all of them is a hard barrier, drawn as a thick cyberpunk glitch dotted line along the union's boundary. homeId/homeLevel/homeLabel = the tether the player starts with (anchored on the home world, which sits at the system origin). line = the barrier's look & motion (dash/gap px along the rim, thicknesses, march speed = how fast dashes flow, colors/alphas = the neon passes, glitch = ambient bursts: dash flicker, radial displacement, sparks). contact = feedback when the ship bumps the barrier (pulse = the local shudder of the line; toastCooldown = console warning throttle, ms).",
|
||||||
"level1Radius": 5120,
|
"level1Radius": 5120,
|
||||||
"radiusGrowth": 2.0,
|
"radiusGrowth": 2.0,
|
||||||
"maxLevel": 12,
|
"maxLevel": 3,
|
||||||
"homeId": "home",
|
"homeId": "home",
|
||||||
"homeLevel": 1,
|
"homeLevel": 1,
|
||||||
"homeLabel": null,
|
"homeLabel": null,
|
||||||
|
|
|
||||||
|
|
@ -116,15 +116,16 @@ for (const id of Object.keys(nodes)) {
|
||||||
check('exploration: the tree is a DAG (no cycles)', !cyclic);
|
check('exploration: the tree is a DAG (no cycles)', !cyclic);
|
||||||
check('exploration: has at least one root (a node with no requires)', Object.values(nodes).some((n) => !(n.requires ?? []).length));
|
check('exploration: has at least one root (a node with no requires)', Object.values(nodes).some((n) => !(n.requires ?? []).length));
|
||||||
|
|
||||||
// tether level chain: L2 is a BUILD (research unlocks it, the build
|
// tether level chain (3 levels total): L2 is a BUILD (research unlocks
|
||||||
// applies it — data/builds.json → tether-l2), so it carries no immediate
|
// it, the build applies it — data/builds.json → tether-l2), so it carries
|
||||||
// effect; L3/L4 still apply their tether raise on research completion.
|
// no immediate effect; L3 still applies its tether raise on research
|
||||||
|
// completion.
|
||||||
const l2NoEffect = !nodes.tether_l2 || !nodes.tether_l2.effects?.tether;
|
const l2NoEffect = !nodes.tether_l2 || !nodes.tether_l2.effects?.tether;
|
||||||
const levelsOk = [3, 4].every((lvl) => {
|
const levelsOk = [3].every((lvl) => {
|
||||||
const n = nodes[`tether_l${lvl}`];
|
const n = nodes[`tether_l${lvl}`];
|
||||||
return n && n.effects?.tether?.level === lvl;
|
return n && n.effects?.tether?.level === lvl;
|
||||||
});
|
});
|
||||||
check('exploration: tether_l3/l4 each raise the tether to their level', levelsOk);
|
check('exploration: tether_l3 raises the tether to level 3', levelsOk);
|
||||||
check('exploration: tether_l2 carries no immediate tether effect (the build applies it)', l2NoEffect);
|
check('exploration: tether_l2 carries no immediate tether effect (the build applies it)', l2NoEffect);
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
@ -145,15 +146,15 @@ check('model: issues(tree) is clean', Array.isArray(issues(tree)) && issues(tree
|
||||||
// two declarations together.
|
// two declarations together.
|
||||||
const { unlocksOf, buildDefs, unlockIssues, buildIssues } = await import('../js/research/ResearchModel.js');
|
const { unlocksOf, buildDefs, unlockIssues, buildIssues } = await import('../js/research/ResearchModel.js');
|
||||||
check('model: unlocksOf normalizes {builds, research}', JSON.stringify(unlocksOf(tree, 'tether_l2')) === JSON.stringify({ builds: ['tether-l2'], research: ['tether_l3', 'tether_anchors'] }));
|
check('model: unlocksOf normalizes {builds, research}', JSON.stringify(unlocksOf(tree, 'tether_l2')) === JSON.stringify({ builds: ['tether-l2'], research: ['tether_l3', 'tether_anchors'] }));
|
||||||
check('model: unlocksOf is empty for a node without unlocks', JSON.stringify(unlocksOf(tree, 'tether_l4')) === JSON.stringify({ builds: [], research: [] }));
|
check('model: unlocksOf is empty for a node without unlocks', JSON.stringify(unlocksOf(tree, 'signal_amp')) === JSON.stringify({ builds: [], research: [] }));
|
||||||
check('model: unlockIssues(tree) is clean (mirrors + build wiring agree)', unlockIssues(tree).length === 0);
|
check('model: unlockIssues(tree) is clean (mirrors + build wiring agree)', unlockIssues(tree).length === 0);
|
||||||
check('model: buildIssues() is clean (every build require resolves)', buildIssues().length === 0);
|
check('model: buildIssues() is clean (every build require resolves)', buildIssues().length === 0);
|
||||||
|
|
||||||
const layout = layoutTree(tree);
|
const layout = layoutTree(tree);
|
||||||
check('model: layout rows = 4 (l1 → l2/anchor/signal → l3 → l4)', layout.rows === 4);
|
check('model: layout rows = 3 (l1 → l2/anchor/signal → l3)', layout.rows === 3);
|
||||||
check('model: layout levels — l1 root, l2/signal tier 1, l3/anchors tier 2, l4 tier 3',
|
check('model: layout levels — l1 root, l2/signal tier 1, l3/anchors tier 2',
|
||||||
layout.level.tether_l1 === 0 && layout.level.tether_l2 === 1 && layout.level.signal_amp === 1 &&
|
layout.level.tether_l1 === 0 && layout.level.tether_l2 === 1 && layout.level.signal_amp === 1 &&
|
||||||
layout.level.tether_l3 === 2 && layout.level.tether_anchors === 2 && layout.level.tether_l4 === 3);
|
layout.level.tether_l3 === 2 && layout.level.tether_anchors === 2);
|
||||||
check('model: layout is deterministic (same tree → same columns)', JSON.stringify(layout.col) === JSON.stringify(layoutTree(tree).col));
|
check('model: layout is deterministic (same tree → same columns)', JSON.stringify(layout.col) === JSON.stringify(layoutTree(tree).col));
|
||||||
|
|
||||||
// a fresh run: starting techs pre-unlocked, the rest follow the rules
|
// a fresh run: starting techs pre-unlocked, the rest follow the rules
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ const dist = (x, y, t) => Math.hypot(x - t.x, y - t.y);
|
||||||
const growth = config.get('tether.radiusGrowth', 2.0);
|
const growth = config.get('tether.radiusGrowth', 2.0);
|
||||||
const r2 = Tether.radiusForLevel(2);
|
const r2 = Tether.radiusForLevel(2);
|
||||||
check(`level 2 = 5120 × growth (${growth}) (got ${r2})`, Math.abs(r2 - 5120 * growth) < 1e-9);
|
check(`level 2 = 5120 × growth (${growth}) (got ${r2})`, Math.abs(r2 - 5120 * growth) < 1e-9);
|
||||||
const maxLevel = config.get('tether.maxLevel', 12);
|
const maxLevel = config.get('tether.maxLevel', 3);
|
||||||
let mono = true;
|
let mono = true;
|
||||||
let prev = 0;
|
let prev = 0;
|
||||||
for (let l = 1; l <= maxLevel; l++) {
|
for (let l = 1; l <= maxLevel; l++) {
|
||||||
|
|
|
||||||
|
|
@ -789,15 +789,15 @@ The player holds a REPUTATION (standing) on each planet and space station:
|
||||||
Pure rules/state in `js/research/` (Node-tested), the window is a
|
Pure rules/state in `js/research/` (Node-tested), the window is a
|
||||||
passive view, `effects` carry the payload (e.g. `tether.level` grows
|
passive view, `effects` carry the payload (e.g. `tether.level` grows
|
||||||
the home tether). First category **Exploration** is live (Tether
|
the home tether). First category **Exploration** is live (Tether
|
||||||
Level 1–4, Tether Anchoring, Signal Amplification); add a category =
|
Level 1–3, Tether Anchoring, Signal Amplification); add a category =
|
||||||
one JSON file + one line in the registry + one line in the manifest
|
one JSON file + one line in the registry + one line in the manifest
|
||||||
(`js/ui/ResearchWindow.js`, `js/research/*`, `data/research.json`,
|
(`js/ui/ResearchWindow.js`, `js/research/*`, `data/research.json`,
|
||||||
`data/research/exploration.json`, dev/research-builds.test.mjs)
|
`data/research/exploration.json`, dev/research-builds.test.mjs)
|
||||||
- [x] Tether progression: level 2 is a **build** — research
|
- [x] Tether progression: level 2 is a **build** — research
|
||||||
`tether_l2` (blueprint, no effect) → BUILD slot on a world holding a
|
`tether_l2` (blueprint, no effect) → BUILD slot on a world holding a
|
||||||
level-1 tether → 200 minerals / 20 s → the world's tether strengthens
|
level-1 tether → 200 minerals / 20 s → the world's tether strengthens
|
||||||
to level 2 (10240 px), effect and all, outliving the stay. Levels 3–4
|
to level 2 (10240 px), effect and all, outliving the stay. Level 3
|
||||||
are still research-direct (`effects.tether.level` on the home
|
is still research-direct (`effects.tether.level` on the home
|
||||||
tether); anchoring extra rings on planets/stations remains
|
tether); anchoring extra rings on planets/stations remains
|
||||||
(the `add`/`setLevel`/`onChange` seams are in place)
|
(the `add`/`setLevel`/`onChange` seams are in place)
|
||||||
(`data/builds.json → tether-l2`, `js/build/*`,
|
(`data/builds.json → tether-l2`, `js/build/*`,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue