{ "_comment": "SHIP CONSOLE \u2014 the command deck's SHIP button (js/ui/ShipWindow.js, depth 80, the MapWindow/QuestWindow idiom). LEFT = the bay camera (assets/videos/ship.mp4, a muted 2:3 loop; the clip is authored separately \u2014 a missing file leaves the NO SIGNAL plate up and the console still works). RIGHT = the tab row (Ship Status / Skill Tree / Cargo Holds \u2014 `standby: true` renders the socket dimmed with its standbyTag and a press fires the locked toast instead of switching) over the tab's plate. The Ship Status plate reads the ship's LIVE numbers through the `status.rows` table below: code owns behavior, this file owns labels, units, order, formatting and where each number comes from (`from` = a namespaced path \u2014 `config:`, `ship:`, `live:` read while a fight is running and off the maxima otherwise; `op: reciprocal` turns a cooldown into a rate). Ship Skill Tree stays OFFLINE until the class/skill layer lands; Cargo Holds lists the trade bays + the mineral aggregate, and a filled bay opens the good's scout summary (js/economy/TradeScout.js \u2014 the buy/sell screens' report).", "enabled": true, "title": "SHIP SYSTEMS", "meta": "HULL & HARDWARE // LIVE TELEMETRY", "video": { "_comment": "The bay camera feed (same contract as map.video / quests.video): `file` is a clip under assets/videos/ (or a full relative path / URL). Muted, looping, 2:3 portrait. A missing file leaves the NO SIGNAL plate up.", "file": "ship.mp4", "aspect": [ 2, 3 ], "tag": "BAY CAMERA // LIVE FEED", "noSignalTag": "BAY CAMERA OFFLINE" }, "tabs": [ { "id": "status", "label": "Ship Status", "accent": "#7ce8a4" }, { "id": "skills", "label": "Skill Tree", "accent": "#ffc94d", "standby": true, "standbyTag": "OFFLINE" }, { "id": "cargo", "label": "Cargo Holds", "accent": "#00e5ff" } ], "skillsLockedToast": "SKILL TREE OFFLINE \u2014 NO CLASS ON FILE", "skillsPanel": { "_comment": "The OFFLINE tab's body (the final structure stays visible): the locked plate's headline + the copy that explains what lands there.", "headline": "SKILL TREE OFFLINE", "note": "COMBAT SKILLS ARM WHEN THE CLASS LAYER LANDS.\nCHOOSE A CLASS AT COMBAT LEVEL 5 \u2014 THE TREE GROWS FROM THERE." }, "glitch": { "_comment": "The console's glitch voice (the quests window's): intervalMs between bursts, durationMs the burst holds, slices RGB bands across the feed, maxOffset px of slide.", "enabled": true, "intervalMs": [ 4200, 9200 ], "durationMs": [ 110, 260 ], "slices": [ 1, 4 ], "maxOffset": 9 }, "sweep": { "_comment": "The scan band that crawls down the feed while the console is up.", "enabled": true, "intervalMs": [ 5200, 9800 ], "durationMs": [ 1400, 2200 ] }, "status": { "_comment": "The SHIP STATUS tab. readout = the hardware table (order = draw order); level = the combat record + the level 0\u21925 journey.", "hardwareTitle": "HARDWARE READOUT", "groups": [ { "id": "armor", "label": "ARMOR" }, { "id": "flight", "label": "FLIGHT" }, { "id": "guns", "label": "GUNS" }, { "id": "hold", "label": "HOLD" } ], "rows": [ { "_comment": "HULL \u2014 the ship's max structure (data/ship.json \u2192 stats.hullIntegrity) with the live combat hull as the meter fill.", "id": "hull", "group": "armor", "label": "HULL STRENGTH", "from": "live:hullMax", "unit": "HP", "format": "int", "meter": { "of": "live:hull" }, "accent": "#ff2d6f", "note": "STRUCTURAL LIMIT", "hotBelow": 0.34 }, { "_comment": "CURRENT HULL \u2014 the armor actually on the ship right now (the live fight's hull; full strength with no fight running).", "id": "hullNow", "group": "armor", "label": "CURRENT HULL", "from": "live:hull", "unit": "HP", "format": "int", "meter": { "_comment": "The bar is the SAME armor meter as HULL STRENGTH (current over max) \u2014 written as an explicit pair because this row's own value is the numerator.", "fill": "live:hull", "max": "live:hullMax" }, "accent": "#ff7ba0", "nominalLabel": "NOMINAL", "hotBelow": 0.34 }, { "id": "shields", "group": "armor", "label": "SHIELDS", "from": "live:shieldsMax", "unit": "HP", "format": "int", "accent": "#00e5ff", "hotBelow": 0.34 }, { "_comment": "SHIELD LEVEL \u2014 the live shield bar (recharges after shieldRegenDelay s without a hit at shieldRegenRate/s).", "id": "shieldNow", "group": "armor", "label": "SHIELD LEVEL", "from": "live:shields", "unit": "HP", "format": "int", "meter": { "_comment": "Same meter as SHIELDS (current over max).", "fill": "live:shields", "max": "live:shieldsMax" }, "accent": "#78b4ff", "nominalLabel": "FULL", "hotBelow": 0.34 }, { "id": "shieldRegen", "group": "armor", "label": "SHIELD RECHARGE", "from": "config:combat.playerShip.shieldRegenRate", "unit": "/s", "format": "int", "accent": "#00e5ff", "meta": { "_comment": "The recharge's dead time after a hit (data/combat.json \u2192 playerShip.shieldRegenDelay), drawn as the row's meta line.", "label": "ARM AFTER", "from": "config:combat.playerShip.shieldRegenDelay", "unit": "s", "format": "int" } }, { "id": "accel", "group": "flight", "label": "ACCELERATION", "from": "config:ship.thrust", "unit": "px/s\u00b2", "format": "int", "accent": "#7ce8a4" }, { "id": "maxSpeed", "group": "flight", "label": "MAX VELOCITY", "from": "config:ship.maxSpeed", "unit": "px/s", "format": "int", "accent": "#7ce8a4" }, { "id": "turn", "group": "flight", "label": "TURN RATE", "from": "config:ship.rotSpeed", "unit": "rad/s", "format": "1dp", "accent": "#7ce8a4" }, { "_comment": "FIRE RATE \u2014 the cannon's shots per second (the reciprocal of data/combat.json \u2192 player.weapon.cooldown).", "id": "fireRate", "group": "guns", "label": "FIRE RATE", "from": "config:combat.player.weapon.cooldown", "op": "reciprocal", "unit": "/s", "format": "2dp", "accent": "#ffc94d" }, { "id": "damage", "group": "guns", "label": "DAMAGE", "from": "config:combat.player.weapon.damage", "unit": "/hit", "format": "int", "accent": "#ffc94d" }, { "_comment": "DPS \u2014 damage \u00d7 fire rate (the row's meta read, not a stored number).", "id": "dps", "group": "guns", "label": "DPS", "from": [ { "from": "config:combat.player.weapon.damage" }, { "from": "config:combat.player.weapon.cooldown", "op": "reciprocal" } ], "op": "product", "unit": "/s", "format": "1dp", "accent": "#ffd84d", "meta": { "label": "SHOT SPEED", "from": "config:combat.player.weapon.bulletSpeed", "unit": "px/s", "format": "int" } }, { "id": "cargoBays", "group": "hold", "label": "CARGO BAYS", "from": "ship:stats.cargoHold", "unit": "bays", "format": "int", "meter": { "of": "ship:cargo.used" }, "accent": "#00e5ff", "meta": { "label": "GRID", "from": "config:economy.cargo.maxSlots", "unit": "SLOTS", "format": "int" } }, { "id": "mineralHold", "group": "hold", "label": "MINERAL STORAGE", "from": "ship:stats.mineralStorage", "unit": "u", "format": "int", "meter": { "of": "ship:minerals" }, "accent": "#78b4ff" }, { "id": "miningSpeed", "group": "hold", "label": "MINING RATE", "from": "ship:stats.miningSpeed", "unit": "\u00d7 base", "format": "2dp", "accent": "#a8ffc4" } ], "creditsLabel": "CREDITS", "noCombatNote": "DRIVE DARK \u2014 NO CONTACTS" }, "level": { "_comment": "The combat record (js/combat/XpManager.js \u2014 the curve is data/progression.json). The journey plate shows levels `from`\u2026`to` as a timeline of milestone nodes with the XP fill between them; at `classAt` (and above) the timeline gives way to CHOOSE YOUR CLASS, and once a class is on file (playerClass.name) it shows the class + insignia instead. Every string lives here.", "title": "COMBAT RECORD", "levelWord": "LEVEL", "xpLabel": "XP", "nextLabel": "NEXT LV", "maxLabel": "MAX LEVEL", "journeyTitle": "THE JOURNEY // LV {from}\u2013{to}", "from": 0, "to": 5, "classAt": 5, "milestones": [ { "level": 0, "label": "SADDLE", "note": "FIRST SEAT" }, { "level": 1, "label": "SOLO", "note": "FIRST KILL" }, { "level": 2, "label": "SCUFFED", "note": "HULL HOLDING" }, { "level": 3, "label": "HUNTED", "note": "ON THE BOARD" }, { "level": 4, "label": "VETERAN", "note": "GUNS HOT" }, { "level": 5, "label": "CHOSEN", "note": "PICK A CLASS" } ], "chooseClass": { "_comment": "The level-5 door: the timeline's replacement. The press fires the scene's onChooseClass seam (the class-selection flow lands with the class layer \u2014 a toast for now, never a dead click).", "label": "CHOOSE YOUR CLASS", "hint": "LEVEL {classAt} REACHED \u2014 THE CLASS LAYER AWAITS", "toast": "CLASS SELECT OFFLINE \u2014 THE CLASS LAYER LANDS NEXT" }, "classPlate": { "_comment": "The class + insignia slot (data-driven seam: shipClass/insignia come from the save once class selection exists; until then the console shows the CHOOSE YOUR CLASS door).", "label": "CLASS", "noneLabel": "NO CLASS ON FILE" } }, "cargo": { "_comment": "The CARGO HOLDS tab. The hold is trade goods only \u2014 one good per bay, in fill order (js/economy/CargoHold.js); locked bays are the hold's upgrade room. MINERALS is the aggregate aboard vs capacity (the Mineral HUD's numbers \u2014 js/entities/Ship.js). A filled bay is tappable: it opens the good's summary + best exits (data below \u2192 detail).", "holdTitle": "TRADE BAYS", "holdMeta": "{used} / {capacity} BAYS \u00b7 {room} FREE", "emptyNote": "HOLD EMPTY \u2014 EVERY BAY IS CLEAN", "noBaysNote": "NO CARGO BAYS INSTALLED \u2014 UPGRADE THE HOLD", "lockedTag": "LOCKED", "manifestTag": "MANIFEST \u25b8", "lockedRoomMeta": "+{locked} LOCKED BAYS \u00b7 UPGRADE THE HOLD AT HARGUN ({slots} TOTAL)", "mineralsTitle": "MINERAL HOLDS", "mineralsMeta": "{minerals} / {capacity} u", "emptyMineralsNote": "NO ORE ABOARD", "creditsLabel": "CREDITS", "detail": { "_comment": "The selected good's summary (the shop scout's report \u2014 js/economy/TradeScout.js scoutGood). `baseline` is the profit reference with no shop under it: baseValue = the catalog value (data/goods.json), buy = this system's own buy price.", "title": "BAY MANIFEST", "categoryLabel": "CLASS", "tierLabel": "T", "valueLabel": "BASE VALUE", "originLabel": "MADE IN", "hubLabel": "HUB POOL", "producerLabel": "MADE OF", "demandLabel": "SOUGHT ON", "exitsTitle": "BEST EXITS", "exitsEmpty": "NO CHARTED EXIT YET \u2014 EVERY CAPITAL BUYS EVERYTHING", "exitMeta": "{hops} HOPS", "profitBar": { "_comment": "the per-unit margin rail under each exit row \u2014 scaled against the list's best row, so goods of different value compare the same way", "track": "#14324f", "fill": "#7ce8a4", "fillNeg": "#ff5a6e" }, "baseline": "baseValue", "topN": 3, "backLabel": "BACK TO BAYS" } } }