[lane B+C+F] The Appendix Sovereign: the secret boss, and a reputation you cannot fake
Two boss specs written to C's format (LANE_C_NOTES §Boss specs, round 3 additions) and the
first of them built. Both extend the roster along C's own law — one boss, one pressure — onto
the two axes the game had systems for and no boss pointed at: reputation (§4 Sovereign) and
restraint (§5 Mast Cell, specced only).
THE SOVEREIGN. The appendix is not vestigial: it is a biofilm reservoir where the gut's flora
shelter and re-seed the colon after a purge, and the whole fight is that fact. It is the only
boss whose existence is decided before you arrive — read once on entry:
karma >= 70 THE COMMUNION — no fight at all. Full coat, a permanent phage escort. The
reward for five levels of restraint is being spared a boss.
karma >= 30 THE TOLL — P1 only.
below THE SOVEREIGN — three phases. P1's matrix is immune to the cannon and only the
antacid torpedo disperses it (a mature biofilm's whole defining trait); P2 is a
quorum bloom that re-armours when the room gets crowded; P3 is a 20s reseed run.
And the mechanic that makes it this boss: the lair is seeded with YOUR OWN FLORA, and it feeds
on dysbiosis — every commensal your stray fire kills re-armours a plate, live, mid-fight.
KARMA vs STANDING — a real bug found in testing, and worth the extra number. `standing` is a
SPENDABLE meter: a full bar is consumed to bud an ally, dropping to a 50 floor. That meant a
perfect player could never HOLD >= 70, so the Communion was almost unreachable — verified: a
symbiotic pilot showed standing 50 and was demoted to "toll". `karma` is the same inputs with
nothing spending it. The Sovereign's tier and gates' `secretTo` branches read karma; E's bar
still reads standing.
Also: boss.js generalised to a registry, which was the claim its header made — the Sovereign
needed zero changes to enemies.js. LS_appendix (3 segments, 800 units, 16 events), reached from
L4's ileocecal valve via `secretTo`/`secretNeed` — anatomically exact, the appendix hangs off
the cecum right past that valve. The secret is hidden behind conduct, not behind a wall.
C's selfcheck caught par.time as a free medal and the fix improved the design: par 160 sits
under the timid traversal, so only a Communion run (which has no fight) can beat it. The time
medal rewards the same conduct the level is about.
Verified: all three tiers branch correctly (communion = 0 nodes + 4 phages; toll = 1 phase;
full = 3 phases, 4 armoured); killing a commensal mid-fight re-armours a plate (reason
"dysbiosis"); the valve routes karma 100 -> LS_appendix and karma 0 -> L5. Levels 5/5 valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
02e2387906
commit
a318cf09f7
@ -198,10 +198,14 @@ One-pager each: phases → telegraphs → windows → failure pressure. Written
|
||||
shape lairs** and **D can concept hero meshes** in round 2. Numbers are design intent, not
|
||||
measured — they need B's framework to tune against.
|
||||
|
||||
**The roster's shape: three bosses, three different pressures.** Guardian = a *coat* race
|
||||
(positional attrition). Tapeworm = a *distance* race (a chase you can lose without dying).
|
||||
Blockage = a *hull* race + doom timer (darkness and risk/reward). If they all became DPS
|
||||
checks we would have built one boss three times.
|
||||
**The roster's shape: one boss, one pressure.** Guardian = a *coat* race (positional
|
||||
attrition). Tapeworm = a *distance* race (a chase you can lose without dying). Blockage = a
|
||||
*hull* race + doom timer (darkness and risk/reward). If they all became DPS checks we would
|
||||
have built one boss three times.
|
||||
|
||||
Round 3 extends the roster along the same law, onto the two axes the game had systems for and
|
||||
no boss pointed at: Appendix Sovereign = a *reputation* race (§4) and the Mast Cell = a
|
||||
*restraint* test (§5). Five bosses, five pressures, no repeats.
|
||||
|
||||
### 1. Pyloric Guardian — L3 exit, arena (`arenas[2]`, s 2350, radius 70)
|
||||
|
||||
@ -263,6 +267,90 @@ the medium — **the gas pockets are the light source**, which is the whole look
|
||||
|
||||
---
|
||||
|
||||
## Boss specs — round 3 additions
|
||||
|
||||
Same law as the roster above: **a boss earns its place by attacking a resource nothing else
|
||||
attacks.** The three originals take coat, distance and hull. These two take the two axes the
|
||||
game built systems for and then never pointed a boss at — **your reputation** and **your
|
||||
trigger finger**. Neither needs a new archetype; both are schedules over what exists
|
||||
(`combat/boss.js` is a state machine, not an entity system — see its header).
|
||||
|
||||
### 4. The Appendix Sovereign — LS secret, arena (the cul-de-sac off the cecum)
|
||||
|
||||
The appendix is not vestigial, and this fight is the game finally saying so: it is a **biofilm
|
||||
reservoir**, a dead-end where the gut's own flora shelter from the torrent and **re-seed the
|
||||
colon after a purge**. The Sovereign is that mat, matured — a layered dome carrying the lineage
|
||||
of every colony you have flown past since the esophagus.
|
||||
|
||||
**It is the only boss whose existence is decided before you arrive.** BIOME STANDING is read on
|
||||
entry and the encounter branches:
|
||||
|
||||
| standing | what happens |
|
||||
|---|---|
|
||||
| **≥ 70 — symbiotic** | **The Communion.** Not a fight. The mat opens, re-seeds your coat to full and sends a permanent phage escort out with you. ~30 s of spectacle and a gift. The reward for five levels of restraint is *being spared a boss*. |
|
||||
| **30–69 — neutral** | **The Toll.** It does not know you. P1 only, softened — prove yourself and it opens. |
|
||||
| **< 30 — dysbiotic** | **The Sovereign.** The mat has turned. Full three-phase fight, the hardest in the game. |
|
||||
|
||||
The dysbiotic fight, which is the one worth speccing:
|
||||
|
||||
| phase | hp | the loop | window |
|
||||
|---|---|---|---|
|
||||
| **P1 The Matrix** | 100→60% | The dome is sheathed in extracellular matrix and **the cannon does nothing to it** — that is not a gimmick, it is what a biofilm IS (a mature one is orders of magnitude more drug-resistant than free bacteria). Only the **antacid torpedo** disperses matrix, exposing feeding channels beneath. Nutrient orbs bloom around the lair to fund the ammo. | continuous, gated by **ammo**, not by time |
|
||||
| **P2 Quorum Bloom** | 60→30% | Channels exposed, it **calls** — quorum sensing, for real: it floods the lair with virions and spore pods and re-armours a matrix plate every time the population crosses its threshold. Hold the count DOWN with sustained fire while hitting the channels. Let it bloom and you are back in P1. | continuous, gated by **heat** |
|
||||
| **P3 The Reseed** | 30→0% | It will not die in a cul-de-sac: it tries to **flush itself into the colon to survive**, and the lair drains toward the cecum. **20 s doom timer** — if it reaches the exit it lives, the secret is lost, and the run ends without it. Core exposed while it swims. | **~2 s per surge, ×4** |
|
||||
|
||||
- **The mechanic that makes it this boss and not another:** **the lair is full of your own flora.**
|
||||
Cyan colonies drift between you and the dome for the whole fight, and the Sovereign *feeds on
|
||||
dysbiosis* — every commensal your stray fire kills drops your standing and **buffs it in real
|
||||
time** (a plate regrows, a bloom comes early). It is the only fight in GUTS where the right
|
||||
move is regularly to **stop shooting**, under maximum pressure, with a boss in your face.
|
||||
- **Telegraphs:** the dome glows brighter and redder as your standing falls — the boss wears
|
||||
your conduct as its skin · matrix plates crack white where a torpedo landed · a rising
|
||||
*chorus* 1.5 s before a quorum bloom · P3 = the whole lair's fog streams toward the exit.
|
||||
- **Failure pressure:** **reputation, live.** Every other boss takes a resource off your ship.
|
||||
This one takes your difficulty dial and hands it to your trigger discipline. A player who
|
||||
sprays makes the fight unwinnable by their own hand, which is the thesis of the game stated
|
||||
as a boss.
|
||||
- **Why a secret boss may borrow every pressure:** the roster law says one pressure per boss so
|
||||
we do not build one boss three times. An optional superboss at the end of a hidden branch is
|
||||
the **exam**, and an exam is allowed to be cumulative — ammo (P1), heat (P2), distance (P3).
|
||||
Its OWN pressure, the one no other boss has, is the flora law running underneath all three.
|
||||
- **Scaling:** `difficulty.window`; the branch thresholds shift (easy 60/25 · normal 70/30 ·
|
||||
hard 80/40), so mercy is easier to earn on easy and must be deliberate on hard.
|
||||
|
||||
### 5. The Mast Cell — L1 oral cavity, arena (the tutorial's centrepiece)
|
||||
|
||||
A sentinel immune cell on patrol in the oral mucosa, packed with granules of histamine. **It is
|
||||
not hostile.** It has no attack, it does not chase, and its aura is harmless. It is also
|
||||
hair-trigger, and the entire encounter is built on one question: *will you shoot it?*
|
||||
|
||||
The GDD's fiction has said from day one that the enemy is "an infestation **+ an immune system
|
||||
that can't tell you're friendly**". Every boss so far fights that second clause. This one lets
|
||||
you decline.
|
||||
|
||||
| phase | hp | the loop | window |
|
||||
|---|---|---|---|
|
||||
| **P1 Patrol** | — | It drifts a slow circuit of the cavity, granules glowing **cyan** — the flora colour, doing narrative work: the readability law is telling you *this is not your enemy* and the level is watching whether you believe it. Thread past it and the encounter simply **ends**. There is no fight. | the whole beat |
|
||||
| **P2 Degranulation** | 100→40% | Triggered by **any** damage you deal it. It turns amber and dumps histamine: the cavity's walls **swell inward** (playable radius drops ~30%), coat drain doubles, and amylase droplets pour in. It still does not chase you — the level is now the enemy, and you did that. | continuous |
|
||||
| **P3 Anaphylaxis** | 40→0% | Past a second damage threshold the cascade goes systemic: the lumen constricts toward lethal and a **25 s doom timer** starts — the airway is closing. Reaching the epiglottis is now a race you started for no reason. | continuous |
|
||||
|
||||
- **Telegraphs:** cyan granules while calm · a visible *ripple* through its membrane if your
|
||||
reticle lingers on it — the game warning you off before you ever pull · the first hit turns it
|
||||
amber and the comms crew break protocol to shout · the walls visibly swell on every plume.
|
||||
- **Failure pressure:** **restraint, and nothing else.** The pacifist route has no coat race, no
|
||||
hull race and no timer. All the pressure is psychological: five minutes of tutorial have
|
||||
trained the reflex "amber thing → shoot", the game hands you a thing that is *not* amber, and
|
||||
the punishment for the reflex is not damage — it is **the level getting worse and staying
|
||||
worse**. Best-case and worst-case are separated entirely by what the player chose.
|
||||
- **Why it belongs in L1:** a tutorial teaches verbs. This one teaches the game's *thesis* —
|
||||
that the body is weather and not all of it is your enemy — and it teaches it in the only way
|
||||
that sticks, by letting you get it wrong and live in the consequence. It also arms the flora
|
||||
law before L2 ever shows a cyan colony.
|
||||
- **Scaling:** difficulty sets the anaphylaxis doom timer (32 / 25 / 20 s) and whether P2 has a
|
||||
grace hit (easy forgives one; hard triggers on a graze).
|
||||
|
||||
---
|
||||
|
||||
## → Lane B
|
||||
|
||||
Four things, in priority order. (1) is the only one that blocks you consuming my levels.
|
||||
|
||||
@ -187,6 +187,13 @@ Ratified round 2 (round-1 requests):
|
||||
- `standing {value}` — BIOME STANDING 0..100 (combat/index.js owns it). Tending flora raises it;
|
||||
harming flora drops it; crossing the threshold buds phage allies. E can meter it. ("Don't
|
||||
shoot the biome, it helps you" — V2_IDEAS, now partly real.)
|
||||
- `karma {value}` — 0..100, the UNSPENDABLE conduct ledger. Same inputs as `standing`, but
|
||||
nothing consumes it. `standing` is a resource (a full bar is spent budding an ally, dropping
|
||||
to 50); `karma` is the record. Anything asking "how did this player treat the biome across the
|
||||
campaign" must read karma — the Sovereign's tier and a gate's `secretTo` branch both do.
|
||||
- `secret:open {gate, to, standing}` — a gate's `secretTo` branch was taken (karma ≥ `secretNeed`).
|
||||
- `boss:telegraph {id,phase,eta}` · `boss:window {id,open,phase}` · `boss:rearm {id,reason}` —
|
||||
boss schedule events (`reason`: `quorum` | `dysbiosis`).
|
||||
- `biofilm {opened, s}` — a `biofilm` level event was crossed (the reputation cash-out). `opened`
|
||||
= standing was high enough, so the biome parted (allies budded + coat refilled, standing spent);
|
||||
false = it stayed sealed. E can voice both. (New event type `biofilm`, fields `need/allies/coat`.)
|
||||
|
||||
@ -122,8 +122,16 @@ bus.on('level:event', (ev) => {
|
||||
bus.emit('audio:cue', { name: 'checkpoint' });
|
||||
} else if (ev.type === 'gate' && !run.done) {
|
||||
run.done = true;
|
||||
// A gate may carry a SECRET branch (`secretTo` + `secretNeed`): C's ileocecal valve opens
|
||||
// onto the appendix instead of the colon when BIOME STANDING has earned it. Resolved here
|
||||
// because boot owns where the run goes next, and read at the moment of crossing — the
|
||||
// reputation you hold when you arrive is the one that counts.
|
||||
const standing = combat?.karma ?? 0; // conduct, not the spendable meter
|
||||
const secret = ev.secretTo && standing >= (ev.secretNeed ?? 70);
|
||||
const to = secret ? ev.secretTo : (ev.to ?? null);
|
||||
if (secret) bus.emit('secret:open', { gate: ev.id ?? null, to, standing });
|
||||
bus.emit('level:complete', {
|
||||
id: world.level?.id ?? null, to: ev.to ?? null, gate: ev.id ?? null,
|
||||
id: world.level?.id ?? null, to, gate: ev.id ?? null, secret: !!secret,
|
||||
stats: {
|
||||
time: run.t, deaths: run.deaths,
|
||||
score: combat?.score.value ?? 0, kills: combat?.score.kills ?? 0,
|
||||
|
||||
@ -28,28 +28,64 @@ import { BALANCE as B } from './balance.js';
|
||||
// first window, which is what makes the fight a fight — the ring outlives the opening, so you
|
||||
// come back for it, and coming back is what lets the stomach's coat-drain be the real clock.
|
||||
// P3's core is 60 = exactly the "x5 hits" C specifies.
|
||||
const PHASES = [
|
||||
{ id: 1, open: 2.5, period: 8.0, nodes: 4, hp: 150, armored: 0, vents: 3, ventEvery: 3.0 },
|
||||
{ id: 2, open: 1.8, period: 5.0, nodes: 4, hp: 150, armored: 2, vents: 3, ventEvery: 2.4 },
|
||||
{ id: 3, open: 1.2, period: 4.0, nodes: 1, hp: 60, armored: 0, vents: 3, ventEvery: 2.0, doom: 25 },
|
||||
];
|
||||
const TELEGRAPH = 2.0; // C: "a deep gulp 2 s before every open"
|
||||
const RIM_RHO = 0.82; // fraction of the safe radius the nodes sit at - the iris rim
|
||||
//
|
||||
// THE REGISTRY. A boss is a list of phases plus a few knobs; everything mechanical is shared.
|
||||
// Adding the Sovereign below required no change to enemies.js at all, which is the claim the
|
||||
// Guardian's header made and this is the test of it.
|
||||
// open/period the vulnerability window and its cycle. open >= period = always shootable.
|
||||
// nodes/hp the ring spawned for that phase; `armored` of them need a torpedo first.
|
||||
// vents turrets at fixed arcs (their homing dart IS an acid spout / a spore jet).
|
||||
// bloom {every, kinds, cap} — adds spawned during the phase (quorum sensing).
|
||||
// doom seconds before the phase kills you if you have not finished it.
|
||||
const BOSSES = {
|
||||
pyloric_guardian: {
|
||||
telegraph: 2.0, // C: "a deep gulp 2 s before every open"
|
||||
node: 'mucosal_node',
|
||||
rho: 0.82, // the iris rim
|
||||
phases: [
|
||||
{ id: 1, open: 2.5, period: 8.0, nodes: 4, hp: 150, armored: 0, vents: 3, ventEvery: 3.0 },
|
||||
{ id: 2, open: 1.8, period: 5.0, nodes: 4, hp: 150, armored: 2, vents: 3, ventEvery: 2.4 },
|
||||
{ id: 3, open: 1.2, period: 4.0, nodes: 1, hp: 60, armored: 0, vents: 3, ventEvery: 2.0, doom: 25 },
|
||||
],
|
||||
},
|
||||
|
||||
export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
// LANE_C_NOTES §4. The reputation boss. `standing` gates the whole encounter on entry, and
|
||||
// keeps mattering: every commensal you kill in the lair re-armours a plate (see onHarm).
|
||||
appendix_sovereign: {
|
||||
telegraph: 1.5, // C: "a rising chorus 1.5 s before a quorum bloom"
|
||||
node: 'mucosal_node', // the matrix plates / feeding channels reuse the node body
|
||||
rho: 0.7,
|
||||
standing: { spare: 70, soften: 30 }, // >= spare: no fight at all. >= soften: P1 only.
|
||||
phases: [
|
||||
// P1 — the matrix. ALL plates armoured, so the cannon genuinely cannot start this fight:
|
||||
// only the antacid torpedo disperses matrix. Always "open": a biofilm has no iris, its
|
||||
// defence is chemistry, and the gate on progress is AMMO.
|
||||
{ id: 1, open: 9, period: 9, nodes: 4, hp: 90, armored: 4, vents: 2, ventEvery: 3.2 },
|
||||
// P2 — quorum bloom. Channels exposed; it calls for defenders on a timer and re-armours
|
||||
// when the room gets crowded. The gate on progress is HEAT.
|
||||
{ id: 2, open: 7, period: 7, nodes: 3, hp: 110, armored: 0, vents: 2, ventEvery: 2.6,
|
||||
bloom: { every: 3.2, kinds: ['virion', 'virion', 'spore_pod'], cap: 14, rearmAt: 10 } },
|
||||
// P3 — the reseed. It runs for the colon; 20 s to stop it or it lives and the secret is
|
||||
// lost. Short exposures while it swims.
|
||||
{ id: 3, open: 2.0, period: 5.0, nodes: 1, hp: 80, armored: 0, vents: 2, ventEvery: 2.2, doom: 20 },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function createBoss({ bus, rng, player, enemies, flags = {}, getKarma = null }) {
|
||||
const rand = rng ? rng('boss') : () => 0.5;
|
||||
const windowScale = Number(flags.window) > 0 ? Number(flags.window) : 1;
|
||||
|
||||
let active = null; // null | the running fight
|
||||
const offs = [];
|
||||
|
||||
function spawnRing(s, phase) {
|
||||
function spawnRing(f, phase) {
|
||||
const out = [];
|
||||
for (let i = 0; i < phase.nodes; i++) {
|
||||
// Evenly spaced around the cross-section, rotated a little each phase so a player who
|
||||
// memorised one safe approach has to re-read the ring.
|
||||
const theta = (i / phase.nodes) * Math.PI * 2 + rand() * 0.5;
|
||||
const e = enemies.spawn('mucosal_node', { s, theta, rho: RIM_RHO });
|
||||
const e = enemies.spawn(f.def.node, { s: f.s, theta, rho: f.def.rho });
|
||||
if (!e) continue;
|
||||
e.hp = phase.hp; // per-phase: rim nodes are tanky, the core is 5 hits
|
||||
e.invuln = true; // the iris starts shut
|
||||
@ -72,12 +108,13 @@ export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
}
|
||||
|
||||
function setPhase(f, n) {
|
||||
f.phase = PHASES[n];
|
||||
f.phase = f.def.phases[n];
|
||||
f.phaseIdx = n;
|
||||
for (const e of f.nodes) if (e.alive) e.alive = false; // old ring is gone
|
||||
f.nodes = spawnRing(f.s, f.phase);
|
||||
f.nodes = spawnRing(f, f.phase);
|
||||
f.open = false;
|
||||
f.t = 0;
|
||||
f.bloomT = 0;
|
||||
f.doomT = f.phase.doom ?? 0;
|
||||
for (const v of f.vents) if (v.alive) v.cfg = { ...v.cfg, fireEvery: f.phase.ventEvery };
|
||||
bus.emit('boss:phase', { id: f.id, phase: f.phase.id, nodes: f.nodes.length });
|
||||
@ -86,15 +123,40 @@ export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
|
||||
function start(ev) {
|
||||
if (active) return;
|
||||
const def = BOSSES[ev.id] ?? BOSSES.pyloric_guardian;
|
||||
const f = {
|
||||
id: ev.id ?? 'pyloric_guardian', s: ev.s ?? 0,
|
||||
nodes: [], vents: [], phase: PHASES[0], phaseIdx: 0,
|
||||
open: false, t: 0, doomT: 0, warned: false, done: false,
|
||||
id: ev.id ?? 'pyloric_guardian', def, s: ev.s ?? 0,
|
||||
nodes: [], vents: [], phase: def.phases[0], phaseIdx: 0,
|
||||
open: false, t: 0, bloomT: 0, doomT: 0, warned: false, done: false,
|
||||
};
|
||||
f.vents = spawnVents(f.s, PHASES[0].vents);
|
||||
|
||||
// LANE_C_NOTES §4: the Sovereign is the one boss whose existence is decided before you
|
||||
// arrive. KARMA (the unspendable conduct ledger, not the spendable standing meter) is read
|
||||
// ONCE, on entry — a verdict on how you played the campaign, not on how recently you cashed
|
||||
// your reputation in for an ally.
|
||||
if (def.standing) {
|
||||
const st = getKarma ? getKarma() : 0;
|
||||
f.standing = st;
|
||||
if (st >= def.standing.spare) {
|
||||
// The Communion. The reward for five levels of restraint is being spared a boss.
|
||||
active = f;
|
||||
bus.emit('boss:start', { id: f.id, s: f.s, phases: 0, tier: 'communion', standing: st });
|
||||
player?.refill?.({ coat: 999, hull: 40 });
|
||||
for (let i = 0; i < 3; i++) enemies.spawn('phage', { s: f.s + 6 + i * 4 });
|
||||
finish(f, 'communion');
|
||||
return f;
|
||||
}
|
||||
// The Toll: it does not know you, so it shows you the matrix and nothing worse.
|
||||
f.maxPhase = st >= def.standing.soften ? 1 : def.phases.length;
|
||||
}
|
||||
|
||||
f.vents = spawnVents(f.s, def.phases[0].vents);
|
||||
active = f;
|
||||
setPhase(f, 0);
|
||||
bus.emit('boss:start', { id: f.id, s: f.s, phases: PHASES.length });
|
||||
bus.emit('boss:start', {
|
||||
id: f.id, s: f.s, phases: f.maxPhase ?? def.phases.length,
|
||||
tier: f.maxPhase === 1 ? 'toll' : 'full', standing: f.standing,
|
||||
});
|
||||
return f;
|
||||
}
|
||||
|
||||
@ -113,13 +175,36 @@ export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
|
||||
const live = f.nodes.filter((e) => e.alive);
|
||||
|
||||
// Ring cleared -> next phase, or the fight is over.
|
||||
// Ring cleared -> next phase, or the fight is over. `maxPhase` is the Sovereign's Toll tier:
|
||||
// a neutral biome shows you the matrix and, once you have cleared it, decides that will do.
|
||||
const lastPhase = f.maxPhase ?? f.def.phases.length;
|
||||
if (!live.length) {
|
||||
if (f.phaseIdx + 1 < PHASES.length) setPhase(f, f.phaseIdx + 1);
|
||||
if (f.phaseIdx + 1 < lastPhase) setPhase(f, f.phaseIdx + 1);
|
||||
else finish(f, 'win');
|
||||
return;
|
||||
}
|
||||
|
||||
// Quorum bloom (Sovereign P2): it calls for defenders on a timer, and when the room gets
|
||||
// crowded it re-armours a channel — so letting the population run is how you lose ground.
|
||||
// Population is the clock here, which is why this phase reads as a heat-management fight.
|
||||
const bl = f.phase.bloom;
|
||||
if (bl) {
|
||||
f.bloomT -= dt;
|
||||
if (f.bloomT <= 0) {
|
||||
f.bloomT = bl.every;
|
||||
const crowd = enemies.live.filter((e) => e.alive && bl.kinds.includes(e.type)).length;
|
||||
if (crowd < bl.cap) {
|
||||
const kind = bl.kinds[(f.bloomN = (f.bloomN ?? 0) + 1) % bl.kinds.length];
|
||||
enemies.spawn(kind, { s: f.s + (rand() - 0.5) * 20 });
|
||||
bus.emit('audio:cue', { name: 'enemy_hit' });
|
||||
}
|
||||
if (crowd >= bl.rearmAt) { // above quorum: the matrix comes back
|
||||
const bare = live.find((e) => !e.armor);
|
||||
if (bare) { bare.armor = true; bus.emit('boss:rearm', { id: f.id, reason: 'quorum' }); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// P3's doom timer: it stops opening and tries to seal permanently. Run it down whatever the
|
||||
// iris is doing - the only escape is killing the core, which is why P3's ring is one node.
|
||||
if (f.doomT > 0) {
|
||||
@ -134,9 +219,9 @@ export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
const wasOpen = f.open;
|
||||
f.open = f.t < openFor;
|
||||
|
||||
// Telegraph: the deep gulp, TELEGRAPH seconds before the window comes round again.
|
||||
// Telegraph: the deep gulp (or the Sovereign's rising chorus) before the window returns.
|
||||
const untilOpen = f.t < openFor ? 0 : f.phase.period - f.t;
|
||||
if (!f.open && untilOpen <= TELEGRAPH && !f.warned) {
|
||||
if (!f.open && untilOpen <= f.def.telegraph && !f.warned) {
|
||||
f.warned = true;
|
||||
bus.emit('boss:telegraph', { id: f.id, phase: f.phase.id, eta: untilOpen });
|
||||
bus.emit('audio:cue', { name: 'warn_reflux_surge' });
|
||||
@ -151,6 +236,20 @@ export function createBoss({ bus, rng, player, enemies, flags = {} }) {
|
||||
}
|
||||
|
||||
offs.push(bus.on('level:event', (ev) => { if (ev?.type === 'boss') start(ev); }));
|
||||
|
||||
// LANE_C_NOTES §4, the mechanic that makes the Sovereign this boss and not another: the lair
|
||||
// is seeded with your own flora, and the Sovereign FEEDS ON DYSBIOSIS. Every commensal your
|
||||
// stray fire kills re-armours a plate — the fight gets harder by your hand, in real time,
|
||||
// while a boss is in your face. It is the only encounter where the right move is regularly to
|
||||
// stop shooting. (Gated on `standing` so only reputation bosses care.)
|
||||
offs.push(bus.on('flora:harmed', () => {
|
||||
const f = active;
|
||||
if (!f || f.done || !f.def.standing) return;
|
||||
const bare = f.nodes.find((e) => e.alive && !e.armor);
|
||||
if (bare) bare.armor = true;
|
||||
bus.emit('boss:rearm', { id: f.id, reason: 'dysbiosis' });
|
||||
bus.emit('audio:cue', { name: 'overheat' });
|
||||
}));
|
||||
// A death mid-fight resets the encounter rather than leaving a half-killed ring in the arena:
|
||||
// C put a checkpoint at 2260 precisely so a wipe costs seconds, and that promise only holds if
|
||||
// the boss re-arms with it.
|
||||
|
||||
@ -21,7 +21,7 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null
|
||||
const hazards = createHazards({ scene, world, bus, rng, player });
|
||||
const pickups = createPickups({ scene, world, bus, rng, player, weapons });
|
||||
// Bosses are a SCHEDULE over the enemy pool, not a parallel entity system — see boss.js.
|
||||
const boss = createBoss({ bus, rng, player, enemies, flags });
|
||||
const boss = createBoss({ bus, rng, player, enemies, flags, getKarma: () => karma });
|
||||
|
||||
enemies.setBlastFx((pos, size, dur) => weapons.spawnBlast(pos, size, dur));
|
||||
enemies.setOnDeath((e) => weapons.spawnBlast(e.pos, e.radius * 1.6, 0.3));
|
||||
@ -84,10 +84,22 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null
|
||||
// Scaled 0..100 so E can meter it honestly: tending fills the bar, a full bar buds an ally
|
||||
// and drops it to 50 (the recharge floor), harming flora empties a big chunk. Numbers are a
|
||||
// first pass — C/playtest tunes the feel.
|
||||
let standing = 0, phageBudCd = 0;
|
||||
// TWO numbers, because they do two different jobs and conflating them is a bug I shipped once:
|
||||
// standing — a SPENDABLE meter. Fills as you tend, and a full bar is CONSUMED to bud an ally
|
||||
// (dropping to a 50 floor). It is a resource.
|
||||
// karma — a LEDGER of conduct. Same inputs, but nothing ever spends it. This is what the
|
||||
// Appendix Sovereign and the secret door read, because "how did you treat the
|
||||
// biome across the campaign" must not be answerable by "how recently did you cash
|
||||
// in". With one number, budding an ally at 100 dropped you to 50 and quietly made
|
||||
// the Sovereign's best outcome unreachable.
|
||||
let standing = 0, karma = 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', (e) => { setStanding(standing + (e?.gain ?? 5)); budPhage(); });
|
||||
const offHarmed = bus.on('flora:harmed', () => setStanding(standing - 35));
|
||||
const setKarma = (v) => { karma = Math.max(0, Math.min(100, v)); bus.emit('karma', { value: karma }); };
|
||||
const offTended = bus.on('flora:tended', (e) => {
|
||||
const g = e?.gain ?? 5;
|
||||
setStanding(standing + g); setKarma(karma + g); budPhage();
|
||||
});
|
||||
const offHarmed = bus.on('flora:harmed', () => { setStanding(standing - 35); setKarma(karma - 35); });
|
||||
function budPhage() {
|
||||
if (standing < 100 || phageBudCd > 0) return; // a FULL bar buds an ally
|
||||
if (enemies.live.filter((e) => e.type === 'phage' && e.alive).length >= 3) return; // ally cap
|
||||
@ -134,6 +146,10 @@ export function createCombat({ scene, world, bus, rng, flags = {}, assets = null
|
||||
return {
|
||||
update,
|
||||
enemies, weapons, hazards, pickups, boss, score,
|
||||
/** BIOME STANDING 0..100 — the spendable meter (E's HUD bar). */
|
||||
get standing() { return standing; },
|
||||
/** KARMA 0..100 — the unspendable conduct ledger. boot reads it for `secretTo` branches. */
|
||||
get karma() { return karma; },
|
||||
// Respawn support (boot calls this from its death->checkpoint glue): re-arm hazard
|
||||
// timelines at/ahead of the respawn s. Enemies and uncollected pickups persist — the
|
||||
// pump never re-fires past events, so nothing double-spawns.
|
||||
|
||||
@ -151,6 +151,6 @@
|
||||
{ "s": 5700, "type": "spawn", "enemy": "chyme_clump", "count": 3, "spread": 80 },
|
||||
{ "s": 5800, "type": "checkpoint", "name": "Valve Approach" },
|
||||
{ "s": 5900, "type": "pickup", "kind": "nutrient_orb", "count": 3, "spread": 40, "note": "A last line of orbs on the racing line - the level lets you leave at speed." },
|
||||
{ "s": 6000, "type": "gate", "id": "ileocecal_valve", "to": "L5_large_intestine", "note": "The ileocecal valve: a genuine one-way door in the real gut, which is exactly what a level gate is. You go through; the chyme does not come back." }
|
||||
{ "s": 6000, "type": "gate", "id": "ileocecal_valve", "to": "L5_large_intestine", "secretTo": "LS_appendix", "secretNeed": 55, "note": "The ileocecal valve: a genuine one-way door in the real gut, which is exactly what a level gate is. You go through; the chyme does not come back. THE SECRET DOOR: the appendix hangs off the cecum immediately past this valve, so `secretTo` routes you into it instead of the colon when BIOME STANDING is >= 55 - the biome opens its reservoir to a pilot who has been kind to it. The secret is hidden behind conduct rather than behind a wall, which is the only kind of secret this game should have." }
|
||||
]
|
||||
}
|
||||
|
||||
69
web/js/levels/LS_appendix.json
Normal file
69
web/js/levels/LS_appendix.json
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"id": "LS_appendix",
|
||||
"name": "Appendix",
|
||||
"tagline": "The reservoir",
|
||||
"seed": 20260721,
|
||||
"par": { "time": 160, "score": 1600, "samples": 3 },
|
||||
"next": "L5_large_intestine",
|
||||
"design": {
|
||||
"owner": "Lane C",
|
||||
"identity": "The secret, and the campaign's verdict on you. Short, gold, and almost still - after L4's ten-minute maze this is a held breath. Whether it is a treasure room or the hardest fight in the game is decided by five levels of BIOME STANDING before you ever arrive (LANE_C_NOTES §Boss specs 4).",
|
||||
"anatomy": "A blind-ended pouch off the cecum, entered through the appendiceal orifice and a narrow neck - and the neck is the point: it is exactly why the real organ obstructs and inflames. Not vestigial. Modern immunology reads it as a BIOFILM RESERVOIR, a refuge where the gut's flora shelter from the torrent and re-seed the colon after a purge. The whole boss is that fact.",
|
||||
"entry": "Reached from L4's ileocecal valve, and only with the biome on your side: the valve carries `secretTo` + `secretNeed`, so high standing opens the appendiceal door and low standing walks you straight past it into the colon. The secret is not hidden behind a wall, it is hidden behind conduct.",
|
||||
"scale": "800 units, radius 7-16. The neck at 7 is the tightest non-boss lumen in the game after L4's terminal ileum; the reservoir opens to a 45-unit arena, which is small for a lair on purpose - a cul-de-sac should feel like one.",
|
||||
"flow": "Biome default is 2 u/s (a reservoir is defined by NOT flowing). Segments override to 4-6 so the approach is slow rather than glacial, and the lair is an arena anyway - in a room the current is irrelevant and your thrust is the only speed there is.",
|
||||
"no_gate_ritual": "The exit rejoins the colon at the cecum. L5 does not exist yet, so `next` dead-ends gracefully on the medal card (boot's chaining guard) rather than dumping the player into the stub.",
|
||||
"par_rationale": "160s, which is UNDER the 165s timid traversal the sim reports - and that is the design, not an oversight. The pacing sim measures travel and cannot see a boss, so on a level whose boss lasts 0-90s depending on your standing, par has to mean something the sim cannot express. It does: a player who arrives symbiotic gets the Communion, has no fight at all, and can beat 160 comfortably. A player who has to fight the Sovereign cannot. THE TIME MEDAL REWARDS THE SAME CONDUCT THE LEVEL IS ABOUT, which is the only par this level should have. par.score 1600 is likewise sized to the pacifist route (3 samples + the orb line), so neither medal requires killing anything in the gut's own seed bank."
|
||||
},
|
||||
"segments": [
|
||||
{
|
||||
"biome": "appendix",
|
||||
"name": "Appendiceal Orifice",
|
||||
"length": 200,
|
||||
"radius": { "base": 9, "wobble": 0.1 },
|
||||
"curviness": 0.25,
|
||||
"flow": 6,
|
||||
"note": "The door in the cecal wall. Gold rim light, almost no current, and the first quiet since the ileum - the level opens by taking the pressure OFF, because the fight at the end will be decided by whether the player is calm enough to hold fire."
|
||||
},
|
||||
{
|
||||
"biome": "appendix",
|
||||
"name": "The Neck",
|
||||
"length": 200,
|
||||
"radius": { "base": 7, "wobble": 0.12 },
|
||||
"curviness": 0.4,
|
||||
"flow": 5,
|
||||
"note": "The narrow lumen that makes appendicitis a thing: obstruct this and the pouch behind it has nowhere to drain. Tightest stretch in the level, and deliberately placed BEFORE the lair - you squeeze in, and on the way out you will know exactly how narrow the only exit is."
|
||||
},
|
||||
{
|
||||
"biome": "appendix",
|
||||
"name": "The Reservoir",
|
||||
"length": 400,
|
||||
"radius": { "base": 16, "wobble": 0.15 },
|
||||
"curviness": 0.2,
|
||||
"flow": 6,
|
||||
"note": "The pouch itself, opening into the lair arena. Lymphoid tissue everywhere, flora everywhere - this is where the gut keeps its seed stock. The Sovereign sits at 600 with the colonies it protects drifting around it, which is the whole trap: its bodyguards are the thing you must not shoot."
|
||||
}
|
||||
],
|
||||
"arenas": [
|
||||
{ "at": 600, "radius": 45, "biome": "appendix", "note": "The Sovereign's lair. Small for an arena on purpose - a cul-de-sac, not a hall. Spans s 555-645." }
|
||||
],
|
||||
"events": [
|
||||
{ "s": 20, "type": "checkpoint", "name": "Appendiceal Orifice" },
|
||||
{ "s": 90, "type": "pickup", "kind": "nutrient_orb", "count": 3, "spread": 40, "note": "Gold on gold. The treasure-room read starts immediately (ART_BIBLE §Biome palettes: appendix = treasure)." },
|
||||
{ "s": 140, "type": "spawn", "enemy": "faecali_beacon", "count": 2, "spread": 40, "rho": 0.6, "note": "The reward commensal, greeting you at the door. If you are arriving with high standing these are your welcome party; if you are not, they are the first thing a panicking player shoots." },
|
||||
{ "s": 200, "type": "checkpoint", "name": "The Neck" },
|
||||
{ "s": 260, "type": "pickup", "kind": "biopsy_sample", "count": 1, "theta": 1.57, "rho": 0.88, "note": "SAMPLE 1/3. Cheap by design - wall-pinned in the neck with nothing chasing you. The appendix is the one level that gives you a collectible for free, because its real cost is the fight at the end." },
|
||||
{ "s": 340, "type": "checkpoint", "name": "Neck Exit" },
|
||||
{ "s": 400, "type": "pickup", "kind": "antacid_ammo", "count": 3, "spread": 30, "note": "THE most important pickup in the level: the Sovereign's matrix is immune to the cannon and only antacid disperses it (LANE_C_NOTES §4 P1). A player who arrives dry has to farm the orbs mid-fight. Three, in plain sight, on the way in - the level arms you and then asks whether you can ration." },
|
||||
{ "s": 480, "type": "checkpoint", "name": "Reservoir" },
|
||||
{ "s": 520, "type": "spawn", "enemy": "bifido_colony", "count": 4, "spread": 60, "rho": 0.75, "note": "The lair's flora, seeded BEFORE the boss so they are already drifting when the fight starts. These are the Sovereign's difficulty dial: every one your stray fire kills re-armours a plate (boss.js flora:harmed). Off the racing line, but not out of the firing line." },
|
||||
{ "s": 540, "type": "checkpoint", "name": "The Sovereign", "note": "The boss checkpoint, outside the arena (which starts at 555) - C's law: never respawn a player inside the encounter." },
|
||||
{ "s": 560, "type": "pickup", "kind": "biopsy_sample", "count": 1, "theta": 4.71, "rho": 0.8, "note": "SAMPLE 2/3, at the lip of the lair. Take it now or take it with a boss awake." },
|
||||
{ "s": 600, "type": "boss", "id": "appendix_sovereign", "note": "THE SECRET BOSS. Branches on BIOME STANDING read once on entry: >=70 the Communion (no fight, full coat, a permanent phage escort), >=30 the Toll (P1 only), below that the full three-phase Sovereign. Spec in LANE_C_NOTES §Boss specs 4." },
|
||||
{ "s": 660, "type": "spawn", "enemy": "faecali_beacon", "count": 2, "spread": 40, "rho": 0.7, "note": "More flora INSIDE the arena, deliberately between you and the dome. The fight's signature: the correct move is regularly to stop shooting." },
|
||||
{ "s": 720, "type": "checkpoint", "name": "Reservoir Floor" },
|
||||
{ "s": 750, "type": "pickup", "kind": "biopsy_sample", "count": 1, "theta": 3.14, "rho": 0.85, "note": "SAMPLE 3/3, past the dome - only reachable once the Sovereign is settled one way or the other." },
|
||||
{ "s": 800, "type": "gate", "id": "appendiceal_return", "to": "L5_large_intestine", "note": "Back out through the neck and into the colon, re-seeded. The way you came in is the only way out - which is what a blind-ended pouch means." }
|
||||
]
|
||||
}
|
||||
@ -57,7 +57,10 @@ export const SCHEMA = 2;
|
||||
export const SUPPORTED_SCHEMAS = new Set([1, 2]);
|
||||
|
||||
/** Campaign order. L4/L5/LS land in later rounds; the registry is the source of truth. */
|
||||
export const CAMPAIGN = ['L1_mouth', 'L2_esophagus', 'L3_stomach', 'L4_small_intestine'];
|
||||
// LS_appendix is the secret and is NOT on the critical path: L4's ileocecal valve routes there
|
||||
// only when BIOME STANDING is high enough (the gate's `secretTo`/`secretNeed`, honoured in
|
||||
// boot.js). It lives in the registry so it can be loaded, chained out of, and selfchecked.
|
||||
export const CAMPAIGN = ['L1_mouth', 'L2_esophagus', 'L3_stomach', 'L4_small_intestine', 'LS_appendix'];
|
||||
|
||||
/** Booting with no ?lvl= gives you the vertical slice. Lane F: see LANE_C_NOTES §→ Lane F. */
|
||||
export const DEFAULT_LEVEL = 'L2_esophagus';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user