[lane B+C] Three more microbes: the swarm, the driller, and the reward beacon
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 <noreply@anthropic.com>
This commit is contained in:
parent
8706387ff6
commit
fce26abfab
@ -99,6 +99,23 @@ export const BALANCE = {
|
|||||||
// timing a boost, not out-running it.
|
// timing a boost, not out-running it.
|
||||||
latcher: { hp: 16, radius: 1.4, score: 160, speed: 20, turn: 2.6, aggroS: 55,
|
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 },
|
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 },
|
darts: { pool: 48, radius: 0.45 },
|
||||||
|
|||||||
@ -126,6 +126,27 @@ const ARCHETYPES = {
|
|||||||
geo: () => new THREE.TetrahedronGeometry(B.enemies.latcher.radius * 1.3, 0),
|
geo: () => new THREE.TetrahedronGeometry(B.enemies.latcher.radius * 1.3, 0),
|
||||||
color: EMISSIVE.hostile,
|
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 }) {
|
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;
|
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)
|
// 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:
|
// — 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).
|
// 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);
|
onBlast?.(e.pos, 1.2, 0.2);
|
||||||
if ((e.hits = (e.hits || 0) + 1) >= cfg.bursts) { kill(e, 'spent'); continue; }
|
if ((e.hits = (e.hits || 0) + 1) >= cfg.bursts) { kill(e, 'spent'); continue; }
|
||||||
}
|
}
|
||||||
} else if (e.type === 'seeker') {
|
} else if (e.type === 'seeker' || e.type === 'virion') {
|
||||||
// pursue in spline space — a chase for three float subtractions
|
// 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 ds = ps.s - e.s, dx = ps.x - e.x, dy = ps.y - e.y;
|
||||||
const d = Math.hypot(ds, dx, dy) || 1e-5;
|
const d = Math.hypot(ds, dx, dy) || 1e-5;
|
||||||
if (ps.alive && Math.abs(ds) < cfg.aggroS) {
|
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)
|
kill(e, 'contact'); // pepsin dissolves on the hit (GDD)
|
||||||
continue;
|
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') {
|
} else if (e.type === 'turret') {
|
||||||
// wall-mounted; fires only at what's in range and still ahead of it
|
// wall-mounted; fires only at what's in range and still ahead of it
|
||||||
e.cooldown -= dt;
|
e.cooldown -= dt;
|
||||||
@ -506,7 +548,7 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
|
|||||||
let n = 0;
|
let n = 0;
|
||||||
for (const e of pool.slots) {
|
for (const e of pool.slots) {
|
||||||
if (!e.alive) continue;
|
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);
|
_dir.set(e.vs, e.vx, e.vy);
|
||||||
_q.setFromUnitVectors(FORWARD, _dir.lengthSq() > 1e-6 ? _dir.normalize() : FORWARD);
|
_q.setFromUnitVectors(FORWARD, _dir.lengthSq() > 1e-6 ? _dir.normalize() : FORWARD);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -110,6 +110,7 @@
|
|||||||
|
|
||||||
{ "s": 1520, "type": "checkpoint", "name": "Thoracic Esophagus" },
|
{ "s": 1520, "type": "checkpoint", "name": "Thoracic Esophagus" },
|
||||||
{ "s": 1560, "type": "pickup", "kind": "mucin_glob", "count": 2, "spread": 30 },
|
{ "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": 1600, "type": "pickup", "kind": "nutrient_orb", "count": 4, "spread": 60 },
|
||||||
{ "s": 1680, "type": "checkpoint", "name": "Thoracic Run" },
|
{ "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": 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": 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": 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": 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": 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": 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": 2380, "type": "spawn", "enemy": "bolus_chunk", "count": 6, "spread": 140 },
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
* Round-2 microbe pass added `flora` (the first friend), `spore_pod` (a distinct-mesh foe),
|
* 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
|
* `phage` (the first ally — a friendly seeker that hunts foes), and `mucus_grazer` (a commensal
|
||||||
* that trades your coat for faster standing). */
|
* 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:
|
* 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 ' +
|
'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.',
|
'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 --------------------------------------------
|
// ---- passthrough: bare archetype names --------------------------------------------
|
||||||
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user