diff --git a/docs/TECH.md b/docs/TECH.md index e4e9a35..89a44ea 100644 --- a/docs/TECH.md +++ b/docs/TECH.md @@ -184,6 +184,9 @@ Ratified round 2 (round-1 requests): - `standing {value}` — BIOME STANDING 0..100 (combat/index.js owns it). Tending flora raises it; harming flora drops it; crossing the threshold buds phage allies. E can meter it. ("Don't shoot the biome, it helps you" — V2_IDEAS, now partly real.) +- `biofilm {opened, s}` — a `biofilm` level event was crossed (the reputation cash-out). `opened` + = standing was high enough, so the biome parted (allies budded + coat refilled, standing spent); + false = it stayed sealed. E can voice both. (New event type `biofilm`, fields `need/allies/coat`.) ## Asset manifest contract (D owns `assets.js` + `manifest.json`) diff --git a/web/js/combat/index.js b/web/js/combat/index.js index 7ca3096..d26699c 100644 --- a/web/js/combat/index.js +++ b/web/js/combat/index.js @@ -56,6 +56,8 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null } else if (ev.type === 'pickup') { for (let i = 0; i < (ev.count ?? 1); i++) pickups.spawn(ev.kind, { s: fan(ev, i), theta: thetaFor(ev, i), rho: ev.rho ?? null }); + } else if (ev.type === 'biofilm') { + biofilmGate(ev); } }); @@ -93,6 +95,22 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null } } + // biofilm gate — the reputation CASH-OUT (V2_IDEAS: "it helps you in level 5"). Cross one with + // high BIOME STANDING and the native flora part the biofilm: a wave of phage allies buds and + // your coat is refilled, then the goodwill is spent. Low standing => it stays sealed and you run + // the gauntlet raw. Placed as a `biofilm` level event (`need`/`allies`/`coat` optional). Emits + // `biofilm {opened}` so E can voice it. The L5 payoff, built as a mechanism usable anywhere. + function biofilmGate(ev) { + const opened = standing >= (ev.need ?? 60); + if (opened) { + for (let i = 0; i < (ev.allies ?? 3); i++) enemies.spawn('phage', { s: player.state.s + 6 + i * 4 }); + player.refill({ coat: ev.coat ?? 40 }); + setStanding(20); // spent the goodwill + } + bus.emit('biofilm', { opened, s: ev.s }); + bus.emit('audio:cue', { name: opened ? 'gate_pass' : 'gate_hit' }); + } + function update(dt) { if (phageBudCd > 0) phageBudCd -= dt; enemies.update(dt, player); diff --git a/web/js/levels/L2_esophagus.json b/web/js/levels/L2_esophagus.json index 349a815..40360d9 100644 --- a/web/js/levels/L2_esophagus.json +++ b/web/js/levels/L2_esophagus.json @@ -126,6 +126,7 @@ { "s": 2300, "type": "pickup", "kind": "antacid_ammo", "count": 2, "spread": 20, "note": "Arms the player 700 units before the reflux finale. Antacid neutralises an acid zone (GDD) - fired BACKWARD it stalls the surge. The level hands you the tool, never explains it, and rewards you for noticing." }, { "s": 2380, "type": "spawn", "enemy": "bolus_chunk", "count": 6, "spread": 140 }, { "s": 2420, "type": "spawn", "enemy": "candida_bloom", "count": 2, "theta": [1.8, 5.1], "note": "Blooms + debris simultaneously: the level's densest sustained stretch." }, + { "s": 2500, "type": "biofilm", "need": 60, "allies": 3, "coat": 40, "note": "MICROBE PASS (round 2) - the reputation CASH-OUT (the L5 payoff, demoed here). If you tended the opening flora/akkermansia/beacon and kept BIOME STANDING high, the biofilm parts: 3 phage allies bud and your coat refills, right before the hiatus + reflux finale. Ignore the biome and it stays sealed - you run the finale raw. 'Don't shoot the biome, it helps you.'" }, { "s": 2540, "type": "pickup", "kind": "mucin_glob", "count": 1 }, { "s": 2580, "type": "checkpoint", "name": "Diaphragmatic Hiatus" }, diff --git a/web/js/levels/index.js b/web/js/levels/index.js index b4c6504..92d978b 100644 --- a/web/js/levels/index.js +++ b/web/js/levels/index.js @@ -526,6 +526,10 @@ export async function validate(level, { expectId = null } = {}) { if (e.warn != null && e.warn < 2 && e.lethal !== false) W(`${at}: warn ${e.warn}s is under the 2s telegraph law for lethal hazards`); break; } + case 'biofilm': + // reputation cash-out (combat/index.js biofilmGate). Optional need (0..100)/allies/coat. + if (e.need != null && (e.need < 0 || e.need > 100)) E(`${at}: biofilm need ${e.need} out of 0..100`); + break; case 'checkpoint': case 'boss': case 'gate': break; default: W(`${at}: unknown event type "${e.type}" — B/E will ignore it`); }