From fce26abfab07e7b8c9c7b3f117368b4cf39a99fe Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 20 Jul 2026 00:03:34 +1000 Subject: [PATCH] [lane B+C] Three more microbes: the swarm, the driller, and the reward beacon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roster round-2, batch 2 — three new archetypes, mostly reusing existing behaviour branches: - virion (norovirus): a tiny fast swarmer, its own pool of 40 so a cloud reads as a swarm. Shares the seeker branch (chase + dissolve on contact). The arithmetic threat. -> L5. - driller (H. pylori): a TANKY corkscrewer (hp 50). Seeker chase + a rotating perpendicular drift (the helix), and it does NOT dissolve on contact — it grinds you and keeps drilling, so you must out-damage it. A new behaviour branch. -> L3 stomach; previewed in L2 at s2240. - beacon (Faecalibacterium): the reward-state commensal. Shares the commensal branch, heals AND gives the biggest standing (25) with no coat cost. A bright cyan octahedral gem, the special one of the family. -> L5; previewed in the L2 breather at s1580. Verified in-engine: driller corkscrews + survives contact; virion is tiny + dissolves; beacon heals coat and spikes standing to 50% in two tends. Three clean distinct silhouettes (gem / cone / dot-swarm). Co-Authored-By: Claude Opus 4.8 --- web/js/combat/balance.js | 17 +++++++++++ web/js/combat/enemies.js | 50 ++++++++++++++++++++++++++++++--- web/js/levels/L2_esophagus.json | 2 ++ web/js/levels/enemies.js | 22 ++++++++++++++- 4 files changed, 86 insertions(+), 5 deletions(-) diff --git a/web/js/combat/balance.js b/web/js/combat/balance.js index c480fec..9b03d53 100644 --- a/web/js/combat/balance.js +++ b/web/js/combat/balance.js @@ -99,6 +99,23 @@ export const BALANCE = { // timing a boost, not out-running it. latcher: { hp: 16, radius: 1.4, score: 160, speed: 20, turn: 2.6, aggroS: 55, drain: 8, drainTick: 0.3, attachMax: 3.5, reattachCd: 1.5, pool: 12 }, + + // virion (norovirus) — the ARITHMETIC swarm: tiny, fast, individually trivial (hp 6, one + // pellet), lethal only in numbers. Shares the seeker behaviour (chase + dissolve on contact) + // but its own tiny-radius pool of 40, so a norovirus cloud reads as a swarm, not a squad. + virion: { hp: 6, radius: 0.7, score: 60, speed: 22, turn: 2.8, contact: 8, aggroS: 55, pool: 40 }, + + // driller (H. pylori) — the one microbe that thrives in the stomach. A TANKY corkscrewer: + // chases like a seeker but spirals (spin/corkscrew) so it's hard to pin, and it does NOT + // dissolve on contact — it grinds you and keeps coming. hp 50 = you must commit real fire. + driller: { hp: 50, radius: 1.6, score: 220, speed: 16, turn: 2.0, contact: 18, aggroS: 60, + spin: 4, corkscrew: 6, pool: 8 }, + + // beacon (Faecalibacterium) — the reward-state commensal. The most anti-inflammatory bug in + // you; a screen of these IS a healthy gut. Shares the commensal branch: heals AND gives the + // biggest standing (gain 25) with no coat cost. Rare (pool 10) — the pure payoff node. + beacon: { hp: 22, radius: 2.0, score: 0, drift: 0.9, bob: 0.5, + auraRadius: 4.2, coat: 8, gain: 25, auraTick: 0.3, pool: 10 }, }, darts: { pool: 48, radius: 0.45 }, diff --git a/web/js/combat/enemies.js b/web/js/combat/enemies.js index 6d34875..6ac2224 100644 --- a/web/js/combat/enemies.js +++ b/web/js/combat/enemies.js @@ -126,6 +126,27 @@ const ARCHETYPES = { geo: () => new THREE.TetrahedronGeometry(B.enemies.latcher.radius * 1.3, 0), color: EMISSIVE.hostile, }, + // virion (norovirus) — a tiny fast swarmer, its own pool so a cloud of them reads as a swarm. + // Shares the seeker behaviour (chase + dissolve on contact). Amber. + virion: { + cfg: B.enemies.virion, + geo: () => new THREE.IcosahedronGeometry(B.enemies.virion.radius, 0), + color: EMISSIVE.hostile, + }, + // driller (H. pylori) — a tanky corkscrewing chaser. Amber; a long cone (the spiral drill). + driller: { + cfg: B.enemies.driller, + geo: () => { const g = new THREE.ConeGeometry(B.enemies.driller.radius * 0.6, B.enemies.driller.radius * 2.6, 5); g.rotateX(Math.PI / 2); return g; }, + color: EMISSIVE.hostile, + }, + // beacon (Faecalibacterium) — the reward commensal. Cyan (flora family), a bright octahedral + // gem so it reads as the special one; shares the commensal branch (heal + big standing). + beacon: { + cfg: B.enemies.beacon, + geo: () => new THREE.OctahedronGeometry(B.enemies.beacon.radius, 0), + color: EMISSIVE.neutral, + foe: false, + }, }; export function createEnemies({ scene, world, bus, rng, assets = null }) { @@ -311,7 +332,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) { e.auraT = cfg.auraTick; } } - } else if (e.type === 'flora' || e.type === 'mucus_grazer') { + } else if (e.type === 'flora' || e.type === 'mucus_grazer' || e.type === 'beacon') { // COMMENSALS. Drift like a floater; brushing the aura raises BIOME STANDING (flora:tended) // — that is the reputation you bank. The coat effect is data-driven and sign-flipped: // flora sheds `coat` (a mobile mucin lane) + small standing (default +5). @@ -386,8 +407,9 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) { onBlast?.(e.pos, 1.2, 0.2); if ((e.hits = (e.hits || 0) + 1) >= cfg.bursts) { kill(e, 'spent'); continue; } } - } else if (e.type === 'seeker') { - // pursue in spline space — a chase for three float subtractions + } else if (e.type === 'seeker' || e.type === 'virion') { + // pursue in spline space — a chase for three float subtractions. virion (norovirus) + // shares it exactly: tiny, fast, dissolves on contact — a swarm, not a squad. const ds = ps.s - e.s, dx = ps.x - e.x, dy = ps.y - e.y; const d = Math.hypot(ds, dx, dy) || 1e-5; if (ps.alive && Math.abs(ds) < cfg.aggroS) { @@ -404,6 +426,26 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) { kill(e, 'contact'); // pepsin dissolves on the hit (GDD) continue; } + } else if (e.type === 'driller') { + // H. pylori: a tanky corkscrewing chaser. Seeker pursuit + a rotating perpendicular + // drift (the helix) so it's hard to pin, and — unlike the seeker — it does NOT dissolve + // on contact: it grinds you and keeps drilling. You have to out-damage it. + const ds = ps.s - e.s, dx = ps.x - e.x, dy = ps.y - e.y; + const d = Math.hypot(ds, dx, dy) || 1e-5; + if (ps.alive && Math.abs(ds) < cfg.aggroS) { + const k = Math.min(1, cfg.turn * dt); + e.vs += ((ds / d) * cfg.speed - e.vs) * k; + e.vx += ((dx / d) * cfg.speed - e.vx) * k; + e.vy += ((dy / d) * cfg.speed - e.vy) * k; + } + e.phase += dt * cfg.spin; // corkscrew + e.s += e.vs * dt; + e.x += (e.vx + Math.cos(e.phase) * cfg.corkscrew) * dt; + e.y += (e.vy + Math.sin(e.phase) * cfg.corkscrew) * dt; + if (ps.alive && d < cfg.radius + player.radius) { + e.auraT -= dt; + if (e.auraT <= 0) { player.damage(cfg.contact, 'contact'); e.auraT = 0.25; } // grinds, persists + } } else if (e.type === 'turret') { // wall-mounted; fires only at what's in range and still ahead of it e.cooldown -= dt; @@ -506,7 +548,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) { let n = 0; for (const e of pool.slots) { if (!e.alive) continue; - if (type === 'seeker' || type === 'phage') { // hunters point along their heading + if (type === 'seeker' || type === 'phage' || type === 'virion' || type === 'driller') { // point along heading _dir.set(e.vs, e.vx, e.vy); _q.setFromUnitVectors(FORWARD, _dir.lengthSq() > 1e-6 ? _dir.normalize() : FORWARD); } else { diff --git a/web/js/levels/L2_esophagus.json b/web/js/levels/L2_esophagus.json index 9061ddd..349a815 100644 --- a/web/js/levels/L2_esophagus.json +++ b/web/js/levels/L2_esophagus.json @@ -110,6 +110,7 @@ { "s": 1520, "type": "checkpoint", "name": "Thoracic Esophagus" }, { "s": 1560, "type": "pickup", "kind": "mucin_glob", "count": 2, "spread": 30 }, + { "s": 1580, "type": "spawn", "enemy": "faecali_beacon", "count": 1, "rho": 0.6, "note": "MICROBE PASS (round 2) - the REWARD commensal, previewed in the recovery breather where a health beacon belongs. Faecalibacterium: heals AND gives the biggest BIOME STANDING (25) with no coat cost. The bright cyan gem completes the commensal family (flora rod / akkermansia blob / beacon gem). Belongs to L5; here to be seen." }, { "s": 1600, "type": "pickup", "kind": "nutrient_orb", "count": 4, "spread": 60 }, { "s": 1680, "type": "checkpoint", "name": "Thoracic Run" }, { "s": 1700, "type": "hazard", "kind": "reflux_surge", "speed": 16, "from": -60, "span": 260, "warn": 3.0, "lethal": false, "neutralizable": true, "note": "TEACH reflux - the most important event in the level. A burp: 16 u/s against a 14 u/s current, so even a timid player at throttle 1.15 (16.1 u/s) escapes it. Touching it hurts and does not kill. When the real surge arrives at s=3000 the player already knows the verb." }, @@ -120,6 +121,7 @@ { "s": 2060, "type": "spawn", "enemy": "candida_bloom", "count": 3, "theta": [0.2, 2.4, 4.6], "note": "Three arcs covered in the level's twistiest tube: each is a surprise on a blind bend. This is the beat that makes curviness a mechanic." }, { "s": 2100, "type": "hazard", "kind": "ring_gate", "period": 3.6, "open": 2.4, "warn": 2.5, "note": "TEACH ring gate: open 2.4s of every 3.6s (67% open). At 16 u/s you clear it without thinking - which is the point. You will meet its evil twin at s=2800." }, { "s": 2200, "type": "spawn", "enemy": "eosinophil_swarm", "count": 6, "spread": 80, "note": "Biggest swarm in the level." }, + { "s": 2240, "type": "spawn", "enemy": "hpylori_driller", "count": 1, "note": "MICROBE PASS (round 2) preview - H. pylori, the tanky corkscrewer. Chases in a helix (hard to pin) and does NOT dissolve on contact - it grinds you and keeps coming, so you must commit real fire. Belongs to L3 (stomach); one previewed here on the ramp to teach the 'out-damage it' read." }, { "s": 2280, "type": "checkpoint", "name": "Subcarinal" }, { "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 }, diff --git a/web/js/levels/enemies.js b/web/js/levels/enemies.js index 2606ded..388ae7a 100644 --- a/web/js/levels/enemies.js +++ b/web/js/levels/enemies.js @@ -23,7 +23,7 @@ * Round-2 microbe pass added `flora` (the first friend), `spore_pod` (a distinct-mesh foe), * `phage` (the first ally — a friendly seeker that hunts foes), and `mucus_grazer` (a commensal * that trades your coat for faster standing). */ -export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer', 'cdiff', 'latcher']; +export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer', 'cdiff', 'latcher', 'virion', 'driller', 'beacon']; /** * Fields: @@ -124,6 +124,26 @@ export const ENEMIES = Object.freeze({ 'CLAMPS to your hull, and drains coat until you BOOST it off — the first foe that gives ' + 'boost a defensive job. Belongs to L4 (the villi maze); registered for its encounter pass.', }, + norovirus_swarm: { + archetype: 'virion', biome: 'large_intestine', tint: 0xff6a3a, wall: false, + note: 'Norovirus — the winter-vomiting bug; a handful of virions is an infectious dose. The ' + + 'ARITHMETIC threat: each is trivial (one pellet), but C places them in CLOUDS. Its own ' + + 'tiny-radius pool so a swarm reads as a swarm. Belongs to L5.', + }, + hpylori_driller: { + archetype: 'driller', biome: 'stomach', tint: 0xff5a2a, wall: false, + note: 'Helicobacter pylori — the one microbe that thrives in stomach acid, spiralling into the ' + + 'mucosa with its flagella. In-game a TANKY corkscrewer: it chases in a helix (hard to pin) ' + + 'and does not dissolve on contact — it grinds you and keeps coming. The L3 stomach\'s ' + + 'signature foe; registered for L3\'s encounter pass.', + }, + faecali_beacon: { + archetype: 'beacon', biome: 'large_intestine', tint: 0x39e6ff, wall: false, + note: 'Faecalibacterium prausnitzii — your most anti-inflammatory commensal; its ABSENCE is a ' + + 'disease marker, so a screen full of these IS a healthy gut. The reward-state node: brush ' + + 'it and it heals AND gives the biggest BIOME STANDING (25) with no coat cost. Rare — the ' + + 'pure payoff. A bright cyan gem, the special one of the commensal family.', + }, // ---- passthrough: bare archetype names -------------------------------------------- // Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and