{ "_comment": "RESEARCH — time-based tech, one project at a time (maxConcurrent). The command deck's RESEARCH button opens the full-screen research console (js/ui/ResearchWindow.js). Categories are the tab row; each category's TECH TREE lives in its own file under data/research/ — the file basename is the config section AND the category id (data/research/exploration.json → section 'exploration'). A tree is a branching DAG drawn top→down: `nodes` is a flat map (data order matters — roots/children lay out left-to-right in this order), each node's `requires` names the parents that must be RESEARCHED before it is (the DAG edges — authoritative). Research rules: start an available node → its `duration` (timeUnit) runs; when it completes the node is unlocked, its `effects` apply (the scene applies them — the seam), and its children become available. `unlocks` is what the tech OPENS beyond follow-on tech: `builds` = ids in data/builds.json the tech makes available (the build's own `requires` — \"category/node id\" — is the authoritative gate; the declaration here is what the console's UNLOCKS readout and the test validate), `research` = readable mirror of the children's `requires` edges. `starting` names nodes a fresh run already owns. A node may additionally carry `quest` (see _questGate) — an optional STORY-QUEST reward-claim gate layered on `requires`. Keys starting with `_` are documentation, not data.", "enabled": true, "timeUnit": "seconds", "maxConcurrent": 1, "defaultCategory": "system", "categories": [ { "id": "system", "label": "System", "accent": "#5fd4ff", "dynamic": true }, { "id": "mining", "label": "Mining", "accent": "#ffc94d" }, { "id": "cargo", "label": "Cargo", "accent": "#7ce8a4" }, { "id": "exploration", "label": "Exploration", "accent": "#00e5ff" } ], "_dynamic": "A category flagged `dynamic: true` has NO data/research/.json — its tree is built at runtime per situation (js/research/SystemCategory.js: one tech pair per solar system, named after the system the player is in — its Map tech is granted on entry, its jumpgate tech researchable once every NAV point — planets, space stations, jump gates — is discovered, and activation flips the gates on, here and in the linked systems' return gates). Static categories keep the one-file-per-category rule; the tests exempt dynamic ones from the tree-file check.", "video": { "_comment": "The console's left-panel feed: loops muted while the window is open. aspect is [w, h] — the file is 544×800 (a 2:3 portrait).", "file": "assets/videos/research-computer.mp4", "aspect": [2, 3] }, "fx": { "_comment": "Window dressing. glitch = occasional RGB-split slice bursts; sweep = the scan band that crawls down the video feed; tabShimmer = the READY-RESEARCH cue on the category tabs (the deck's RESEARCH-slot shimmer's twin — data/actionbar.json → animation.shimmer, js/ui/ShimmerCue.js).", "glitch": { "enabled": true, "intervalMs": [6000, 13000], "durationMs": [220, 420] }, "sweep": { "enabled": true, "everyMs": [4200, 8600], "durationMs": 1500 }, "tabShimmer": { "_comment": "Per frame while the console is open + no project in flight (maxConcurrent 1): every category tab whose tree still holds an available tech wears the edge comet (ResearchWindow._paintCues). The face sweep is deck-only (faceMs/gapMs 0 — the comet owns its window); tabs are staggered i*420 ms. color is the comm pulse's yellow family.", "enabled": true, "color": "#ffd84d", "periodMs": 4200, "faceMs": 0, "gapMs": 0, "edgeMs": 1500, "dashFrac": 0.3 } }, "_nodeTemplate": { "label": "Tech name", "description": "What this tech is and what it does — shown in the detail readout at the bottom of the console.", "icon": "tether", "duration": 120, "requires": ["parent-tech-id"], "unlocks": { "builds": ["build-id-in-builds-json"], "research": ["child-tech-id"] }, "effects": { "tether": { "level": 2 } } }, "_questGate": "Optional node field — `quest: `: a STORY-QUEST gate on top of `requires`. The named quest (data/quests.json) must have its reward CLAIMED (the dossier's CLAIM action — the same paid-out witness the run's chain uses to grant the next quest) before the node is researchable. The model stays pure: the scene supplies the live predicate, and js/research/ResearchModel.js → attachQuestGates wires it into the tree's optional `available`/`lockNote` hooks (isAvailable consults the hook like any extra gate — the SYSTEM category's chart gate is the same seam; the console's LOCKED readout shows the quest's title via lockNote). In use: mining/premium_arm + mining/advanced_storage (data/research/mining.json) and cargo/cargo_bay_8 (data/research/cargo.json) wait on 'special_delivery'.", "_buildGate": "Optional node field — `build: `: a BUILD-COMPLETION gate on top of `requires`. The named build (data/builds.json) must be BUILT before the node is researchable (for a ship upgrade, built anywhere — the scene's live predicate reads its BuildState built set). Same pure seam as the quest gate: js/research/ResearchModel.js → attachBuildGates wires it into the same optional `available`/`lockNote` hooks (the console's LOCKED readout shows ` BUILD REQUIRED` via lockNote). The two gates compose (each is checked first in the order it was attached). In use: cargo/cargo_bay_12 waits on the mining-storage-improved build, cargo/cargo_bay_16 on mining-storage-advanced (data/research/cargo.json)." }