{ "_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. Keys starting with `_` are documentation, not data.", "enabled": true, "timeUnit": "seconds", "maxConcurrent": 1, "defaultCategory": "exploration", "categories": [ { "id": "exploration", "label": "Exploration", "accent": "#00e5ff" }, { "id": "mining", "label": "Mining", "accent": "#ffc94d" }, { "id": "system", "label": "System", "accent": "#5fd4ff", "dynamic": true } ], "_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.", "glitch": { "enabled": true, "intervalMs": [6000, 13000], "durationMs": [220, 420] }, "sweep": { "enabled": true, "everyMs": [4200, 8600], "durationMs": 1500 } }, "_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 } } } }