Phase 5: active items — room-clear charged, dedicated pedestals, Space to use

- ACTIVES table with 10 actives (Bone Nova, Offal Bomb, Blood Transfusion,
  Cleaver Storm, Butcher's Bell, Marrow Draught, Slaughter Time, Panic Room,
  Skinner's Coin, Gut Reroll); charges accrue +1/combat room, +2/boss
- Dedicated 'active' pickup with weapon-style swap-drop; 30% boss / 15% item-room
  bonus pedestal spawns; flat +2 powerScore while held
- HUD active slot with charge pips + READY state; ACTIVES help page (12 pages);
  teal medallion frame variant in draw_sprites.py; 10 generated icons
- Procedural Sfx.active()/activeEmpty() (real clips deferred to audio batch)
- Fix: toast panel width now fits the wider of title/subtitle and clamps to screen
- Bump asset cache to v=40
This commit is contained in:
MeatSlicer Developer committed 2026-07-30 17:14:25 +10:00
1 parent f71d202850
commit 0ec12cd72c
27 files changed
+337 -37

No files matched your search

+43 -3
View File
@@ -61,7 +61,7 @@ for (const f of files) {
}
// const/let top-level bindings live in the context's lexical scope, not on the
// global object — expose the ones the harness pokes at directly.
vm.runInContext('Object.assign(this, { G, Input, WEAPONS, Music, Sfx, SfxBank, W, H, WALL, ITEMS, PERKS, ENEMY_TYPES, BOSS_DEFS, SPRITE_MANIFEST, Sprites, ACTOR_ANIMS, PRESSURE_UNIT, PRESSURE_DIAL_MIN, PRESSURE_DIAL_MAX, HELP_PAGES, HELP_RENDERERS, SPAWN_WARN })', sandbox);
vm.runInContext('Object.assign(this, { G, Input, WEAPONS, Music, Sfx, SfxBank, W, H, WALL, ITEMS, ACTIVES, PERKS, ENEMY_TYPES, BOSS_DEFS, SPRITE_MANIFEST, Sprites, ACTOR_ANIMS, PRESSURE_UNIT, PRESSURE_DIAL_MIN, PRESSURE_DIAL_MAX, HELP_PAGES, HELP_RENDERERS, SPAWN_WARN })', sandbox);
let simTime = 0;
const ctx = sandbox;
@@ -530,6 +530,46 @@ console.log('== phase-4 boss-exclusive legendaries ==');
ctx.startRun(); step(3, 16);
}
console.log('== active items (room-clear charged) ==');
{
const p = ctx.G.player;
p.active = null;
// pickup grants a full-charge active
ctx.spawnPickup('active', p.x, p.y, { aid: 'bonenova' });
step(2, 16);
check('active pickup equips fully charged', p.active && p.active.iid === 'bonenova' && p.active.charges === ctx.ACTIVES.bonenova.cost);
// swap: picking up a second active drops the first
ctx.spawnPickup('active', p.x, p.y, { aid: 'offalbomb' });
step(2, 16);
check('second active equips and drops the old one', p.active.iid === 'offalbomb' && ctx.G.pickups.some(k => k.type === 'active' && k.aid === 'bonenova'));
// use: charges spend, effect fires
const powerBefore = ctx.powerScore();
check('holding an active adds flat power', powerBefore > 0);
ctx.useActive();
check('using an active spends all charges', p.active.charges === 0);
// charge accrues from room clears
ctx.recordRoomClear({ type: 'combat' });
check('combat clear grants +1 charge', p.active.charges === 1);
ctx.recordRoomClear({ type: 'boss' });
check('boss clear grants +2 charges', p.active.charges === ctx.ACTIVES.offalbomb.cost);
// under-charged use is blocked
p.active.charges = 0;
const enemiesBefore = ctx.G.enemies.length;
ctx.useActive();
check('under-charged active does not fire', p.active.charges === 0 && ctx.G.enemies.length === enemiesBefore);
// panic room blocks firing
p.active = { iid: 'panicroom', charges: ctx.ACTIVES.panicroom.cost };
ctx.useActive();
check('panic room grants invulnerability', p.invT >= 2.4 && p.panicRoomT > 0);
p.weapon = { id: 'bonepopper', ammo: Infinity };
p.fireT = 0;
ctx.Input.mdown = true; step(3, 16); ctx.Input.mdown = false;
check('panic room prevents firing', ctx.G.bullets.length === 0);
p.panicRoomT = 0; p.invT = 1;
p.active = null;
ctx.G.pickups.length = 0;
}
console.log('== phase-3 hook passives ==');
{
const p = ctx.G.player;
@@ -1016,8 +1056,8 @@ console.log('== item stickiness ramps with owned count ==');
console.log('== help manual grows with the implant roster ==');
{
check('11 help pages (5 implant pages for 82 items)', ctx.HELP_PAGES.length === 11);
check('11 help renderers', ctx.HELP_RENDERERS.length === 11);
check('12 help pages (5 implants + actives)', ctx.HELP_PAGES.length === 12);
check('12 help renderers', ctx.HELP_RENDERERS.length === 12);
check('help tabs never overflow the panel', (() => {
const last = ctx.helpTabRect(ctx.HELP_PAGES.length - 1);
const first = ctx.helpTabRect(0);