1037 lines
50 KiB
JSON
1037 lines
50 KiB
JSON
{
|
||
"_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 }
|
||
}
|
||
}
|