[lane B+C] C. difficile: the kill that isn't clean

A foe whose corpse keeps hurting — the definitive colon enemy. C. diff drifts with a corrosive
aura like any floater, but on death it leaves a SPORE_CLOUD: a static acid-green zone that
damages anything in it for ~5s, then disperses. Killing it costs you the ground it died on
(shoot the cloud to clear it, or fly around).

- enemies.js: two archetypes. `cdiff` (amber octahedron, shares the floater drift+aura branch)
  drops a `spore_cloud` in kill(), placed at the death spot. `spore_cloud` (acid-green per the
  readability law, foe:false so the phage ignores it and clearing it never scores) is static,
  damages on contact, and counts down a `life`. Entities gained a `life` field (only the cloud
  uses it).
- levels/enemies.js: ARCHETYPES += cdiff; catalogue `cdiff_bloom` (biome large_intestine —
  registered for L5's encounter pass, not shoehorned into the esophagus).

Verified: killing a cdiff dropped a cloud at its death spot; sitting in it dropped coat 100->89;
the cloud expired after its life. Amber octahedron vs acid-green blob — clean read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-19 20:38:15 +10:00
parent aae1ebdcfd
commit b0abbaf76b
3 changed files with 58 additions and 5 deletions

View File

@ -79,6 +79,18 @@ export const BALANCE = {
// 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 },
// cdiff (C. difficile) — a foe floater (drift + corrosive aura) whose corpse ISN'T clean:
// on death it leaves a spore_cloud (enemies.js kill()). Killing it costs you the ground it
// died on. A colon (L5) enemy; registered now, placed when L5 gets its encounter pass.
cdiff: { hp: 26, radius: 2.3, score: 140, drift: 1.2, bob: 0.45,
auraRadius: 4.0, auraDps: 14, auraTick: 0.2, pool: 12 },
// spore_cloud — C. diff's parting gift. A STATIC corrosive zone (acid-green, readability law)
// that damages on contact and expires after `life`s. Not scored (foe:false), but shootable:
// low hp so you CAN clear it with the cannon — spend heat to disperse it, or fly around.
spore_cloud: { hp: 14, radius: 3.2, score: 0, auraRadius: 4.5, auraDps: 22, auraTick: 0.25,
life: 5, pool: 12 },
},
darts: { pool: 48, radius: 0.45 },

View File

@ -72,6 +72,21 @@ const ARCHETYPES = {
color: EMISSIVE.neutral,
foe: false,
},
// cdiff (C. difficile) — a foe floater whose corpse leaves a spore_cloud (kill()). Amber,
// an angular octahedron so it reads distinct from the round yeast cluster.
cdiff: {
cfg: B.enemies.cdiff,
geo: () => new THREE.OctahedronGeometry(B.enemies.cdiff.radius, 0),
color: EMISSIVE.hostile,
},
// spore_cloud — the lingering corrosive zone C. diff drops on death. Acid-green (readability
// law: acid/corrosive), foe:false so the phage ignores it and clearing it never scores.
spore_cloud: {
cfg: B.enemies.spore_cloud,
geo: () => new THREE.IcosahedronGeometry(B.enemies.spore_cloud.radius, 1),
color: EMISSIVE.acid,
foe: false,
},
};
export function createEnemies({ scene, world, bus, rng, assets = null }) {
@ -148,7 +163,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
hp: cfg.hp, radius: cfg.radius, alive: true,
pos: new THREE.Vector3(), flash: 0, phase: rand() * Math.PI * 2,
cooldown: rand() * (cfg.fireEvery ?? 1), // desync turret volleys
auraT: 0,
auraT: 0, life: cfg.life ?? 0, // life: only spore_cloud counts down
};
live.push(e);
pool.slots.push(e);
@ -178,6 +193,10 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
onDeath?.(e);
return;
}
if (e.type === 'cdiff') { // the corpse keeps hurting: leave a spore cloud
const sp = spawn('spore_cloud', { s: e.s });
if (sp) { sp.x = e.x; sp.y = e.y; } // at the death spot, not a random arc
}
comboN++; comboT = B.combo.window;
bus.emit('enemy:die', { id: e.id, type: e.type, s: e.s, score: e.cfg.score, kind });
bus.emit('combo', { n: comboN });
@ -221,9 +240,23 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
e.flash = Math.max(0, e.flash - dt * 6);
const cfg = e.cfg;
if (e.type === 'floater' || e.type === 'spore_pod') {
// drifts and bobs; taxes the lane it occupies rather than chasing. spore_pod shares
// this behaviour exactly — it's a floater with its own pool/mesh (a yeast cluster).
if (e.type === 'spore_cloud') {
// A lingering corrosive spore cloud (C. diff's parting gift). Static — it does NOT move;
// it just damages on contact and expires after `life`. Killing the C. diff isn't clean.
e.life -= dt;
if (e.life <= 0) { kill(e, 'expire'); continue; }
if (ps.alive) {
const d = Math.hypot(e.s - ps.s, e.x - ps.x, e.y - ps.y);
e.auraT -= dt;
if (d < cfg.auraRadius + player.radius && e.auraT <= 0) {
player.damage(cfg.auraDps * cfg.auraTick, 'spore');
e.auraT = cfg.auraTick;
}
}
} else if (e.type === 'floater' || e.type === 'spore_pod' || e.type === 'cdiff') {
// drifts and bobs; taxes the lane it occupies rather than chasing. spore_pod (yeast) and
// cdiff (C. difficile) share this exactly — floaters with their own pool/mesh. cdiff's
// twist is only at death (kill() drops a spore_cloud), never in how it moves.
e.phase += dt * cfg.bob;
e.s += Math.sin(e.phase) * cfg.drift * dt;
e.x += Math.cos(e.phase * 0.7) * cfg.drift * dt;

View File

@ -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'];
export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer', 'cdiff'];
/**
* Fields:
@ -109,6 +109,14 @@ export const ENEMIES = Object.freeze({
'allies quicker. Cyan (a commensal), amoeba-blob shape so the coat cost reads as a ' +
'different bug, not a betrayal by the healing rod.',
},
cdiff_bloom: {
archetype: 'cdiff', biome: 'large_intestine', tint: 0xff7a2a, wall: false,
note: 'Clostridioides difficile — waits for antibiotics to clear the neighbours, then blooms ' +
'and paints the colon in pseudomembrane, leaving armoured SPORES. In-game a floater ' +
'with a corrosive aura whose corpse ISN\'T clean: killing it drops a spore_cloud that ' +
'keeps hurting the ground it died on (shoot it to disperse, or fly around). The colon\'s ' +
'definitive foe; belongs to L5, registered so C can place it when L5 gets its pass.',
},
// ---- passthrough: bare archetype names --------------------------------------------
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and