// combat/balance.js (Lane B) — every combat number. Sibling of flight/tuning.js: that file // decides how ENDO-1 handles, this one decides what the fight is worth. // // Sized against the stub esophagus (flow 14 u/s, playable disc radius ~7..10 u) and against // the player's own numbers: disc top speed 13 u/s, coat 100 with 8/s regen after 2.5 s. export const BALANCE = { // Lysozyme cannon: infinite ammo, heat-limited (GDD). Heat is the whole design — the gun // is free, so the cost has to be time. Sustained fire nets +25 heat/s (11 shots/s × 5.5, // less 30/s cooling) => ~4 s of held fire before lockout, then ~2 s to recover to resumeAt. // Burst and you never see the meter; hold and you're disarmed exactly when it's busiest. cannon: { rof: 11, // shots/s speed: 95, // u/s in world space (closing speed ~75 with player flow) life: 1.15, // s => ~109 u range, comfortably past the fog damage: 12, radius: 0.35, heatPerShot: 5.5, coolRate: 30, // /s overheatAt: 100, resumeAt: 40, // hysteresis — no stutter-firing on the edge of lockout spread: 0.004, // rad, seeded — a hair of life, not a handicap pool: 96, // rof × life = ~13 in flight; 96 covers spikes with room }, // Antacid torpedo: the dual-use secondary (GDD). Also neutralizes an acid zone for ~10 s // via the level:neutralize bus event — A/C consume it. Ammo-limited, pickup-fed (round 2). torpedo: { rof: 0.8, speed: 42, life: 3.2, damage: 45, splash: 9, radius: 0.7, ammo: 6, ammoMax: 6, neutralizeRadius: 14, neutralizeDuration: 10, pool: 8, }, enemies: { // amylase droplet — slow floater with a corrosive contact aura. A soft obstacle: it // doesn't chase, it taxes the lane it sits in. Aura ticks discretely (see enemies.js). floater: { hp: 24, radius: 2.2, score: 100, drift: 1.6, bob: 0.5, auraRadius: 4.2, auraDps: 18, auraTick: 0.2, pool: 24 }, // pepsin — small fast seeker, dissolves on contact. Chases in spline space (s,x,y), so // a pursuit costs three floats. speed 19 > player flow 14: it WILL catch you from // behind, and the answer is to shoot it or boost, not to outrun it at throttle 1.4. seeker: { hp: 14, radius: 1.0, score: 150, speed: 19, turn: 2.4, contact: 16, aggroS: 60, pool: 32 }, // antibody turret (Peyer's patch) — wall-mounted, fires homing darts. Dart turn radius // is speed/turn = 24/1.8 = 13.3 u, wider than the tube itself: it can never corner you, // only punish a straight line. Dodgeable by design (charter). turret: { hp: 40, radius: 1.6, score: 250, fireEvery: 1.9, rangeS: 55, dartSpeed: 24, dartTurn: 1.8, dartLife: 4.5, dartDamage: 14, pool: 12 }, // flora — the first FRIEND. A commensal (Lactobacillus &c) that drifts like a floater but // HELPS: brush its aura and it sheds `coat` mucus per tick instead of taking it. score 0 and // no combo (enemies.js kill()): it sits in the enemy pool so it is shootable, but shooting // it is the reputation trap, never a reward. `coat` kept small + no parking in a flow tube = // a trickle, not a fountain. hp low: a stray pellet shouldn't wipe a colony you meant to spare. // No `gain` => flora:tended defaults to the small +5 standing in combat/index.js. flora: { hp: 18, radius: 2.0, score: 0, drift: 1.2, bob: 0.4, auraRadius: 3.6, coat: 6, auraTick: 0.3, pool: 24 }, // mucus_grazer (Akkermansia) — a commensal that eats your mucin. Brushing it COSTS `coatCost` // per tick but signals gut health hardest (`gain` 12 vs flora's 5): the "spend coat for // faster allies" trade. Shares flora's drift+aura branch, sign-flipped by data. score 0, // shootable (harming it is still flora:harmed). The clever risk/reward of the reputation loop. mucus_grazer: { hp: 20, radius: 2.2, score: 0, drift: 1.0, bob: 0.35, auraRadius: 4.0, coatCost: 4, gain: 12, auraTick: 0.3, pool: 16 }, // spore_pod — a foe budding-yeast/Candida cluster. Mechanically the floater (drift + corrosive // aura) with its OWN pool so its blobby mesh reads distinct from tumbling food debris. The // first entity authored to be skinned by a Trellis mesh (assets/models/spore_pod.glb). spore_pod: { hp: 30, radius: 2.4, score: 120, drift: 1.4, bob: 0.5, auraRadius: 4.4, auraDps: 20, auraTick: 0.2, pool: 16 }, // phage — the ALLY (bacteriophage). A friendly seeker that hunts the nearest FOE and rams // it (`punch`), bursting after `bursts` hits (a phage lyses its host and dies). FAST (26 > // every foe) so it actually catches things; FRAGILE (hp 8) so a stray pellet or a foe pops // it. score 0 — an ally never scores. Budded by high BIOME STANDING (combat/index.js), the // payoff for tending flora. escort* = where it loiters when no foe is in `huntS` range. phage: { hp: 8, radius: 1.1, score: 0, speed: 26, turn: 3.2, punch: 20, bursts: 2, huntS: 70, escortLead: 10, escortR: 5, pool: 8 }, }, darts: { pool: 48, radius: 0.45 }, // --- hazards (round 2) ------------------------------------------------------------- // C authors WHERE and HOW MUCH in the level JSON (speed/period/amplitude/span/warn); these // are the mechanical constants those params drive. C's params always win where they overlap. hazards: { // reflux_surge — the rear chaser. C's finale: speed 21 vs flow 20, spawning 40 u behind // and chasing 600 u. Lethal on contact by C's authoring (`lethal: true`); the s=1700 // teaching burp is `lethal: false` and only grazes. Antacid stalls it (`neutralizable`). surge: { graze: 26, // coat damage/tick for the NON-lethal teaching version grazeTick: 0.35, // s between graze ticks while inside it stallFactor: 0.0, // speed multiplier while neutralised — 0 = fully parked. // C's design leans on this: "fired backward it stalls the surge // ~10 s" (their finale maths assumes a real stop, not a slow). proximityRange: 220, // u within which we emit hazard:proximity (E's rear indicator) catchRadius: 3.0, // u of s-overlap that counts as being caught }, // aortic_squeeze — DIRECTIONAL by design (C): the arch presses from `theta` only and the // opposite arc is the answer. Non-lethal, and C's teach-then-test ledger depends on that // staying true (LANE_C_NOTES: "if B makes squeeze contact lethal, this breaks — tell me"). squeeze: { graze: 14, // coat damage per contact tick grazeTick: 0.3, shove: 9, // u/s pushed off the bulging wall — the hazard MOVES you arc: 1.9, // rad of the cross-section the bulge occupies (~110°, one side) }, // ring_gate — a POINT, therefore timeable: the player modulates throttle to arrive on an // open beat. Closed contact is expensive but never lethal (C: it is a skill check, and // a flow-locked player cannot stop to wait). gate: { hit: 30, // coat damage for arriving on a closed beat shove: 6, // u/s shove toward the centreline (the iris squeezes you through) irisMin: 0.12, // fraction of the lumen still open when fully "sealed" — never a // true wall: a flow-locked player physically cannot stop, so a // real seal would be an unavoidable toll rather than a skill check }, }, // --- pickups (round 2) ------------------------------------------------------------- // C owns placement + counts (level JSON); B owns what each one is WORTH. C's round-2 task // is to hand over an economy — until it lands these are B's proposal, documented in NOTES. pickups: { radius: 1.5, // generous collect radius: pickups are a reward, not a dexterity test spin: 1.4, // rad/s idle rotation nutrient_orb: { score: 50, boost: 0.6, note: 'score + boost recharge (shaves cooldown)' }, mucin_glob: { coat: 35 }, b12_cell: { hull: 30 }, antacid_ammo: { ammo: 1 }, biopsy_sample: { score: 500, sample: 1 }, }, // Chained kills feed the combo meter (GDD: score × style multiplier; E's music layer // listens). Window is generous enough to cross a fold, tight enough to mean something. combo: { window: 2.5 }, };