The last skeleton. L3 has been 4 events since round 1, waiting (in its own status field) on
"the pH field" and a boss framework; both exist now, so the level is authored: 46 events,
2500 units, the sea, and the Guardian at the end of it.
THE PH MECHANIC, finally load-bearing. Lane A built an acid plane in round 2 and left two
hooks on it — depthAt() labelled "B:" and set() labelled "C's event pump drives this" — and
nothing had ever called either. Now:
- player.js scales the biome's ambient coat drain by SUBMERSION. Stomach 2.2/s x5 = 11/s
under the surface (measured: 8/s net of regen, and regen is off for 2.5s after any hit,
so it is brutal mid-fight); x0.15 above it, which is effectively safe air.
- a new `acid` level event names a target height and boot eases the sea toward it in step(),
so a RISING TIDE is a beat C can author. L3 uses two: -14 -> -6 mid-sea, then -6 -> +2 in
the antrum, where the surface goes ABOVE the centreline and the safe lane simply stops
existing. Measured: the identical spot (y -5) goes from 0/s to 9.67/s across that tide.
This is the GDD's "mucus shallows are safe lanes" expressed as DEPTH rather than wall-hugging,
which is better on two counts: it reads instantly (you can SEE the surface) and it makes the
vertical axis matter in the one level that is a room. Every collectible worth having is under it.
Gated on the sea's s-span, not on depthAt alone — depthAt returns a bare -1 outside the span
and that is indistinguishable from "1 unit above the surface", so reading it naively would have
quietly scaled ambient drain on every level in the game. Caught before it shipped.
Level: cardia (still teal, so the fundus can REVEAL the sea) -> fundus (the teaching room:
huge, low tide, nothing kills you fast) -> body (the sea proper, tide rises while you are in
it) -> antrum (no safe air left, only speed) -> pyloric antrum (the Guardian). H. pylori is
taught alone in the dome and tested in a rising sea; pepsin swarms scale 3 -> 6; bezoar_chunk
is a new catalogue row for the stomach's traffic.
L3 has NO commensals and no biofilm gate, which is anatomically exact — acid sterilises, which
is why H. pylori is famous for surviving it. So BIOME STANDING goes quiet for nine minutes and
comes back in L4, which makes L4's colonies read as relief rather than furniture.
C's sim: par 540s vs par-pace 555s ("requires pushing above par-pace"), pressure
3.75/1.50/2.44/2.16, min clearance 7.15 vs the 2.5 law, all 21 checkpoint gaps under 30s.
par.score corrected 9000 -> 5500, because 9000 was above everything the level can actually pay.
Deferred, and said out loud rather than quietly dropped: churn cyclones. A vortex that flings
the player is a FORCE and no hazard in the tree applies one; it wants the same A/B work as the
Guardian's arena churn and should land with it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
208 lines
14 KiB
JavaScript
208 lines
14 KiB
JavaScript
// levels/enemies.js (Lane C) — the enemy CATALOGUE: fiction id → archetype + skin.
|
|
//
|
|
// WHY THIS EXISTS: level JSONs name enemies in fiction ("bolus_chunk"), not in mechanics
|
|
// ("floater"). This is the join table between the two, and nothing more.
|
|
//
|
|
// OWNERSHIP (settled with Lane B's round-1 work, see LANE_C_NOTES.md §→ Lane B):
|
|
// Lane B owns MECHANICS — hp, radius, speed, damage, fire rate, score. They live in
|
|
// combat/balance.js, tuned against B's cannon and B's feel numbers, and they are keyed by
|
|
// ARCHETYPE. This file deliberately does NOT restate them: an hp of "2 shots" here and an
|
|
// hp of 24 there (against damage 12) is the same number twice, which is one number too
|
|
// many. If you want to know what a bolus chunk can take, ask balance.js.
|
|
// Lane C owns COMPOSITION — which fiction appears, how many, where, in what order, and
|
|
// what it is called. That is this file plus the level JSONs.
|
|
//
|
|
// LANE B: resolve a spawn with `getEnemy(ev.enemy)?.archetype` — one line, snippet in
|
|
// LANE_C_NOTES.md §→ Lane B. The three bare archetype names also resolve (passthrough
|
|
// entries at the bottom), so the round-0 stub level and anything already built against it
|
|
// keep working unchanged.
|
|
//
|
|
// Pure data + pure functions: no imports, no THREE. Must stay node-runnable (qa selfcheck).
|
|
|
|
/** The archetypes Lane B implements. Selfcheck enforces membership.
|
|
* 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', 'virion', 'driller', 'beacon'];
|
|
|
|
/**
|
|
* Fields:
|
|
* archetype 'floater'|'seeker'|'turret' — which of B's behaviors + balance rows drives it
|
|
* biome where it belongs (documentation; '*' = anywhere)
|
|
* tint emissive color. ART_BIBLE law: hostile = hot amber/red, projectiles =
|
|
* white-hot, neutral flora = cyan (turns hostile if shot). Advisory for Lane D.
|
|
* wall true => C must author an explicit `theta`. Lane B already defaults turrets to
|
|
* the wall; this is an AUTHORING rule, not a runtime one — a wall-mounted enemy
|
|
* owns an arc of the cross-section, and which arc is a design decision, never
|
|
* a random one.
|
|
* note the fiction, and why it earns its place in the encounter.
|
|
*/
|
|
export const ENEMIES = Object.freeze({
|
|
// ---- L2 esophagus roster (all three are real esophageal pathology) ----------------
|
|
bolus_chunk: {
|
|
archetype: 'floater', biome: 'esophagus', tint: 0xd98a3a, wall: false,
|
|
note: 'Swallowed food debris tumbling in the swallow. A floater taxes the lane it sits ' +
|
|
'in rather than chasing you, which is exactly what debris should do: it turns the ' +
|
|
'cross-section into a maze without ever raising the stakes. The L2 teaching enemy.',
|
|
},
|
|
eosinophil_swarm: {
|
|
archetype: 'seeker', biome: 'esophagus', tint: 0xff4d3d, wall: false,
|
|
note: 'Eosinophilic esophagitis, weaponised — immune cells that mistake ENDO-1 for the ' +
|
|
'parasite they were built for. Individually trivial; the threat is arithmetic. ' +
|
|
'B tuned the seeker at speed 19 against flow 14 specifically so it catches you ' +
|
|
'from behind: the answer is to shoot or boost, never to outrun. That is the ' +
|
|
'lesson L2 needs before the reflux finale asks you to outrun something.',
|
|
},
|
|
candida_bloom: {
|
|
archetype: 'turret', biome: 'esophagus', tint: 0xffd166, wall: true,
|
|
note: 'Candida esophagitis — white plaque colonies anchored to the mucosa, firing spore ' +
|
|
'clusters. Wall-mounted, so it OWNS an arc of the tube and converts "which side do ' +
|
|
'I fly" into a real decision. B\'s dart turn radius (13.3u) is wider than the tube, ' +
|
|
'so it can never corner you, only punish a straight line — which is why L2 puts ' +
|
|
'blooms on straights and in the squeeze, never on blind bends alone.',
|
|
},
|
|
|
|
// ---- L1 oral cavity (roster stub — L1's encounter pass is round 2+) ---------------
|
|
amylase_droplet: {
|
|
archetype: 'floater', biome: 'oral', tint: 0xe8a13a, wall: false,
|
|
note: 'Saliva enzyme blob with a corrosive contact aura. The tutorial floater — B tuned ' +
|
|
'the floater archetype against exactly this fiction (see balance.js).',
|
|
},
|
|
|
|
// ---- L3 stomach (roster stub — L3's encounter pass is round 2+) -------------------
|
|
pepsin_swarm: {
|
|
archetype: 'seeker', biome: 'stomach', tint: 0xff3b30, wall: false,
|
|
note: 'The seeker in its home biome — B tuned the archetype against this fiction.',
|
|
},
|
|
peyers_turret: {
|
|
archetype: 'turret', biome: 'small_intestine', tint: 0xffe066, wall: true,
|
|
note: 'Antibody turret in a Peyer\'s patch — B tuned the turret archetype against this ' +
|
|
'fiction. Belongs to L4; listed so the catalogue matches balance.js\'s vocabulary.',
|
|
},
|
|
|
|
// ---- round-2 microbe pass: the first FRIEND, and a distinct-mesh foe ---------------
|
|
lacto_drifter: {
|
|
archetype: 'flora', biome: 'esophagus', tint: 0x39e6ff, wall: false,
|
|
note: 'Lactobacillus — a friendly commensal that acidifies the neighbourhood and out-' +
|
|
'competes pathogens. Cyan (ART_BIBLE: flora reads friendly). Brush its aura and it ' +
|
|
'sheds mucus coat; SHOOT it and you have harmed the biome (flora:harmed) — the seed ' +
|
|
'of the BIOME STANDING reputation loop. The first entity that rewards NOT firing.',
|
|
},
|
|
yeast_pod: {
|
|
archetype: 'spore_pod', biome: 'esophagus', tint: 0xffb347, wall: false,
|
|
note: 'Candida budding-yeast cluster — a drifting colony with a corrosive contact aura. ' +
|
|
'Mechanically a floater, but its own archetype so it carries a distinct blobby ' +
|
|
'silhouette: the first foe authored to be skinned by a Trellis mesh.',
|
|
},
|
|
phage: {
|
|
archetype: 'phage', biome: '*', tint: 0x33ffbe, wall: false,
|
|
note: 'Bacteriophage — the most abundant biological entity in you; hunts one bacterial ' +
|
|
'species and bursts it. THE ALLY: a friendly seeker that chases your FOES, not you ' +
|
|
'(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.',
|
|
},
|
|
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.',
|
|
},
|
|
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.',
|
|
},
|
|
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.',
|
|
},
|
|
|
|
bezoar_chunk: {
|
|
archetype: 'floater', biome: 'stomach', tint: 0xd98a3a, wall: false,
|
|
note: 'A fragment of bezoar - the trapped, half-digested mass a real stomach can accrete ' +
|
|
'around indigestible material. L3\'s traffic: in a 40-unit room these stop being ' +
|
|
'obstacles and become TERRAIN, the thing that makes you choose a line through a room ' +
|
|
'rather than around a hazard.',
|
|
},
|
|
|
|
// ---- L4 small intestine roster (the villi maze) ------------------------------------
|
|
// All three map to archetypes Lane B already ships — L4 is authored from existing behaviour,
|
|
// no new systems. The fiction is anatomically placed: Giardia colonises the duodenum/jejunum
|
|
// and Peyer's patches concentrate in the ileum, which is what gives L4 its three acts.
|
|
chyme_clump: {
|
|
archetype: 'floater', biome: 'small_intestine', tint: 0xd9a13a, wall: false,
|
|
note: 'Partly-digested chyme, tumbling through the villi. L4\'s traffic — the small ' +
|
|
'intestine\'s bolus_chunk: it taxes the lane it sits in rather than chasing, which in ' +
|
|
'a tube whose sightlines are already occluded by villi is pressure enough.',
|
|
},
|
|
neutrophil_hunter: {
|
|
archetype: 'seeker', biome: 'small_intestine', tint: 0xff4d3d, wall: false,
|
|
note: 'The GDD\'s "immune hunters" — neutrophils recruited into the mucosa, fast and ' +
|
|
'single-minded. Same seeker that plays the eosinophil in L2, re-fictioned for its own ' +
|
|
'biome: the immune system still cannot tell ENDO-1 from the infection it was called for.',
|
|
},
|
|
bifido_colony: {
|
|
archetype: 'flora', biome: 'small_intestine', tint: 0x39e6ff, wall: false,
|
|
note: 'Bifidobacterium — a friendly commensal of the small bowel. Carries the BIOME ' +
|
|
'STANDING loop into L4 so the reputation you bank here can be spent at the ileocecal ' +
|
|
'biofilm and, later, in the colon. Cyan: shooting it is still the trap.',
|
|
},
|
|
|
|
// ---- passthrough: bare archetype names --------------------------------------------
|
|
// Compatibility so the round-0 stub level's `enemy: 'floater'` style still resolves and
|
|
// Lane B is never broken by this catalogue landing mid-round. Not for use in shipped
|
|
// levels — selfcheck WARNS, because an enemy with no fiction has no place in the body.
|
|
floater: { archetype: 'floater', biome: '*', tint: 0xd98a3a, wall: false, placeholder: true, note: 'Bare archetype passthrough.' },
|
|
seeker: { archetype: 'seeker', biome: '*', tint: 0xff4d3d, wall: false, placeholder: true, note: 'Bare archetype passthrough.' },
|
|
turret: { archetype: 'turret', biome: '*', tint: 0xffd166, wall: true, placeholder: true, note: 'Bare archetype passthrough.' },
|
|
});
|
|
|
|
/** → enemy spec, or null on miss. Consumers must handle null (assets-are-optional law). */
|
|
export function getEnemy(id) {
|
|
return Object.prototype.hasOwnProperty.call(ENEMIES, id) ? ENEMIES[id] : null;
|
|
}
|
|
|
|
/** Pickups Lane C may place. Lane B/E: `pickup {kind}` on the bus when collected. */
|
|
export const PICKUPS = Object.freeze({
|
|
nutrient_orb: { note: 'Score + boost recharge. The breadcrumb: an orb line TEACHES the racing line before it pays anything.' },
|
|
mucin_glob: { note: 'Mucus coat refill. Placed immediately before pressure — the game pays you right before it charges you.' },
|
|
b12_cell: { note: 'Hull repair. Rare: hull is the scarce resource, coat is the renewable one.' },
|
|
antacid_ammo: { note: 'Antacid torpedoes. Dual-use (GDD): B\'s torpedo neutralises an acid zone for 10s in a 14u radius — fired BACKWARD it stalls a reflux surge.' },
|
|
biopsy_sample: { note: 'THE collectible. Exactly 3 per level (selfcheck enforces). Visible-but-costly law: always in plain sight, never on the racing line.' },
|
|
});
|
|
|
|
/** Hazard kinds Lane C places in data. Specs + owners in LANE_C_NOTES.md §Hazard requests. */
|
|
export const HAZARDS = Object.freeze({
|
|
reflux_surge: { needs: ['speed'], optional: ['from', 'span', 'lethal', 'neutralizable'], owner: 'B', note: 'Acid wall chasing from behind at `speed`. `from` = spawn offset relative to the trigger s (negative = behind the player). `lethal: false` => the non-lethal teaching version. `neutralizable` => B\'s antacid torpedo stalls it (balance.js: neutralizeDuration 10s).' },
|
|
aortic_squeeze: { needs: ['period', 'amplitude', 'theta'], optional: ['span'], owner: 'A', note: 'Zone hazard: the aortic arch pulses against ONE SIDE of the tube. The wall at `theta` bulges inward by `amplitude` * radius on `period`; the opposite arc stays clear and is the answer. Directional on purpose — a zone that sealed symmetrically would be an untimeable toll in a flow-locked tube (you cannot stop). Wall contact = graze damage, not death.' },
|
|
ring_gate: { needs: ['period', 'open'], optional: [], owner: 'A', note: 'Point hazard: a peristaltic ring seals the lumen, opening for `open` seconds every `period`. Timeable precisely BECAUSE it is a point — the player modulates throttle on approach to arrive on an open beat.' },
|
|
});
|