orbit/data/combat.json

1037 lines
50 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"_comment": "COMBAT — all combat tuning, one file. Two contexts share the numbers: the standalone range (combat.html / the dev server's /combat — the WASD arena, js/combat/CombatScene.js) and the MAIN GAME (index.html?combat — js/combat/CombatSystem.js drops the pack into the normal game: click-to-fly, the deck swaps for the combat bar, the ship auto-fires at the nearest hostile). Pure rules (armor damage, orbit steering) are js/combat/Combat.js (tested: dev/combat.test.mjs).",
"enabled": true,
"game": {
"_comment": "COMBAT IN THE MAIN GAME (the ?combat boot). spawnCount = hostiles that appear around the ship when the player starts. SPAWNING: hostiles materialize OFF-SCREEN — the furthest of the (spawnRadius band, the camera's visible edge + spawnLead) — and FLY IN on their own chase AI (standoff ring, then lances): nothing pops into view mid-fight. spawnRadius px = the minimum materialization band around the ship; spawnLead px = how far PAST the camera's visible edge they materialize (the fly-in distance — bigger = a longer stream across the screen). autoFire = the ship's guns — while a hostile is within range the ship fires at the NEAREST one (the mouse steers, the cannon tracks; the cannon's own numbers are player.weapon below). ON-SCREEN RULE (js/combat/CombatSystem.js): a hostile must be VISUALLY ON SCREEN (at least partly in the camera's view) to fire or be fired upon — off-screen craft hold their lances, the cannon won't engage them, and their tracers pass straight through; they join the fight the moment they fly into view.",
"spawnCount": 3,
"spawnRadius": 340,
"spawnLead": 140,
"autoFire": {
"enabled": true,
"range": 800
}
},
"energy": {
"_comment": "The player's ENERGY — the skill resource (the combat bar's right ORB — the main game's and the range's). The eight seats (keys 1-8) spend it: a cast takes its cost off the orb and that skill's own recharge runs from there. regen = energy/s recovered while the ship is up — 6/s buys a 60-energy Ramming Charge back in 10s, so a seat is a TEMPO tool rather than one shot per fight; 0 puts the orb back on static.",
"max": 100,
"regen": 6
},
"player": {
"_comment": "The combat feel of YOUR ship in the range (the art, hull stats, and ship name still come from data/ship.json). WASD/arrows fly, SPACE/J fires (hold = auto-fire). The flight feel (js/combat/Combat.js → handleFeel, tested): thrust px/s^2 at full throttle, maxSpeed px/s cap, drag 1/s (exponential coast decay — the range OVERRIDES ship.drag with this, so data/combat.json is the one tuning surface), brake = the S/Down reverse-throttle as a fraction of FULL thrust (instant — a responsive stop). rotSpeed rad/s = the SUSTAINED keyboard turn rate; rotApproach 1/s = how fast the turn rate builds (a tap is a nudge, a hold is a full spin); thrustRamp 1/s = how fast the throttle reaches full on W; thrustDecay 1/s = how fast the throttle cuts when W lets go (no lingering kick). weapon.* = the player's cannon: cooldown s between shots, bulletSpeed px/s, damage per hit, spread rad of aim jitter, bulletLife s before a shot fizzles, color/size the tracer's glow; boltLength/boltWidth px + muzzle px are the laser's VISUALS (the bolt's length × thickness and the muzzle flash's size — js/combat/Bullet.js + Fx.muzzle).",
"thrust": 2000,
"maxSpeed": 560,
"drag": 1.8,
"rotSpeed": 6.4,
"rotApproach": 20,
"thrustRamp": 18,
"thrustDecay": 30,
"brake": 0.9,
"weapon": {
"key": "space",
"cooldown": 0.16,
"bulletSpeed": 680,
"damage": 10,
"spread": 0.03,
"bulletLife": 1.15,
"color": "#00e5ff",
"size": 16,
"boltLength": 46,
"boltWidth": 10,
"muzzle": 46
}
},
"enemy": {
"_comment": "The hostile craft. texture = spritesheet of frameWidth×frameHeight frames (frame picks which one — frame 0 = the red triple-boost fighter, art facing north, filling its frame; the sheet is 10×10 of 128px frames). size = hull length in world px (render = size × the art's footprint), hitRadius = the collision circle in world px (bullets + rams). count = hostiles in the arena at once. speed px/s + turn (1/s velocity-approach rate) = the chase feel. orbitRadius px = the standoff ring they keep around the player; orbitSpeed rad/s = how fast they circle it (direction alternates per craft). hull/shields = their armor (shields absorb first — js/combat/Combat.js). xp = the EXP a kill of one is worth (the XP bank's currency — js/combat/XpManager.js; the curve is data/progression.json, and the base value is 10, so five kills of the base craft = level 1). weapon.* = their lance: range px, cooldown s, bulletSpeed px/s, damage, spread rad, bulletLife s; boltLength/boltWidth px + muzzle px are the lance's VISUALS (the bolt's length × thickness and the muzzle flash's size — js/combat/Bullet.js + Fx.muzzle). ramDamage = hull damage a COLLISION deals you; ramDamageSelf = what the collision deals THEM (per hit, ramGrace s apart). respawn = the pack reforms (respawnDelay s) after a kill, so the range stays stocked while you tune.",
"texture": "assets/images/ships-enemy.png",
"frameWidth": 128,
"frameHeight": 128,
"frame": 0,
"artFacing": "north",
"size": 54,
"hitRadius": 20,
"count": 3,
"speed": 165,
"turn": 3.2,
"orbitRadius": 300,
"orbitSpeed": 0.6,
"hull": 60,
"shields": 0,
"xp": 10,
"weapon": {
"range": 560,
"cooldown": 1.4,
"bulletSpeed": 330,
"damage": 8,
"spread": 0.08,
"bulletLife": 2.4,
"color": "#ff2d6f",
"size": 18,
"boltLength": 54,
"boltWidth": 12,
"muzzle": 50
},
"ramDamage": 10,
"ramDamageSelf": 22,
"ramGrace": 0.8,
"respawn": true,
"respawnDelay": 2.5
},
"enemyTypes": {
"_comment": "The hostile roster a WAVE (combat.waves.scripts) can mix — js/combat/EnemyTypes.js deep-merges a type's overrides onto the base `enemy` above, so a type declares only what differs. `raider` is the base craft under a named id (frame 0). Frames 1–7 are the SEVEN new hostiles, each a distinct craft with a distinct WEAPON PATTERN (weapon.pattern) and a MODERATE difficulty step-up: hull/shields (armor to soak) climb monotonically bomber → warden, so each is a little harder to defeat than the last. PATTERNS (js/combat/Weapons.js): 'lance' (the default — one aimed bolt, the base behavior), 'spray' (a FAN of `bolts` spread over `fan` rad), 'burst' (a TIGHT `bolts`-shot stinger — staggered speeds so it fans over distance), 'rapid' (single bolts on a short cooldown — a machine gun), 'ring' (`bolts` fired RADIAL around the craft — break the orbit), 'missile' (`missiles` HEAT-SEEKING shots that home with a CAPPED turn-rate (missileTurn rad/s) so the player can out-turn them), 'combo' (a heavy lance + a missile, the elite). Add a craft here (a new `frame` or its own `texture` + stat overrides) and name it in a wave's roster; EnemyTypes.enemySheetList() is the preload seam for its textures. Each type's `xp` = what a kill of it is worth (the rule of thumb is (hull+shields)/5, rounded, so a tougher craft is worth more of the XP bank).",
"raider": {
"$comment": "The PIRATE (data/quests.json → pirate_problems — the ambush wave combat.waves.scripts.pirate_ambush is all raiders). comms = the pirate's combat VOICE (the rolls live in js/combat/CombatSystem.js; the comms themselves are data/comms.json's pirate-attack / pirate-defeat — the box's clip + one random speech clip + the message). attack.onScreenChance = the chance a pirate shows an ATTACK comm the first time it flies ON SCREEN (0.5 = 50%); attack.spawnChance = the chance a NEW pirate sent into a rolling wave (the opening burst, each kill's replacement) ATTACK-comms on arrival (0.2 = 20%) — a hit marks the ship, so its later on-screen roll doesn't stack (a pirate never double-comms); defeat.chance = the chance a downed pirate shows a DEFEAT comm (0.2 = 20%).",
"frame": 0,
"comms": {
"attack": {
"onScreenChance": 0.5,
"spawnChance": 0.2,
"trigger": "pirateAttack"
},
"defeat": {
"chance": 0.2,
"trigger": "pirateDefeat"
}
}
},
"bomber": {
"_comment": "frame 1 — the LONG LANCE: slow, sits back, one heavy bolt on a long fuse. The easy end of the new ramp.",
"frame": 1,
"speed": 105,
"turn": 2.2,
"hull": 80,
"shields": 10,
"xp": 18,
"weapon": {
"pattern": "lance",
"range": 760,
"cooldown": 2.0,
"bulletSpeed": 250,
"damage": 10,
"spread": 0.04,
"bulletLife": 3.0
}
},
"spitter": {
"_comment": "frame 2 — the SPRAY: a fan of cheap bolts covering an arc; low damage per bolt, wide cone.",
"frame": 2,
"speed": 130,
"turn": 3.0,
"hull": 90,
"shields": 20,
"xp": 22,
"weapon": {
"pattern": "spray",
"range": 480,
"cooldown": 1.9,
"bulletSpeed": 300,
"damage": 11,
"spread": 0.03,
"bulletLife": 2.2,
"bolts": 5,
"fan": 1.1,
"color": "#ff7a3d"
}
},
"stinger": {
"_comment": "frame 3 — the BURST: a fast, tight 3-shot stinger at close range (staggered speeds fan it out over distance).",
"frame": 3,
"speed": 185,
"turn": 4.0,
"hull": 100,
"shields": 25,
"xp": 25,
"weapon": {
"pattern": "burst",
"range": 400,
"cooldown": 1.5,
"bulletSpeed": 420,
"damage": 12,
"spread": 0.02,
"bulletLife": 1.8,
"bolts": 3,
"burstFan": 0.10,
"color": "#ffe14d"
}
},
"hunter": {
"_comment": "frame 4 — the HUNTER: launches a HEAT-SEEKING missile that homes with a capped turn-rate (missileTurn) — dodgeable by out-turning it.",
"frame": 4,
"speed": 150,
"turn": 3.2,
"hull": 110,
"shields": 30,
"xp": 28,
"weapon": {
"pattern": "missile",
"range": 620,
"cooldown": 2.4,
"missiles": 1,
"missileSpeed": 190,
"missileTurn": 2.2,
"missileDamage": 13,
"missileLife": 4.0,
"missileColor": "#ff5d2d",
"missileSize": 18,
"color": "#ff5d2d"
}
},
"gunner": {
"_comment": "frame 5 — the GUNNER: rapid single bolts on a short cooldown (a machine gun); solid per-bolt punch at a high rate.",
"frame": 5,
"speed": 190,
"turn": 4.2,
"hull": 120,
"shields": 40,
"xp": 32,
"weapon": {
"pattern": "rapid",
"range": 440,
"cooldown": 0.5,
"bulletSpeed": 380,
"damage": 14,
"spread": 0.06,
"bulletLife": 1.6,
"color": "#c8ff3d"
}
},
"barrage": {
"_comment": "frame 6 — the BARRAGE: a RADIAL ring of bolts (break the orbit); slow and armored, pokes a hole in the ring.",
"frame": 6,
"speed": 100,
"turn": 2.0,
"hull": 135,
"shields": 50,
"xp": 37,
"weapon": {
"pattern": "ring",
"range": 360,
"cooldown": 2.2,
"bulletSpeed": 260,
"damage": 15,
"spread": 0.0,
"bulletLife": 2.6,
"bolts": 8,
"color": "#b44dff"
}
},
"warden": {
"_comment": "frame 7 — the WARDEN (elite): a heavy lance PLUS a homing missile on every volley; the hardest of the seven.",
"frame": 7,
"speed": 90,
"turn": 1.8,
"hull": 150,
"shields": 60,
"xp": 42,
"weapon": {
"pattern": "combo",
"range": 700,
"cooldown": 2.6,
"bulletSpeed": 240,
"damage": 18,
"spread": 0.02,
"bulletLife": 3.2,
"size": 20,
"boltLength": 64,
"boltWidth": 14,
"missiles": 1,
"missileSpeed": 170,
"missileTurn": 2.4,
"missileDamage": 20,
"missileLife": 4.2,
"missileColor": "#ff2d6f",
"missileSize": 20,
"color": "#ff2d6f"
}
},
"$factions_note": "FRAMES 30–37 = THE APEX, frames 40–47 = THE MINDWEAVE — the two FACTION rosters (the ?combat=apex / ?combat=mindweave duel arena — combat.factions below names them). BALANCE RULE (the user's numbers, vs the PIRATE raider — 60 armor, 8 bolt damage): BOTH FACTIONS = 2× DAMAGE (16 = 8 × 2, every bolt/seeker/shell) and 3.5× ARMOR (175 hull + 35 shields = 210 = 60 × 3.5). Cooldowns 0.6–3.0 s (pattern character, not power), ramDamage 13 (10 × 1.3). xp = (hull+shields)/5 = 42 (the roster's rule of thumb). DIFFERENTIATION comes from the WEAPON PATTERN (weapon.pattern — the shared dispatch, js/combat/Weapons.js) + the ABILITY LAYER (abilities[].id — js/combat/EnemyAbilities.js: cloaked_ambush / flank_strafe for the Apex's predatory kit, blink_reposition / illusion_decoy / telekinetic_wave for the Mindweave's psychic kit). Each fighter is its own craft; the faction's SHARED skills are the kit's common entries (the Apex all carry the cloak and most the flank; the Mindweave all carry the blink or the phantoms and several the tides) at per-ship numbers.",
"apex_raptor": {
"$comment": "APEX 1 (frame 30) — RAPTOR, the forward gun: a 5-bolt SWEEP out the nose (heavy forward firepower) on a medium beat; the flank's cut gets it behind the player every 10 s or so so the sweep lands on the back.",
"name": "Raptor",
"frame": 30,
"speed": 175,
"turn": 3.6,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "sweep",
"range": 520,
"cooldown": 1.2,
"bulletSpeed": 360,
"damage": 16,
"spread": 0.03,
"bulletLife": 2.0,
"bolts": 5,
"fan": 1.0,
"color": "#ff4d2d"
},
"abilities": [
{ "id": "flank_strafe", "period": 10, "radius": 210, "color": "#ff4d2d" }
]
},
"apex_mauler": {
"$comment": "APEX 2 (frame 31) — MAULER, the heavy hitter: one BIG slow bolt (damage 16, the 2× bolt, wide visual) — slow, armored, deliberate; comes out of CLOAK to swing it (the shared ambush).",
"name": "Mauler",
"frame": 31,
"speed": 120,
"turn": 2.4,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "lance",
"range": 560,
"cooldown": 1.6,
"bulletSpeed": 240,
"damage": 16,
"spread": 0.02,
"bulletLife": 2.6,
"size": 22,
"boltLength": 70,
"boltWidth": 16,
"color": "#ff2d4d"
},
"abilities": [
{ "id": "cloaked_ambush", "cloakMs": 3200, "color": "#ff2d4d" }
]
},
"apex_stalker": {
"$comment": "APEX 3 (frame 32) — STALKER, the double-cloak (the signature): cloaks on contact, strikes, then DROPS BACK INTO CLOAK on its own clock (recloakCooldown/recloakMs) — strike, vanish, strike again. Fast, clean lance.",
"name": "Stalker",
"frame": 32,
"speed": 185,
"turn": 4.0,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "lance",
"range": 520,
"cooldown": 1.1,
"bulletSpeed": 380,
"damage": 16,
"spread": 0.04,
"bulletLife": 2.0,
"color": "#ff8a2d"
},
"abilities": [
{ "id": "cloaked_ambush", "cloakMs": 3600, "recloakCooldown": 16, "recloakMs": 2800, "color": "#ff8a2d" }
]
},
"apex_screamer": {
"$comment": "APEX 4 (frame 33) — SCREAMER, the wide fan: a 9-bolt SPRAY across 1.6 rad (dodge the core, not the wall); the flank's cut re-positions it before each scream.",
"name": "Screamer",
"frame": 33,
"speed": 150,
"turn": 3.0,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "spray",
"range": 480,
"cooldown": 1.4,
"bulletSpeed": 340,
"damage": 16,
"spread": 0.02,
"bulletLife": 1.8,
"bolts": 9,
"fan": 1.6,
"color": "#ffd84d"
},
"abilities": [
{ "id": "flank_strafe", "period": 9, "radius": 200, "color": "#ffd84d" }
]
},
"apex_reaver": {
"$comment": "APEX 5 (frame 34) — REAVER, the gun: RAPID bolts at 0.6 s (the pack's highest DPS — out-turn it) with the double-cloak signature (a gun you can't see).",
"name": "Reaver",
"frame": 34,
"speed": 190,
"turn": 4.4,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "rapid",
"range": 460,
"cooldown": 0.6,
"bulletSpeed": 400,
"damage": 16,
"spread": 0.06,
"bulletLife": 1.5,
"color": "#ff6a2d"
},
"abilities": [
{ "id": "cloaked_ambush", "cloakMs": 2800, "recloakCooldown": 18, "recloakMs": 2200, "color": "#ff6a2d" }
]
},
"apex_fang": {
"$comment": "APEX 6 (frame 35) — FANG, the stinger: a TIGHT 4-shot BURST (staggered speeds open it over distance — the close-range snap) at 16 damage each; cuts behind before it snaps.",
"name": "Fang",
"frame": 35,
"speed": 180,
"turn": 4.2,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "burst",
"range": 420,
"cooldown": 1.5,
"bulletSpeed": 420,
"damage": 16,
"spread": 0.02,
"bulletLife": 1.6,
"bolts": 4,
"burstFan": 0.14,
"color": "#ff3d2d"
},
"abilities": [
{ "id": "flank_strafe", "period": 12, "radius": 230, "color": "#ff3d2d" }
]
},
"apex_tyrant": {
"$comment": "APEX 7 (frame 36) — TYRANT, the swarm: TWO heat-seeking missiles per volley (out-turn both) on a long beat; cloaks in, then the seekers come — the flank's cut keeps it off your nose.",
"name": "Tyrant",
"frame": 36,
"speed": 150,
"turn": 3.2,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "missile",
"range": 620,
"cooldown": 2.2,
"missiles": 2,
"missileSpeed": 190,
"missileTurn": 2.2,
"missileDamage": 16,
"missileLife": 4.0,
"missileColor": "#ff5d2d",
"missileSize": 18,
"color": "#ff5d2d"
},
"abilities": [
{ "id": "cloaked_ambush", "cloakMs": 3000, "color": "#ff5d2d" },
{ "id": "flank_strafe", "period": 13, "radius": 220, "color": "#ff5d2d" }
]
},
"apex_alpha": {
"$comment": "APEX 8 (frame 37) — ALPHA, the pack's top predator (the elite): a COMBO of heavy lance (16) + two seekers every 2.4 s, the FULL kit — the longest cloak, the double-cloak's nap, and the flank's cut. 2× the pirate's damage, 3.5× its armor — the whole predatory doctrine in everything else.",
"name": "Alpha",
"frame": 37,
"speed": 130,
"turn": 2.8,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "combo",
"range": 640,
"cooldown": 2.4,
"bulletSpeed": 260,
"damage": 16,
"spread": 0.02,
"bulletLife": 2.8,
"size": 20,
"boltLength": 64,
"boltWidth": 14,
"missiles": 2,
"missileSpeed": 180,
"missileTurn": 2.3,
"missileDamage": 16,
"missileLife": 4.2,
"missileColor": "#ffb02d",
"missileSize": 20,
"color": "#ffb02d"
},
"abilities": [
{ "id": "cloaked_ambush", "cloakMs": 3800, "recloakCooldown": 20, "recloakMs": 3000, "color": "#ffb02d" },
{ "id": "flank_strafe", "period": 10, "radius": 210, "color": "#ffb02d" }
]
},
"weave_mindblade": {
"$comment": "MINDWEAVE 1 (frame 40) — MINDBLADE, the spiral: a 10-bolt SPIRAL (each bolt a step further around, staggered speeds — the 1942-boss bloom) at 16 damage; BLINKS to a fresh stand (biasing behind) and leaves a phantom at the old one.",
"name": "Mindblade",
"frame": 40,
"speed": 140,
"turn": 3.0,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "spiral",
"range": 560,
"cooldown": 2.2,
"bulletSpeed": 210,
"damage": 16,
"spread": 0.01,
"bulletLife": 3.0,
"bolts": 10,
"spiralStep": 0.55,
"stagger": 0.12,
"color": "#b44dff"
},
"abilities": [
{ "id": "blink_reposition", "cooldown": 12, "color": "#b44dff" }
]
},
"weave_telekine": {
"$comment": "MINDWEAVE 2 (frame 41) — TELEKINE, the tide: a rapid mind-cannon (16 per 0.6 s) backed by a PSYCHIC TIDE — every 13 s a wave blooms and SHOVES the player out of its line (the dodge window: be far or be moved).",
"name": "Telekine",
"frame": 41,
"speed": 175,
"turn": 3.8,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "rapid",
"range": 500,
"cooldown": 0.6,
"bulletSpeed": 400,
"damage": 16,
"spread": 0.05,
"bulletLife": 1.6,
"color": "#4dd7ff"
},
"abilities": [
{ "id": "telekinetic_wave", "cooldown": 13, "dir": "push", "radius": 290, "power": 280, "color": "#4dd7ff" },
{ "id": "blink_reposition", "cooldown": 17, "color": "#4dd7ff" }
]
},
"weave_phantasm": {
"$comment": "MINDWEAVE 3 (frame 42) — PHANTASM, the court: a blossoming VOLLEY (8 staggered bolts — the wall) with the FULL illusion kit — projects a COURT of three phantoms (the auto-fire's pool chases them) and blinks among them.",
"name": "Phantasm",
"frame": 42,
"speed": 150,
"turn": 3.2,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "volley",
"range": 540,
"cooldown": 1.6,
"bulletSpeed": 260,
"damage": 16,
"spread": 0.01,
"bulletLife": 2.6,
"bolts": 8,
"fan": 1.5,
"stagger": 0.4,
"color": "#ff4dc8"
},
"abilities": [
{ "id": "illusion_decoy", "cooldown": 15, "count": 3, "radius": 180, "lifeMs": 2800, "color": "#ff4dc8" },
{ "id": "blink_reposition", "cooldown": 16, "decoyMs": 2000, "color": "#ff4dc8" }
]
},
"weave_weaver": {
"$comment": "MINDWEAVE 4 (frame 43) — WEAVER, the threads: a 9-bolt SWEEP out its own heading (its threads fly where it FACES — aim your counter off its nose) with the most frequent BLINK of the faction (11 s — it is rarely where you last saw it).",
"name": "Weaver",
"frame": 43,
"speed": 160,
"turn": 3.6,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "sweep",
"range": 520,
"cooldown": 1.3,
"bulletSpeed": 300,
"damage": 16,
"spread": 0.02,
"bulletLife": 2.2,
"bolts": 9,
"fan": 1.4,
"color": "#4dffc8"
},
"abilities": [
{ "id": "blink_reposition", "cooldown": 11, "decoyMs": 2200, "color": "#4dffc8" }
]
},
"weave_lure": {
"$comment": "MINDWEAVE 5 (frame 44) — LURE, the net: a RADIAL ring of ten (break the orbit) with the phantoms and a PULL tide — it drags the player IN toward the ring (the push tide's inverse: its net closes on you).",
"name": "Lure",
"frame": 44,
"speed": 130,
"turn": 2.6,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "ring",
"range": 460,
"cooldown": 2.3,
"bulletSpeed": 240,
"damage": 16,
"spread": 0.0,
"bulletLife": 2.6,
"bolts": 10,
"color": "#4da6ff"
},
"abilities": [
{ "id": "illusion_decoy", "cooldown": 14, "count": 2, "radius": 200, "lifeMs": 3000, "color": "#4da6ff" },
{ "id": "telekinetic_wave", "cooldown": 16, "dir": "pull", "radius": 300, "power": 240, "color": "#4da6ff" }
]
},
"weave_dreamer": {
"$comment": "MINDWEAVE 6 (frame 45) — DREAMSHELL, the shells: one LARGE-RADIUS slow shell (44 px glow, 16 damage, a short fuse — the big slow rock) per volley, backed by a hard PUSH tide (300 px/s — the strongest shove of the faction).",
"name": "Dreamshell",
"frame": 45,
"speed": 110,
"turn": 2.2,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "shell",
"range": 600,
"cooldown": 2.6,
"damage": 16,
"shells": 1,
"shellDamage": 16,
"shellSize": 44,
"shellSpeed": 140,
"shellLife": 2.4,
"color": "#8a4dff"
},
"abilities": [
{ "id": "telekinetic_wave", "cooldown": 14, "dir": "push", "radius": 300, "power": 300, "color": "#8a4dff" },
{ "id": "illusion_decoy", "cooldown": 17, "count": 2, "radius": 200, "lifeMs": 2600, "color": "#8a4dff" }
]
},
"weave_serpent": {
"$comment": "MINDWEAVE 7 (frame 46) — SERPENTMIND, the coil: a LONG 14-bolt spiral (a tighter step, slower bolts — a COIL that hangs around longer than Mindblade's) with the blink and a PULL tide (it repositions, then reeled you in).",
"name": "Serpentmind",
"frame": 46,
"speed": 145,
"turn": 3.0,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "spiral",
"range": 580,
"cooldown": 2.4,
"bulletSpeed": 190,
"damage": 16,
"spread": 0.01,
"bulletLife": 3.2,
"bolts": 14,
"spiralStep": 0.42,
"stagger": 0.1,
"color": "#c84dff"
},
"abilities": [
{ "id": "blink_reposition", "cooldown": 13, "color": "#c84dff" },
{ "id": "telekinetic_wave", "cooldown": 18, "dir": "pull", "radius": 310, "power": 260, "color": "#c84dff" }
]
},
"weave_overmind": {
"$comment": "MINDWEAVE 8 (frame 47) — OVERMIND, the faction's apex (the elite): a TRIPLE SHELL volley (three large orbs per turn — the slowest, heaviest barrage of the roster) with the FULL psychic kit — the most frequent blink, a standing phantom court, and the strongest PULL tide (300). Control every axis of the fight at 2× the pirate's offense and 3.5× its armor.",
"name": "Overmind",
"frame": 47,
"speed": 100,
"turn": 2.0,
"hull": 175,
"shields": 35,
"xp": 42,
"ramDamage": 13,
"ramDamageSelf": 29,
"weapon": {
"pattern": "shell",
"range": 620,
"cooldown": 3.0,
"damage": 16,
"shells": 3,
"shellDamage": 16,
"shellSize": 44,
"shellSpeed": 150,
"shellLife": 2.6,
"color": "#e04dff"
},
"abilities": [
{ "id": "blink_reposition", "cooldown": 10, "decoyMs": 2200, "color": "#e04dff" },
{ "id": "illusion_decoy", "cooldown": 13, "count": 2, "radius": 190, "lifeMs": 2800, "color": "#e04dff" },
{ "id": "telekinetic_wave", "cooldown": 15, "dir": "pull", "radius": 320, "power": 300, "color": "#e04dff" }
]
}
},
"factions": {
"_comment": "The FACTION DUEL ARENA (the ?combat=apex / ?combat=mindweave boots — js/scenes/GameScene.js → _combatBootMode, js/combat/DuelPicker.js the in-game picker, CombatSystem.startDuel/spawnDuel/spawnDuelPack the fight's seam). Each faction names its fighters (enemyTypes ids, display order = the picker's rows 1–8); 'style' is the picker's one-line read. Key '0' (or the row) sends the WHOLE pack. These rosters are NOT wired into any quest/galaxy content — the arena is their only home for now.",
"apex": {
"name": "Apex",
"tagline": "apex predators — cloaked ambushes, flanking strikes, devastating forward firepower",
"types": [
{ "id": "apex_raptor", "name": "Raptor", "style": "forward fan sweep + the flank's cut" },
{ "id": "apex_mauler", "name": "Mauler", "style": "one heavy bolt out of cloak" },
{ "id": "apex_stalker", "name": "Stalker", "style": "strike, vanish, strike again (double cloak)" },
{ "id": "apex_screamer", "name": "Screamer", "style": "a nine-bolt wall across your nose" },
{ "id": "apex_reaver", "name": "Reaver", "style": "the unseen gun — rapid + double cloak" },
{ "id": "apex_fang", "name": "Fang", "style": "a tight stinger from behind" },
{ "id": "apex_tyrant", "name": "Tyrant", "style": "twin seekers out of the dark" },
{ "id": "apex_alpha", "name": "Alpha", "style": "the pack's apex — the full predatory kit" }
]
},
"mindweave": {
"name": "Mindweave",
"tagline": "psychic weavers — blinks, phantoms, telekinetic tides, spiralling barrages",
"types": [
{ "id": "weave_mindblade", "name": "Mindblade", "style": "a ten-bolt spiral that blooms in flight" },
{ "id": "weave_telekine", "name": "Telekine", "style": "rapid fire + a tide that shoves you" },
{ "id": "weave_phantasm", "name": "Phantasm", "style": "a court of phantoms + the blink" },
{ "id": "weave_weaver", "name": "Weaver", "style": "threads out its heading, blinks fastest" },
{ "id": "weave_lure", "name": "Lure", "style": "a radial net + a tide that drags you in" },
{ "id": "weave_dreamer", "name": "Dreamshell", "style": "large slow shells + the hardest shove" },
{ "id": "weave_serpent", "name": "Serpentmind", "style": "a long hanging coil + the reeling pull" },
{ "id": "weave_overmind", "name": "Overmind", "style": "triple shells + the full psychic kit" }
]
}
},
"neutral": {
"_comment": "The base NEUTRAL CRAFT — the friendly player-side ships (data/combat.json → neutralTypes deep-merges its overrides onto this base, exactly like enemyTypes onto enemy). They ride the SAME enemy spritesheet (assets/images/ships-enemy.png) on frames 20–27, one distinct craft per frame. side 'player' is the seam: they NEVER target or damage the player, the player's auto-fire never targets them, and CombatSystem flies them in its OWN allies roster (not enemies — js/combat/CombatSystem.js). Their AI (js/combat/NeutralShip.js): escort the player for a per-ship window (escortMin–escortMax seconds — the spawner rolls one per ship), then break off and fly off-screen; while any pirate is up they instead fight the NEAREST one with their own weapon pattern (the same Weapons.js dispatch the hostiles run). The stats ramp: hull/shields climb monotonically frame 20 → 27 (xp = (hull+shields)/5 — the same rule of thumb as the pirate roster). respawn false: encounter ships are spent, never reformed; ramDamage 0: a friendly ship's ram can't hurt the player.",
"texture": "assets/images/ships-enemy.png",
"frameWidth": 128,
"frameHeight": 128,
"frame": 20,
"artFacing": "north",
"side": "player",
"size": 54,
"hitRadius": 20,
"speed": 175,
"turn": 3.2,
"orbitRadius": 260,
"orbitSpeed": 0.6,
"escortRadius": 110,
"escortMin": 10,
"escortMax": 15,
"hull": 60,
"shields": 0,
"xp": 12,
"weapon": {
"pattern": "lance",
"range": 500,
"cooldown": 1.6,
"bulletSpeed": 340,
"damage": 9,
"spread": 0.05,
"bulletLife": 2.2,
"color": "#37e0a1",
"size": 16,
"boltLength": 48,
"boltWidth": 10,
"muzzle": 44
},
"ramDamage": 0,
"ramDamageSelf": 0,
"ramGrace": 0.8,
"respawn": false
},
"neutralTypes": {
"_comment": "The NEUTRAL roster (frames 20–27 of assets/images/ships-enemy.png) — the random-encounter director (GameScene → CombatSystem.spawnAlly) deploys them, js/combat/NeutralShip.js flies them. Each entry declares ONLY what differs from the neutral base above (the merge is EnemyTypes.neutralTypeConfig — the same deep-merge contract as the enemyTypes): a DISTINCT weapon pattern (the Weapons.js set — lance/spray/burst/rapid/ring/missile/combo — plus 'volley', the eighth pattern) and the STATS RAMP (hull/shields climb monotonically frame 20 → 27; xp = (hull+shields)/5). side stays 'player' (inherited — never hostile).",
"courier": {
"$comment": "frame 20 — the LANCE: the scout. Fast and light, one aimed bolt; the easy end of the ramp.",
"frame": 20,
"speed": 195,
"turn": 3.4,
"hull": 60,
"shields": 0,
"xp": 12,
"weapon": { "pattern": "lance", "range": 500, "cooldown": 1.6, "bulletSpeed": 340, "damage": 9, "spread": 0.05, "bulletLife": 2.2, "color": "#37e0a1" }
},
"runner": {
"$comment": "frame 21 — the RAPID: the fast gun. Short cooldown, honest per-bolt punch.",
"frame": 21,
"speed": 200,
"turn": 4.0,
"hull": 70,
"shields": 10,
"xp": 16,
"weapon": { "pattern": "rapid", "range": 440, "cooldown": 0.5, "bulletSpeed": 380, "damage": 8, "spread": 0.06, "bulletLife": 1.6, "color": "#7df3c1" }
},
"sloop": {
"$comment": "frame 22 — the SPRAY: a fan of cheap bolts covering an arc.",
"frame": 22,
"speed": 150,
"turn": 2.6,
"hull": 80,
"shields": 15,
"xp": 19,
"weapon": { "pattern": "spray", "range": 460, "cooldown": 2.0, "bulletSpeed": 300, "damage": 8, "spread": 0.03, "bulletLife": 2.2, "bolts": 5, "fan": 1.1, "color": "#37e0a1" }
},
"sally": {
"$comment": "frame 23 — the BURST: a fast, tight 3-shot stinger at close range.",
"frame": 23,
"speed": 180,
"turn": 3.6,
"hull": 90,
"shields": 20,
"xp": 22,
"weapon": { "pattern": "burst", "range": 380, "cooldown": 1.5, "bulletSpeed": 420, "damage": 9, "spread": 0.02, "bulletLife": 1.8, "bolts": 3, "burstFan": 0.10, "color": "#aef27d" }
},
"sentinel": {
"$comment": "frame 24 — the RING: a radial volley; slow, armored, pokes a hole in the line.",
"frame": 24,
"speed": 130,
"turn": 2.4,
"hull": 100,
"shields": 30,
"xp": 26,
"weapon": { "pattern": "ring", "range": 360, "cooldown": 2.2, "bulletSpeed": 260, "damage": 10, "spread": 0.0, "bulletLife": 2.6, "bolts": 8, "color": "#4dd7ff" }
},
"herald": {
"$comment": "frame 25 — the MISSILE: one heat-seeking shot per volley (dodgeable at the capped turn).",
"frame": 25,
"speed": 160,
"turn": 3.0,
"hull": 110,
"shields": 35,
"xp": 29,
"weapon": { "pattern": "missile", "range": 620, "cooldown": 2.4, "missiles": 1, "missileSpeed": 190, "missileTurn": 2.2, "missileDamage": 13, "missileLife": 4.0, "missileColor": "#37e0a1", "missileSize": 16, "color": "#37e0a1" }
},
"vanguard": {
"$comment": "frame 26 — the COMBO: a heavy bolt PLUS a heat-seeker on every volley.",
"frame": 26,
"speed": 110,
"turn": 2.0,
"hull": 130,
"shields": 45,
"xp": 35,
"weapon": { "pattern": "combo", "range": 700, "cooldown": 2.6, "bulletSpeed": 240, "damage": 16, "spread": 0.02, "bulletLife": 3.2, "size": 18, "boltLength": 56, "boltWidth": 12, "missiles": 1, "missileSpeed": 170, "missileTurn": 2.4, "missileDamage": 18, "missileLife": 4.2, "missileColor": "#7df3c1", "missileSize": 18, "color": "#7df3c1" }
},
"bulwark": {
"$comment": "frame 27 — the VOLLEY: the ramp's top — a WIDE fan whose speeds are staggered, so the arc blossoms into a wall over distance (Weapons.js 'volley': the burst's stagger across the spray's fan).",
"frame": 27,
"speed": 95,
"turn": 1.8,
"hull": 150,
"shields": 60,
"xp": 42,
"weapon": { "pattern": "volley", "range": 520, "cooldown": 2.4, "bulletSpeed": 280, "damage": 11, "spread": 0.02, "bulletLife": 2.8, "bolts": 7, "fan": 1.5, "stagger": 0.35, "color": "#c8f54d" }
}
},
"transport": {
"_comment": "The ESCORT CONTRACT's transport (sidequests.escort — data/sidequests.json): the neutral FREIGHTER the player rendevouses with at a local waypoint and leads through the jump gates to a planet / space station 1–2 hops away. A PERSISTENT player-side craft (side 'player' — it never targets or damages the player; CombatSystem's active/neutralOnly checks skip it via its `persistent` flag, so a standing convoy never holds the jump gate / the landing door). js/combat/TransportShip.js flies it (the NeutralShip escort/engage AI with the break-off window lapsed — it stays for the life of the contract). ART: assets/images/ships-transport.png, frames 0–3 at 180×320 (the art faces north, filling its frame — the long side is the ship's length). SIZE 135 reads as the hull's world LENGTH: ~2.5× a fighter's 54 (TransportShip scales by the sheet's long side, not the frameWidth the square enemy sheet assumes). ARMOR far above the escort roster (hull 1500 / shields 650 vs the roster's 60–150/0–60) — it soaks fire off the player; WEAPON a wide 'volley' fan (the roster's top pattern) whose per-bolt punch is honest (the transport is a bodyguard, not a gunboat). respawns: false — the convoy is spent when it's lost (the contract's reset, not a re-form).",
"texture": "assets/images/ships-transport.png",
"frameWidth": 180,
"frameHeight": 320,
"frame": 0,
"artFacing": "north",
"side": "player",
"size": 135,
"hitRadius": 46,
"speed": 245,
"turn": 2.6,
"orbitRadius": 320,
"orbitSpeed": 0.45,
"escortRadius": 175,
"hull": 1500,
"shields": 650,
"xp": 0,
"weapon": {
"pattern": "volley",
"range": 660,
"cooldown": 2.3,
"bulletSpeed": 300,
"damage": 14,
"spread": 0.02,
"bulletLife": 3.0,
"bolts": 7,
"fan": 1.5,
"stagger": 0.3,
"size": 22,
"boltLength": 64,
"boltWidth": 14,
"color": "#7ce8a4"
},
"ramDamage": 0,
"ramDamageSelf": 0,
"ramGrace": 0.8,
"respawn": false
},
"encounters": {
"_comment": "PERIODIC RANDOM ENCOUNTERS (js/combat/Encounters.js → EncounterDirector; GameScene spawns on a hit) — every NEUTRAL non-home system, while flying, no combat live, no wave run in flight, one at a time. The director rolls on the interval schedule (rollIntervalMin–rollIntervalMax s; each attempt fires at chanceMin–chanceMax probability), and after an encounter ENDS it stays quiet for cooldownMin–cooldownMax s before the next roll. KIND rolls at `weights` (shipped even — ~1/3 each). SIZE: the player's combat level (js/combat/XpManager — a fresh run starts at 0) picks a band; mixed keeps both kinds present. Encounter hostiles are one-shots (no respawn); escorts carry the neutral.escortMin/escortMax window and break off on their own (js/combat/NeutralShip.js). Seconds + 0–1 probabilities — no code constants.",
"enabled": true,
"rollIntervalMin": 45,
"rollIntervalMax": 90,
"chanceMin": 0.5,
"chanceMax": 0.6,
"cooldownMin": 30,
"cooldownMax": 60,
"weights": { "pirates": 1, "neutrals": 1, "mixed": 1 },
"bands": [
{ "minLevel": 0, "maxLevel": 2, "pirates": [1, 2], "neutrals": [1, 2], "mixedPirates": [1, 1], "mixedNeutrals": [1, 1] },
{ "minLevel": 3, "maxLevel": 5, "pirates": [2, 3], "neutrals": [2, 3], "mixedPirates": [1, 2], "mixedNeutrals": [1, 2] },
{ "minLevel": 6, "maxLevel": 8, "pirates": [5, 7], "neutrals": [5, 7], "mixedPirates": [3, 4], "mixedNeutrals": [2, 3], "roster": { "raider": 5, "spitter": 3, "bomber": 2 } },
{ "minLevel": 9, "maxLevel": 12, "pirates": [7, 9], "neutrals": [7, 9], "mixedPirates": [4, 5], "mixedNeutrals": [3, 4], "roster": { "raider": 4, "spitter": 2, "stinger": 2, "gunner": 2, "bomber": 1 } },
{ "minLevel": 13, "maxLevel": 99, "pirates": [9, 12], "neutrals": [9, 12], "mixedPirates": [4, 5], "mixedNeutrals": [4, 5], "roster": { "raider": 3, "spitter": 2, "stinger": 2, "gunner": 2, "hunter": 2, "barrage": 2 } }
],
"_tiers": "THE LEVEL-6 JUMP (design): 1–2 → 2–3 → 5–7 (a ~2× step in the pack) → 7–9 → 9–12 (the cap — past level 13 the count holds and the roster's heavier types are the pressure). roster = the band's weighted pirate MIX (js/combat/Encounters.js → compose draws it into `pirateTypes`; a band without one leaves the scene's random pick standing — tiers 0–5 predate the rosters): raiders stay the anchor (the filler), spitter/bomber land at the jump, stinger/gunner at 9+, hunter (missiles) and barrage (rings) at 13+ — the heavy hitters, never the early pack."
},
"waves": {
"_comment": "WAVE SCRIPTS — the quest-step combat (js/combat/Waves.js + CombatSystem.startWaves; the quest hook that FIRES one lands later, random encounters too — for now the console drives it: window.__ORBIT_COMBAT__.waves). A script is an ordered list of waves. KINDS: 'rolling' = maxConcurrent on screen at a time, each kill sending in replacePerKill more until the force is spent; 'fixed' = the whole force at once, no replacements until exhausted. COMPOSITION: `roster` = enemyTypes id → weight (the mix); `total` = the units deployed (default = the weight sum); `type` + `count` is the single-type shorthand. lead = s before wave 1; gap = s between waves (per-wave `gap` s overrides). The quests reference scripts BY NAME (e.g. {\"combat\": \"raid\"}) when the hook lands.",
"lead": 1.5,
"gap": 2.0,
"defaults": {
"maxConcurrent": 3,
"replacePerKill": 1
},
"scripts": {
"probe": [
{ "kind": "fixed", "type": "raider", "count": 3 }
],
"skirmish": [
{ "kind": "rolling", "roster": { "raider": 2 }, "total": 8, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "fixed", "roster": { "raider": 3, "bomber": 1 }, "total": 4 }
],
"raid": [
{ "kind": "fixed", "roster": { "raider": 4, "bomber": 2 }, "total": 6 },
{ "kind": "rolling", "roster": { "raider": 3, "bomber": 1 }, "total": 12, "maxConcurrent": 4, "replacePerKill": 1 }
],
"pirate_ambush": [
{
"$comment": "'Pirate Problems' (data/quests.json → pirate_problems) — the quest's combat step ('Defend your ship from pirates'): ONE rolling wave of SIX frame-0 raiders (EnemyTypes: raider = the sheet's frame 0). Fired when the player ENTERS the Neutral Capital's system with the quest live (GameScene._maybeStartPirateAmbush — create() and the grant's start beats); the wave ships materialize OFF-SCREEN around the player, each from a random direction (CombatSystem._spawnWaveEnemy — the same off-screen materialization as the ?combat pack), streaming in on their own chase AI; three at a time (maxConcurrent), each kill sending in a replacement (replacePerKill) until all six are down; the completion marks the 'combatDone' ledger (GameScene._recordCombatDone — the step's monotonic witness, SaveData-captured).",
"kind": "rolling",
"type": "raider",
"count": 6,
"maxConcurrent": 3,
"replacePerKill": 1
}
],
"combat_demo": [
{
"$comment": "?COMBAT DEMO (the tuning sandbox's boot — GameScene._wantsCombatDemo): a SEQUENCE of rolling waves, ONE per new hostile type in DIFFICULTY ORDER (bomber → warden, frames 1–7). Each wave is SIX of that single type, THREE on screen at a time (maxConcurrent), each kill sending in a replacement (replacePerKill) until the six are down — then the NEXT type's wave. This is the showcase for the seven new weapons (js/combat/Weapons.js).",
"kind": "rolling",
"type": "bomber",
"count": 6,
"maxConcurrent": 3,
"replacePerKill": 1
},
{ "kind": "rolling", "type": "spitter", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "rolling", "type": "stinger", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "rolling", "type": "hunter", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "rolling", "type": "gunner", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "rolling", "type": "barrage", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 },
{ "kind": "rolling", "type": "warden", "count": 6, "maxConcurrent": 3, "replacePerKill": 1 }
]
}
},
"playerShip": {
"_comment": "Your armor (both contexts — the real ship's stats.hullIntegrity/shields replace this when combat ships for real). shields absorb before the hull; after shieldRegenDelay s without a hit, shields regrow at shieldRegenRate/s up to their max. respawn = when your hull breaks you come back (respawnDelay s) instead of the run ending. The MAIN GAME now runs the real death flow (js/scenes/GameScene.js → _playerWentDown: the Game Over beat ends the run — it forces playerRespawn off, overridable only by a skill), so here it only keeps the ?combat range's sandbox convenience.",
"hull": 100,
"shields": 40,
"shieldRegenDelay": 4,
"shieldRegenRate": 14,
"respawn": true,
"respawnDelay": 2.0
},
"fx": {
"_comment": "The LOOK of combat (both contexts — js/combat/Fx.js + js/combat/CamShake.js). shake = the camera kick on the WORLD pass (the separate UI pass keeps the bar + feed still): each value is Phaser's shake intensity (a FRACTION of the viewport in px) and ms = the duration. hit = you take a hit, kill = an enemy goes down, break = the shield-collapse beat, ram = the Ramming Charge's impact (the heaviest beat). shield = the shield-sphere impact read: radius px = the sphere's world radius around the ship (the impact point sits this far off the ship), arcSpan rad = the bright arc's angular width (the highlighted hit region), ripple px = the shockwave that grows from the impact point, decayMs = how long the sphere flash lingers.",
"shake": { "hit": 0.0045, "kill": 0.007, "break": 0.011, "ram": 0.014, "ms": 230 },
"shield": { "radius": 40, "arcSpan": 1.9, "ripple": 90, "decayMs": 520 }
},
"arena": {
"_comment": "The range walls. inset = px between the screen edge and the bounce wall; bounce = the fraction of speed a ship keeps off a wall (1 = full mirror bounce). wallColor = the rim line, wallGlow = its neon edge.",
"inset": 26,
"bounce": 0.9,
"wallColor": "#1b2c4a",
"wallGlow": "#00e5ff"
},
"sandbox": {
"_comment": "Sandbox conveniences — none of this ships with the real game. resetKey rebuilds the whole range (a fresh scene); pauseKey freezes the sim (handy for eyeballing positions via window.__COMBAT__ in the console); respawn = the player comes back (respawnDelay s) instead of dying permanently.",
"resetKey": "R",
"pauseKey": "P",
"respawn": true,
"respawnDelay": 1.6
},
"readability": {
"_comment": "The READABILITY LAYER (js/combat/CombatHud.js) — the 'what do I need to see' side of combat, shared by both contexts. reticle = the target/LOCK indicator: rotating corner brackets on the current target (the one your guns are on — the range's nearest hostile, the main game's auto-fire target; the shared rule is Combat.js → pickTarget). margin = px outside the target's hitRadius, spin = rad/s bracket rotation, color = the lock's amber. threat = the hostile cue: a small pulsing marker above EVERY enemy (size px, pulse 1/s, above = px over the ship's center, color = hot red). offscreen = the edge ARROWS for hostiles outside the camera's view (main game — the range's arena is one screen, so they rarely fire there): pad = px inside the screen edge, size = arrow px, max = the most arrows drawn (nearest hostiles win), color. feed = the KILL FEED, top-right (entries = max lines, life ms per entry, gap px between lines, size px font, xInset = px in from the screen's RIGHT edge, y0 = px DOWN from the top (the first line), ink + hit colors). barFlash = the combat bar's GAUGE FLASH when YOU take a hit: life ms, alpha = the white overlay's start (the shield gauge flashes on shield hits, the hull gauge on hull hits — both when the shields pop through).",
"reticle": { "enabled": true, "margin": 16, "spin": 1.6, "color": "#ffd84d" },
"threat": { "enabled": false, "size": 8, "pulse": 2.2, "above": 36, "color": "#ff2d6f" },
"offscreen": { "enabled": true, "pad": 28, "size": 13, "max": 4, "color": "#ff2d6f" },
"feed": { "enabled": true, "entries": 5, "life": 3000, "gap": 19, "size": 13, "xInset": 18, "y0": 64, "ink": "#cfe9ff", "kill": "#ffd84d" },
"barFlash": { "life": 340, "alpha": 0.85 }
},
"hud": {
"_comment": "The range console chrome (colors fall back to data/theme.json). title = the corner label; hints = the control line (top-left — the bottom-center is the combat bar's now).",
"title": "COMBAT // SANDBOX",
"hints": "WASD / arrows — fly · space — fire · R — reset range · P — pause"
},
"bar": {
"_comment": "The COMBAT BAR (js/ui/CombatBar.js) — shown in place of the action deck while combat is live (the deck hides, the bar takes its place; when the pack is wiped it's the other way around). geometry = a BOTTOM-ALIGNED, CENTERED strip sized to its contents (assets/bar.png is the storyboard — not a color ref). Left end = the hull ORB (red) with the SHIELD orb (cyan, semi-transparent) overlapping it — shields in front of hull; middle = slot.count skill seats wearing keys 1-8 (the skills land later — for now they are empty seats); right end = the ENERGY orb (yellow, the skills' resource). The orbs fill like a Diablo orb: the liquid drains as the value drops. circle.radius = the hull/energy orb radius; circle.shield.scale = the shield orb's radius as a fraction of the hull radius — the shield orb shares the hull's CENTER (offsetX/offsetY default 0) and its liquid is semi-transparent over the hull's; orbGap = orb-to-slot spacing; margin.edge = the strip's overhang past the orbs. Colors fall back to data/theme.json where sensible.",
"strip": { "height": 64 },
"margin": { "bottom": 26, "edge": 10 },
"orbGap": 24,
"circle": {
"radius": 46,
"hull": { "fillAlpha": 0.95 },
"shield": { "scale": 1.12, "offsetX": 0, "offsetY": 0, "fillAlpha": 0.55 }
},
"colors": {
"bg": "#070d18",
"bgAlpha": 0.92,
"edge": "#22405f",
"shield": "#00e5ff",
"hull": "#ff2d6f",
"energy": "#ffd84d",
"slotBg": "#0b1322",
"slotBorder": "#22405f",
"key": "#5c7196"
},
"slot": { "count": 8, "size": 44, "gap": 10, "keySize": 12 }
}
}