[lane B+C] Giardia: the latch, and boost becomes a dodge
The first foe with an ATTACH state machine, and the first that gives boost a defensive job. Giardia chases like a seeker, CLAMPS its ventral suction disc onto your hull, and drains coat while it rides — until a boost's i-frames throw it (or it lets go after attachMax, then can't re-grab for reattachCd). The counterplay is timing a boost, not out-running it. - enemies.js: `latcher` archetype — a two-state (chase / attached) branch. Attached, it rides player.pos + a spin offset and drains `drain`/tick; ps.iframeT > 0 (boost) detaches and flings it clear. Amber tetrahedron (the trophozoite), so a clinging one reads on the hull. - levels/enemies.js: ARCHETYPES += latcher; catalogue `giardia_latch` (biome small_intestine). - L2: one Giardia previewed on the open thoracic run (s1940) — it belongs to L4's villi maze, but the grab works in a flow tube, so it teaches the boost-dodge verb now. Verified: it chased + clamped, drained coat 100->84 while attached, and a simulated boost (iframeT) shook it off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b0abbaf76b
commit
b2d9b46c30
@ -91,6 +91,14 @@ export const BALANCE = {
|
||||
// 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 },
|
||||
|
||||
// latcher (Giardia) — a foe that CHASES like a seeker, then CLAMPS onto your hull (its
|
||||
// ventral suction disc) and drains `drain` coat/tick while it rides. The new verb is
|
||||
// "shake it off": a boost's i-frames throw it, else it lets go after `attachMax`s, then
|
||||
// can't re-grab for `reattachCd`s. speed 20 > flow so it catches you; the counterplay is
|
||||
// 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 },
|
||||
},
|
||||
|
||||
darts: { pool: 48, radius: 0.45 },
|
||||
|
||||
@ -87,6 +87,13 @@ const ARCHETYPES = {
|
||||
color: EMISSIVE.acid,
|
||||
foe: false,
|
||||
},
|
||||
// latcher (Giardia) — chases, CLAMPS to your hull, drains coat until a boost shakes it.
|
||||
// Amber; a spiky tetrahedron (the trophozoite) so a clinging one reads on the hull.
|
||||
latcher: {
|
||||
cfg: B.enemies.latcher,
|
||||
geo: () => new THREE.TetrahedronGeometry(B.enemies.latcher.radius * 1.3, 0),
|
||||
color: EMISSIVE.hostile,
|
||||
},
|
||||
};
|
||||
|
||||
export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
||||
@ -293,6 +300,33 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
||||
bus.emit('flora:tended', { s: e.s, gain: cfg.gain }); // gain undefined for flora => default
|
||||
}
|
||||
}
|
||||
} else if (e.type === 'latcher') {
|
||||
// Giardia: chase, CLAMP, drain — until a boost shakes it. Boost becomes a DODGE here.
|
||||
if (e.attached) {
|
||||
e.phase += dt * 3; // spin on the hull
|
||||
const off = e.radius + player.radius;
|
||||
e.s = ps.s; e.x = ps.x + Math.cos(e.phase) * off; e.y = ps.y + Math.sin(e.phase) * off;
|
||||
e.attachT -= dt; e.auraT -= dt;
|
||||
if (ps.alive && e.auraT <= 0) { player.damage(cfg.drain, 'latch'); e.auraT = cfg.drainTick; }
|
||||
if (ps.iframeT > 0 || e.attachT <= 0 || !ps.alive) { // boost i-frames throw it; else it lets go
|
||||
e.attached = false; e.cooldown = cfg.reattachCd;
|
||||
e.vs = -6; e.vx = Math.cos(e.phase) * 8; e.vy = Math.sin(e.phase) * 8; // flung clear
|
||||
}
|
||||
} else {
|
||||
e.cooldown = Math.max(0, (e.cooldown || 0) - dt); // can't re-grab instantly
|
||||
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.s += e.vs * dt; e.x += e.vx * dt; e.y += e.vy * dt;
|
||||
if (ps.alive && e.cooldown <= 0 && d < cfg.radius + player.radius) {
|
||||
e.attached = true; e.attachT = cfg.attachMax; e.auraT = 0; // CLAMP
|
||||
}
|
||||
}
|
||||
} else if (e.type === 'phage') {
|
||||
// The ALLY. A friendly seeker that hunts the nearest FOE (not the player) and rams it —
|
||||
// a bacteriophage bursts its host. No foe in range => it escorts, easing to a standoff
|
||||
|
||||
@ -114,6 +114,7 @@
|
||||
{ "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." },
|
||||
{ "s": 1900, "type": "spawn", "enemy": "bolus_chunk", "count": 3, "spread": 100 },
|
||||
{ "s": 1940, "type": "spawn", "enemy": "giardia_latch", "count": 1, "note": "MICROBE PASS (round 2) preview - Giardia belongs to L4 (villi maze) but its grab works in a flow tube, so one rides here to teach the new verb: it clamps to your hull and drains coat until you BOOST it off. Boost is now a dodge. Placed on the open thoracic run where you have room and a charged boost; C relocates it to L4 when that level lands." },
|
||||
|
||||
{ "s": 2000, "type": "checkpoint", "name": "Bronchial Narrowing" },
|
||||
{ "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." },
|
||||
|
||||
@ -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'];
|
||||
export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage', 'mucus_grazer', 'cdiff', 'latcher'];
|
||||
|
||||
/**
|
||||
* Fields:
|
||||
@ -117,6 +117,13 @@ export const ENEMIES = Object.freeze({
|
||||
'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.',
|
||||
},
|
||||
giardia_latch: {
|
||||
archetype: 'latcher', biome: 'small_intestine', tint: 0xff5a2a, wall: false,
|
||||
note: 'Giardia duodenalis — the "smiley-face" trophozoite that clamps its ventral suction ' +
|
||||
'disc onto the gut wall and carpets it, starving you (malabsorption). In-game it chases, ' +
|
||||
'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.',
|
||||
},
|
||||
|
||||
// ---- passthrough: bare archetype names --------------------------------------------
|
||||
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
||||
|
||||
Loading…
Reference in New Issue
Block a user