From 6ecf199da5672e4b9e8b45ab9bf6b25237c225cf Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Wed, 30 Sep 2026 15:36:27 -0600 Subject: [PATCH] Tune jumpgate activation research duration to 20 s - Reduce data/gates.json activation.researchDuration from 45 s to 20 s so the "Unlock Jumpgates" tech completes faster once all NAV points are discovered. - Update SystemCategory.js doc comment and fallback default to match the new duration. - Adjust system-category.test.mjs assertions (duration, save/restore remaining-time math) to the 20 s value. --- data/gates.json | 2 +- dev/system-category.test.mjs | 18 +++++++++--------- js/research/SystemCategory.js | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/gates.json b/data/gates.json index 27b3fa5..e12f787 100644 --- a/data/gates.json +++ b/data/gates.json @@ -36,7 +36,7 @@ }, "activation": { "_comment": "ACTIVATION (the SYSTEM research category — js/research/SystemCategory.js builds the per-system tree from this): '{system} Map' is granted the moment the player is in the system (duration 0 — 'starting'), and 'Unlock Jumpgates' (researchDuration, in research.timeUnit seconds) becomes researchable once EVERY NAV point of the system is discovered (the central body, all planets, all space stations, all jump gates — the scene's discoverable set minus the asteroid clusters; data/game.json → discovery.distance). Completing it activates the system's gates AND the return gates in the systems they connect to — and per the ACTIVITY rule above, each activated gate anchors a level-`tetherLevel` tether at its own position (a TETHER ANCHOR in its own right; that tether saves/restores with the run's tether list). The map label/description copy is templated: `{system}` is replaced with the system name. The gates tech's label is FIXED (no `{system}`) — the console's node button is too narrow for the system name; the map tech carries it.", - "researchDuration": 45, + "researchDuration": 20, "tetherLevel": 1, "mapTech": { "label": "{system} Map", diff --git a/dev/system-category.test.mjs b/dev/system-category.test.mjs index ce098b7..7b9a9f1 100644 --- a/dev/system-category.test.mjs +++ b/dev/system-category.test.mjs @@ -7,7 +7,7 @@ * SYSTEM tab — categories flagged `dynamic` in research.json, built at * runtime by js/research/SystemCategory.js; copy + duration in * data/gates.json → activation): - * - config: the activation section (45 s research, the two tech's + * - config: the activation section (20 s research, the two tech's * label/description templates with their `{system}` placeholder); * - buildSystemTree: the tree contract (ids embed the system id, the * map tech granted on entry, the jumpgate tech gated by the chart), @@ -44,7 +44,7 @@ const research = read('research.json'); check('gates: activation section present', !!gates.activation && typeof gates.activation === 'object'); check('gates: researchDuration is a positive number (seconds — research.timeUnit)', typeof gates.activation?.researchDuration === 'number' && gates.activation.researchDuration > 0); -check('gates: the 45 s brief duration', gates.activation?.researchDuration === 45); +check('gates: the 20 s brief duration', gates.activation?.researchDuration === 20); check('gates: tetherLevel ≥ 1 (an activated gate anchors a tether — ACTIVITY)', Number.isInteger(gates.activation?.tetherLevel) && gates.activation.tetherLevel >= 1); check('gates: mapTech label template names the system', @@ -106,8 +106,8 @@ check('tree: map tech — icon from the procedural glyph roster', map.icon === ' check('tree: gates tech — the brief label + copy', gNode.label === 'Unlock Jumpgates' && gNode.description === "With system NAV data complete, we have enough information to plot courses through this system's jumpgates."); -check('tree: gates tech — 45 s, requires the map, the activation effect', - gNode.duration === 45 && JSON.stringify(gNode.requires) === JSON.stringify([mapId]) && gNode.effects?.activateGates === true); +check('tree: gates tech — 20 s, requires the map, the activation effect', + gNode.duration === 20 && JSON.stringify(gNode.requires) === JSON.stringify([mapId]) && gNode.effects?.activateGates === true); check('tree: map tech declares the gates tech in its unlocks', JSON.stringify(map.unlocks?.research ?? []) === JSON.stringify([gatesId])); check('tree: issues(tree) is clean', Array.isArray(issues(tree)) && issues(tree).length === 0); @@ -132,16 +132,16 @@ chartComplete = true; check('hooks: chart complete → the gates tech is researchable', isAvailable(tree, state, gatesId)); // -- the full run: one project at a time, save/restore ------------------- -state.start(SYSTEM_CATEGORY, gatesId, 45_000, 0); +state.start(SYSTEM_CATEGORY, gatesId, 20_000, 0); check('run: the in-flight project is the gates tech', state.getActive()?.id === gatesId); check('run: a second project is refused (one at a time)', state.start(SYSTEM_CATEGORY, gatesId, 1000, 0) === false); -const saved = state.toJSON(20_000); // saved 20 s into the 45 s project +const saved = state.toJSON(8_000); // saved 8 s into the 20 s project const restored = new ResearchState(); for (const k of saved.unlocked) restored.unlock(...k.split('::')); restored.restoreActive(saved.active, 900_000); // reloaded later -check('run: save carries the remaining time (45 s − 20 s = 25 s)', saved.active?.remainingMs === 25_000); -check('run: restore keeps the project in flight (25 s left)', - restored.getActive()?.id === gatesId && restored.progress(900_000).fraction < 1 && restored.progress(925_000).fraction >= 1); +check('run: save carries the remaining time (20 s − 8 s = 12 s)', saved.active?.remainingMs === 12_000); +check('run: restore keeps the project in flight (12 s left)', + restored.getActive()?.id === gatesId && restored.progress(900_000).fraction < 1 && restored.progress(912_000).fraction >= 1); // -- the chart gate: NAV points + completion ------------------------------ // A NON-home system (no homeName — its center is empty, the star is diff --git a/js/research/SystemCategory.js b/js/research/SystemCategory.js index be95ff6..17b6883 100644 --- a/js/research/SystemCategory.js +++ b/js/research/SystemCategory.js @@ -14,7 +14,7 @@ * └ "Unlock Jumpgates" requires the map + EVERY NAV * point of the system discovered * (the availability hook below); - * 45 s (data/gates.json → + * 20 s (data/gates.json → * activation.researchDuration); * completing it activates the * system's gates AND the return @@ -122,7 +122,7 @@ export function buildSystemTree(o = {}) { const systemName = String(o.systemName ?? systemId); const mapId = systemNodeId(systemId, MAP_NODE); const gatesId = systemNodeId(systemId, GATES_NODE); - const duration = Math.max(0, Number(config.get('gates.activation.researchDuration', 45)) || 0); + const duration = Math.max(0, Number(config.get('gates.activation.researchDuration', 20)) || 0); const isComplete = typeof o.isComplete === 'function' ? o.isComplete : () => false; // No system name — the detail panel has room, the node button doesn't: