[lane B+C] Akkermansia: spend coat to summon allies sooner

The cleverest node in the reputation loop — a commensal that eats your mucus. Brushing it
COSTS coat but fills BIOME STANDING fast (gain 12 vs flora's 5): the "spend coat for a quicker
phage ally" trade against flora's safe heal-and-slow.

- enemies.js: flora's branch generalised to a shared COMMENSAL branch, sign-flipped by data
  (cfg.coat gifts, cfg.coatCost nibbles); flora:tended now carries `gain`. New `mucus_grazer`
  archetype — cyan (still flora, readability law), but a dodecahedron amoeba-blob so the coat
  cost reads as a different bug, not a betrayal by the healing rod.
- combat/index.js: standing gain comes from the event's `gain`.
- levels: ARCHETYPES += mucus_grazer; catalogue `akkermansia`; L2 teaches it at s210, right
  after the flora reef so the same-colour / opposite-trade contrast lands.

Verified: brushing it dropped coat 100->88 while standing hit 36% in 3 tends (4x flora's rate);
distinct blob vs the flora capsule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-19 20:30:17 +10:00
parent 4bcb942312
commit aae1ebdcfd
5 changed files with 39 additions and 9 deletions

View File

@ -55,9 +55,17 @@ export const BALANCE = {
// no combo (enemies.js kill()): it sits in the enemy pool so it is shootable, but shooting
// it is the reputation trap, never a reward. `coat` kept small + no parking in a flow tube =
// a trickle, not a fountain. hp low: a stray pellet shouldn't wipe a colony you meant to spare.
// No `gain` => flora:tended defaults to the small +5 standing in combat/index.js.
flora: { hp: 18, radius: 2.0, score: 0, drift: 1.2, bob: 0.4,
auraRadius: 3.6, coat: 6, auraTick: 0.3, pool: 24 },
// mucus_grazer (Akkermansia) — a commensal that eats your mucin. Brushing it COSTS `coatCost`
// per tick but signals gut health hardest (`gain` 12 vs flora's 5): the "spend coat for
// faster allies" trade. Shares flora's drift+aura branch, sign-flipped by data. score 0,
// shootable (harming it is still flora:harmed). The clever risk/reward of the reputation loop.
mucus_grazer: { hp: 20, radius: 2.2, score: 0, drift: 1.0, bob: 0.35,
auraRadius: 4.0, coatCost: 4, gain: 12, auraTick: 0.3, pool: 16 },
// spore_pod — a foe budding-yeast/Candida cluster. Mechanically the floater (drift + corrosive
// aura) with its OWN pool so its blobby mesh reads distinct from tumbling food debris. The
// first entity authored to be skinned by a Trellis mesh (assets/models/spore_pod.glb).

View File

@ -63,6 +63,15 @@ const ARCHETYPES = {
color: EMISSIVE.ally,
foe: false,
},
// mucus_grazer (Akkermansia) — a commensal that NIBBLES your coat (it eats mucin) but signals
// gut health hardest. Cyan (still flora, readability law), but a distinct amoeba-blob silhouette
// so the coat cost reads as "a different bug", not a betrayal by the healing rod.
mucus_grazer: {
cfg: B.enemies.mucus_grazer,
geo: () => new THREE.DodecahedronGeometry(B.enemies.mucus_grazer.radius, 0),
color: EMISSIVE.neutral,
foe: false,
},
};
export function createEnemies({ scene, world, bus, rng, assets = null }) {
@ -230,10 +239,12 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
e.auraT = cfg.auraTick;
}
}
} else if (e.type === 'flora') {
// The FRIEND. Drifts like a floater but its aura HEALS: brush past and it sheds a
// little mucus coat (the "mobile mucin lane"). Never damages you. It's still in the
// enemy pool, so you CAN shoot it — and that is the reputation trap, handled in kill().
} else if (e.type === 'flora' || e.type === 'mucus_grazer') {
// 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).
// mucus_grazer nibbles `coatCost` (Akkermansia eats mucin) + big standing (`gain`).
// Neither ever "attacks" you; both stay shootable, which is the reputation trap (kill()).
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;
@ -243,9 +254,10 @@ export function createEnemies({ scene, world, bus, rng, assets = null }) {
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.refill({ coat: cfg.coat }); // friendly: gives coat instead of taking it
if (cfg.coat) player.refill({ coat: cfg.coat });
if (cfg.coatCost) player.damage(cfg.coatCost, 'graze');
e.auraT = cfg.auraTick;
bus.emit('flora:tended', { s: e.s }); // for the (future) BIOME STANDING meter
bus.emit('flora:tended', { s: e.s, gain: cfg.gain }); // gain undefined for flora => default
}
}
} else if (e.type === 'phage') {

View File

@ -81,7 +81,7 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null
// first pass — C/playtest tunes the feel.
let standing = 0, phageBudCd = 0;
const setStanding = (v) => { standing = Math.max(0, Math.min(100, v)); bus.emit('standing', { value: standing }); };
const offTended = bus.on('flora:tended', () => { setStanding(standing + 5); budPhage(); });
const offTended = bus.on('flora:tended', (e) => { setStanding(standing + (e?.gain ?? 5)); budPhage(); });
const offHarmed = bus.on('flora:harmed', () => setStanding(standing - 35));
function budPhage() {
if (standing < 100 || phageBudCd > 0) return; // a FULL bar buds an ally

View File

@ -86,6 +86,7 @@
{ "s": 20, "type": "checkpoint", "name": "Upper Esophageal Sphincter" },
{ "s": 120, "type": "pickup", "kind": "nutrient_orb", "count": 3, "spread": 40, "note": "Orb lines teach the racing line before they score anything." },
{ "s": 180, "type": "spawn", "enemy": "lacto_drifter", "count": 4, "spread": 90, "rho": 0.7, "note": "MICROBE PASS (round 2) - the first FRIEND, taught first, in the calmest stretch. A Lactobacillus reef drifting off the racing line (cyan = friendly, ART_BIBLE). Brush it and it sheds mucus coat; SHOOT it and you have harmed the biome (flora:harmed) - the seed of the BIOME STANDING loop. Teaches 'don't shoot cyan' before any level charges for it." },
{ "s": 210, "type": "spawn", "enemy": "akkermansia", "count": 2, "spread": 30, "rho": 0.5, "note": "MICROBE PASS (round 2) - the CHOICE node, taught right after flora so the contrast lands: same cyan family, opposite trade. Brushing Akkermansia nibbles a little coat but fills BIOME STANDING fast (gain 12) - spend coat to summon a phage ally sooner. Near the racing line so the choice is unavoidable." },
{ "s": 240, "type": "spawn", "enemy": "bolus_chunk", "count": 1, "note": "TEACH bolus: one chunk, slow, unmissable, harmless if you simply steer." },
{ "s": 300, "type": "spawn", "enemy": "yeast_pod", "count": 1, "rho": 0.6, "note": "MICROBE PASS (round 2) - the spore_pod foe's teach beat: one drifting Candida cluster, off-line, harmless if you steer. A distinct blobby silhouette (Trellis-skinned), not the food-debris floater." },
{ "s": 340, "type": "pickup", "kind": "nutrient_orb", "count": 3, "spread": 40 },

View File

@ -21,8 +21,9 @@
/** The archetypes Lane B implements. Selfcheck enforces membership.
* Round-2 microbe pass added `flora` (the first friend), `spore_pod` (a distinct-mesh foe),
* and `phage` (the first ally a friendly seeker that hunts foes). */
export const ARCHETYPES = ['floater', 'seeker', 'turret', 'flora', 'spore_pod', 'phage'];
* `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'];
/**
* Fields:
@ -100,6 +101,14 @@ export const ENEMIES = Object.freeze({
'(teal, not the cyan "don\'t shoot" flora). Budded by high BIOME STANDING — the payoff ' +
'for tending flora instead of spraying it. Placeable by C, but mainly biome-spawned.',
},
akkermansia: {
archetype: 'mucus_grazer', biome: '*', tint: 0x39e6ff, wall: false,
note: 'Akkermansia muciniphila — lives in your mucus layer and EATS it, yet is a marker of ' +
'a lean healthy gut. The cleverest node in the reputation loop: brushing it COSTS a ' +
'little coat but fills BIOME STANDING fast (gain 12 vs flora\'s 5) — spend coat to earn ' +
'allies quicker. Cyan (a commensal), amoeba-blob shape so the coat cost reads as a ' +
'different bug, not a betrayal by the healing rod.',
},
// ---- passthrough: bare archetype names --------------------------------------------
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and