0.9.5
feat: boss-room loading overlay, drop HUD pressure multiplier, elite Brood Sac 4-6 minis
This commit is contained in:
1 parent
2cf8dc89fe
commit
8991bde943
13 files changed
+101
-34
No files matched your search
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [0.9.5] — 2026-08-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Boss-room loading overlay** — first visit of a boss room holds combat in `bossload` for at least 1s while the boss action sheet is warmed (decode + GPU upload) so the roar is not cut by the hitch. Re-entering a visited boss room skips the overlay. A failed sheet cannot deadlock the screen.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Elite Brood Sac summons** — an elite sac now seeds 4–6 minis per cycle (normal remains 2). Wave elite rolls still exclude Brood Sac; debug/other elite paths are the intended case. Death burst is unchanged (4).
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- **Redundant RUN DATA pressure multiplier** — the `×N.NN` number next to score is gone; `PRESSURE NN%` and the bar remain.
|
||||||
|
|
||||||
|
|
||||||
## [0.9.1] — 2026-08-13
|
## [0.9.1] — 2026-08-13
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+1
-1
@@ -147,7 +147,7 @@ Its 180px aura grants +35% speed and heals nearby non-boss monsters ~1.8% max HP
|
|||||||
| XP | 4 |
|
| XP | 4 |
|
||||||
| First appears | Floor 7 (spawn weight 6) |
|
| First appears | Floor 7 (spawn weight 6) |
|
||||||
|
|
||||||
Spawns 2 Minis every 4s and 4 on death. At most two are wave-selected; the brood population cap is 40. Cannot be elite and is excluded from thin halls.
|
Spawns 2 Minis every 4s (elite: 4–6 every 3s) and 4 on death. At most two are wave-selected; the brood population cap is 40. Excluded from normal wave elite rolls and thin halls; an elite sac (debug or other spawn paths) still uses the elite summon count.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -271,7 +271,7 @@ Full formulas are in [enemies.md](enemies.md) and [bosses.md](bosses.md).
|
|||||||
- **Top-left — BUTCHER // VITALS:** level, hearts (red, 2 HP per heart; half containers render as half outlines) and shield hearts (cyan, 2 shield HP per heart), shown on up to two rows, XP bar, active weapon + ammo (red when below 25%), holstered weapon with `[R]` swap hint.
|
- **Top-left — BUTCHER // VITALS:** level, hearts (red, 2 HP per heart; half containers render as half outlines) and shield hearts (cyan, 2 shield HP per heart), shown on up to two rows, XP bar, active weapon + ammo (red when below 25%), holstered weapon with `[R]` swap hint.
|
||||||
- **Item tray** (below vitals): up to 24 item icons with tier numerals I–IX.
|
- **Item tray** (below vitals): up to 24 item icons with tier numerals I–IX.
|
||||||
- **Top-right — SECTOR MAP:** minimap. White = current room, red = boss, gold = item, gray = cleared, maroon = uncleared. Includes floor number.
|
- **Top-right — SECTOR MAP:** minimap. White = current room, red = boss, gold = item, gray = cleared, maroon = uncleared. Includes floor number.
|
||||||
- **Bottom-right — RUN DATA:** floor, kills, score (with the live pressure multiplier), PRESSURE % (with the dial setting), STREAK, pressure bar.
|
- **Bottom-right — RUN DATA:** floor, kills, score, PRESSURE % (with the dial setting), STREAK, pressure bar.
|
||||||
- **HUD opacity** is adjustable from the pause menu (`;` / `'` or the HUD slider, 25%–100%). The boss HP bar and toast notifications always stay fully opaque.
|
- **HUD opacity** is adjustable from the pause menu (`;` / `'` or the HUD slider, 25%–100%). The boss HP bar and toast notifications always stay fully opaque.
|
||||||
- **Bottom-center:** boss HP bar (during boss fights).
|
- **Bottom-center:** boss HP bar (during boss fights).
|
||||||
- **Top-center:** toast notifications (pickups, floor intros, boss events).
|
- **Top-center:** toast notifications (pickups, floor intros, boss events).
|
||||||
@@ -281,6 +281,7 @@ Full formulas are in [enemies.md](enemies.md) and [bosses.md](bosses.md).
|
|||||||
| Screen | What it offers |
|
| Screen | What it offers |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Title** | INITIATE DESCENT, PRESSURE DIAL (−10…+10), HD REMASTER ON/OFF tag, control reference, best score, confirmed desktop exit |
|
| **Title** | INITIATE DESCENT, PRESSURE DIAL (−10…+10), HD REMASTER ON/OFF tag, control reference, best score, confirmed desktop exit |
|
||||||
|
| **Boss approach** | Full-screen hold on first boss-room entry: boss name + FRESH MEAT APPROACHES, ≥1s, then combat |
|
||||||
| **Pause** | Jukebox (track cycling), SFX/music volume + HUD opacity sliders, resume/swap/auto/mute hints, Field Manual (`H` / `?` button) |
|
| **Pause** | Jukebox (track cycling), SFX/music volume + HUD opacity sliders, resume/swap/auto/mute hints, Field Manual (`H` / `?` button) |
|
||||||
| **Mutation Draft** | 3 perk cards, random cut, reroll, auto-draft |
|
| **Mutation Draft** | 3 perk cards, random cut, reroll, auto-draft |
|
||||||
| **Game Over ("BUTCHERED")** | Floor, kills, score, NEW BEST CUT tag, restart |
|
| **Game Over ("BUTCHERED")** | Floor, kills, score, NEW BEST CUT tag, restart |
|
||||||
|
|||||||
+22
-22
@@ -6,32 +6,32 @@
|
|||||||
<meta name="theme-color" content="#0a0305">
|
<meta name="theme-color" content="#0a0305">
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><text y=%2252%22 x=%2250%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22 font-size=%2252%22>🔪</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><text y=%2252%22 x=%2250%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22 font-size=%2252%22>🔪</text></svg>">
|
||||||
<title>MEATSLICER</title>
|
<title>MEATSLICER</title>
|
||||||
<link rel="stylesheet" href="css/style.css?v=59">
|
<link rel="stylesheet" href="css/style.css?v=60">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrap">
|
<div id="wrap">
|
||||||
<canvas id="game" width="960" height="640"></canvas>
|
<canvas id="game" width="960" height="640"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/utils.js?v=59"></script>
|
<script src="js/utils.js?v=60"></script>
|
||||||
<script src="js/state.js?v=59"></script>
|
<script src="js/state.js?v=60"></script>
|
||||||
<script src="js/input.js?v=59"></script>
|
<script src="js/input.js?v=60"></script>
|
||||||
<script src="js/sfx.js?v=59"></script>
|
<script src="js/sfx.js?v=60"></script>
|
||||||
<script src="js/sfxbank.js?v=59"></script>
|
<script src="js/sfxbank.js?v=60"></script>
|
||||||
<script src="js/audio.js?v=59"></script>
|
<script src="js/audio.js?v=60"></script>
|
||||||
<script src="js/sprites.js?v=59"></script>
|
<script src="js/sprites.js?v=60"></script>
|
||||||
<script src="js/particles.js?v=59"></script>
|
<script src="js/particles.js?v=60"></script>
|
||||||
<script src="js/weapons.js?v=59"></script>
|
<script src="js/weapons.js?v=60"></script>
|
||||||
<script src="js/bullets.js?v=59"></script>
|
<script src="js/bullets.js?v=60"></script>
|
||||||
<script src="js/pickups.js?v=59"></script>
|
<script src="js/pickups.js?v=60"></script>
|
||||||
<script src="js/items.js?v=59"></script>
|
<script src="js/items.js?v=60"></script>
|
||||||
<script src="js/perks.js?v=59"></script>
|
<script src="js/perks.js?v=60"></script>
|
||||||
<script src="js/enemies.js?v=59"></script>
|
<script src="js/enemies.js?v=60"></script>
|
||||||
<script src="js/bosses.js?v=59"></script>
|
<script src="js/bosses.js?v=60"></script>
|
||||||
<script src="js/rooms.js?v=59"></script>
|
<script src="js/rooms.js?v=60"></script>
|
||||||
<script src="js/player.js?v=59"></script>
|
<script src="js/player.js?v=60"></script>
|
||||||
<script src="js/hud.js?v=59"></script>
|
<script src="js/hud.js?v=60"></script>
|
||||||
<script src="js/help.js?v=59"></script>
|
<script src="js/help.js?v=60"></script>
|
||||||
<script src="js/debug.js?v=59"></script>
|
<script src="js/debug.js?v=60"></script>
|
||||||
<script src="js/main.js?v=59"></script>
|
<script src="js/main.js?v=60"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+1
-1
@@ -551,7 +551,7 @@ function updateEnemyAI(e, dt) {
|
|||||||
e.summonT -= dt;
|
e.summonT -= dt;
|
||||||
if (e.summonT <= 0) {
|
if (e.summonT <= 0) {
|
||||||
e.summonT = e.elite ? 3 : 4;
|
e.summonT = e.elite ? 3 : 4;
|
||||||
spawnBroodMinis(e, 2, 0.35);
|
spawnBroodMinis(e, e.elite ? irand(4, 6) : 2, 0.35);
|
||||||
e.attackT = 0.5; e.actionT = 0;
|
e.attackT = 0.5; e.actionT = 0;
|
||||||
}
|
}
|
||||||
} else if (t.ranged) {
|
} else if (t.ranged) {
|
||||||
|
|||||||
@@ -167,8 +167,6 @@ function drawHUD(ctx) {
|
|||||||
ctx.fillText('KILLS ' + String(G.kills).padStart(3, '0'), W - 18, H - 67);
|
ctx.fillText('KILLS ' + String(G.kills).padStart(3, '0'), W - 18, H - 67);
|
||||||
ctx.fillStyle = '#f0e5d7'; ctx.font = 'bold 11px monospace';
|
ctx.fillStyle = '#f0e5d7'; ctx.font = 'bold 11px monospace';
|
||||||
ctx.fillText('SCORE ' + String(G.score).padStart(6, '0'), W - 18, H - 46);
|
ctx.fillText('SCORE ' + String(G.score).padStart(6, '0'), W - 18, H - 46);
|
||||||
ctx.fillStyle = '#8f7770'; ctx.font = '8px monospace';
|
|
||||||
ctx.fillText('×' + (G.pressure || 1).toFixed(2), W - 168, H - 46);
|
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
const dialTxt = ' D' + (G.pressureDial > 0 ? '+' : '') + G.pressureDial;
|
const dialTxt = ' D' + (G.pressureDial > 0 ? '+' : '') + G.pressureDial;
|
||||||
ctx.fillStyle = G.pressure > 1 ? '#d6aa39' : '#69b8bd'; ctx.font = 'bold 8px monospace';
|
ctx.fillStyle = G.pressure > 1 ? '#d6aa39' : '#69b8bd'; ctx.font = 'bold 8px monospace';
|
||||||
|
|||||||
+24
@@ -246,6 +246,11 @@ function update(dt) {
|
|||||||
updateParticles(dt);
|
updateParticles(dt);
|
||||||
drawToastsUpdate(dt);
|
drawToastsUpdate(dt);
|
||||||
break;
|
break;
|
||||||
|
case 'bossload':
|
||||||
|
updateParticles(dt);
|
||||||
|
if (G.bossLoadT > 0) G.bossLoadT -= dt;
|
||||||
|
if (G.bossLoadT <= 0 && Sprites.warm(G.bossLoadSheet)) G.mode = 'play';
|
||||||
|
break;
|
||||||
case 'levelup':
|
case 'levelup':
|
||||||
updateLevelup(dt);
|
updateLevelup(dt);
|
||||||
updateParticles(dt);
|
updateParticles(dt);
|
||||||
@@ -328,6 +333,7 @@ function draw() {
|
|||||||
ctx.fillRect(0, 0, W, H);
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
|
||||||
if (G.mode === 'menu') { drawMenu(ctx); return; }
|
if (G.mode === 'menu') { drawMenu(ctx); return; }
|
||||||
|
if (G.mode === 'bossload') { drawBossLoad(ctx); return; }
|
||||||
|
|
||||||
updateCamera();
|
updateCamera();
|
||||||
|
|
||||||
@@ -378,6 +384,24 @@ function draw() {
|
|||||||
if (G.devMode) { if (G.debugPin && G.mode !== 'debug') drawDebugPin(ctx); }
|
if (G.devMode) { if (G.debugPin && G.mode !== 'debug') drawDebugPin(ctx); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawBossLoad(ctx) {
|
||||||
|
ctx.fillStyle = '#0a0506';
|
||||||
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
const name = G.boss && G.boss.name ? G.boss.name : 'FRESH MEAT';
|
||||||
|
ctx.fillStyle = '#d9c9bc'; ctx.font = 'bold 28px monospace';
|
||||||
|
ctx.fillText(name, W / 2, H / 2 - 6);
|
||||||
|
ctx.fillStyle = '#8f1f2e'; ctx.font = 'bold 11px monospace';
|
||||||
|
ctx.fillText('FRESH MEAT APPROACHES', W / 2, H / 2 + 16);
|
||||||
|
const pr = Math.min(1, Math.max(0, G.bossLoadT / BOSS_LOAD_MIN));
|
||||||
|
const tier = Math.floor((1 - pr) * 5) % 3;
|
||||||
|
ctx.fillStyle = '#3a1a1e';
|
||||||
|
for (let i = 0; i < 3; i++) ctx.fillRect(W / 2 - 22 + i * 14, H / 2 + 30, 8, 8);
|
||||||
|
ctx.fillStyle = '#d7a934';
|
||||||
|
ctx.fillRect(W / 2 - 22 + tier * 14, H / 2 + 30, 8, 8);
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
}
|
||||||
|
|
||||||
function drawMenu(ctx) {
|
function drawMenu(ctx) {
|
||||||
const t = performance.now() / 1000;
|
const t = performance.now() / 1000;
|
||||||
const bg = ctx.createRadialGradient && ctx.createRadialGradient(W / 2, H * 0.4, 30, W / 2, H * 0.45, 590);
|
const bg = ctx.createRadialGradient && ctx.createRadialGradient(W / 2, H * 0.4, 30, W / 2, H * 0.45, 590);
|
||||||
|
|||||||
@@ -187,6 +187,10 @@ function enterRoom(gx, gy) {
|
|||||||
// boss fights are a pure ammo drain, so stock the room on the way in
|
// boss fights are a pure ammo drain, so stock the room on the way in
|
||||||
spawnPickup('ammo', a.cx, a.cy + 140);
|
spawnPickup('ammo', a.cx, a.cy + 140);
|
||||||
Music.playBoss(boss.bossKind);
|
Music.playBoss(boss.bossKind);
|
||||||
|
G.mode = 'bossload';
|
||||||
|
G.bossLoadSheet = boss.sprite + '_sheet';
|
||||||
|
G.bossLoadT = BOSS_LOAD_MIN;
|
||||||
|
Sprites.warm(G.bossLoadSheet);
|
||||||
} else if (room.type === 'item') {
|
} else if (room.type === 'item') {
|
||||||
spawnItemPedestal(a.cx - 45, a.cy, null, 'room');
|
spawnItemPedestal(a.cx - 45, a.cy, null, 'room');
|
||||||
// item rooms sometimes also stock a weapon
|
// item rooms sometimes also stock a weapon
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ const SfxBank = {
|
|||||||
if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return;
|
if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return;
|
||||||
const file = this.FILES[name];
|
const file = this.FILES[name];
|
||||||
if (!file || !this.ctx || typeof fetch !== 'function') return;
|
if (!file || !this.ctx || typeof fetch !== 'function') return;
|
||||||
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=59')
|
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=60')
|
||||||
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
|
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
|
||||||
.then(bytes => this.ctx.decodeAudioData(bytes))
|
.then(bytes => this.ctx.decodeAudioData(bytes))
|
||||||
.then(buffer => this.buffers.set(name, this.trim(buffer)))
|
.then(buffer => this.buffers.set(name, this.trim(buffer)))
|
||||||
|
|||||||
+19
-2
@@ -88,15 +88,32 @@ const Sprites = {
|
|||||||
const finish = () => { if (--pending === 0) G.imagesLoaded = true; };
|
const finish = () => { if (--pending === 0) G.imagesLoaded = true; };
|
||||||
img.onload = () => { this.imgs[name] = img; if (typeof img.decode === 'function') img.decode().then(finish, finish); else finish(); };
|
img.onload = () => { this.imgs[name] = img; if (typeof img.decode === 'function') img.decode().then(finish, finish); else finish(); };
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=59'; return; }
|
if (hd && !img._sdRetry) { img._sdRetry = true; img.src = 'assets/' + name + '.png?v=60'; return; }
|
||||||
|
this.imgs[name] = null;
|
||||||
finish();
|
finish();
|
||||||
};
|
};
|
||||||
img.src = hd ? 'assets/hd/' + name + '.webp?v=59' : 'assets/' + name + '.png?v=59';
|
img.src = hd ? 'assets/hd/' + name + '.webp?v=60' : 'assets/' + name + '.png?v=60';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
get(name) { return this.imgs[name] || null; },
|
get(name) { return this.imgs[name] || null; },
|
||||||
|
|
||||||
|
forceUpload(img) {
|
||||||
|
if (typeof document === 'undefined' || !document.createElement) return;
|
||||||
|
if (!document.createElement('canvas').getContext) return;
|
||||||
|
if (!this._warmCtx) {
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
c.width = 1; c.height = 1;
|
||||||
|
this._warmCtx = c.getContext('2d');
|
||||||
|
}
|
||||||
|
if (this._warmCtx) this._warmCtx.drawImage(img, 0, 0, 1, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
warm(name) {
|
||||||
|
if (this.imgs[name]) { this.forceUpload(this.imgs[name]); return true; }
|
||||||
|
return this.imgs[name] !== undefined;
|
||||||
|
},
|
||||||
|
|
||||||
// Compact player-only strip: eight forward-facing stride frames.
|
// Compact player-only strip: eight forward-facing stride frames.
|
||||||
// Runtime rotation is smooth; distance-driven phase keeps contacts from skating.
|
// Runtime rotation is smooth; distance-driven phase keeps contacts from skating.
|
||||||
legs(ctx, x, y, facing, phase, targetW) {
|
legs(ctx, x, y, facing, phase, targetW) {
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ const G = {
|
|||||||
hudAlpha: 1,
|
hudAlpha: 1,
|
||||||
hdRemaster: false,
|
hdRemaster: false,
|
||||||
imagesLoaded: false,
|
imagesLoaded: false,
|
||||||
|
bossLoadT: 0,
|
||||||
|
bossLoadSheet: null,
|
||||||
roomLayer: null,
|
roomLayer: null,
|
||||||
roomLayerKey: '',
|
roomLayerKey: '',
|
||||||
atmosphereLayer: null,
|
atmosphereLayer: null,
|
||||||
@@ -72,6 +74,7 @@ const PRESSURE_GAIN = PRESSURE_UNIT; // retained: dial 0 == 1 point
|
|||||||
const PRESSURE_DROP_BASE = 3; // dial 0 == 0.03 relief base
|
const PRESSURE_DROP_BASE = 3; // dial 0 == 0.03 relief base
|
||||||
const PRESSURE_RELIEF_MUL = 0.5; // global brake: all pressure drops (hit relief + passive decay) are halved
|
const PRESSURE_RELIEF_MUL = 0.5; // global brake: all pressure drops (hit relief + passive decay) are halved
|
||||||
const DEATH_LOCK = 3;
|
const DEATH_LOCK = 3;
|
||||||
|
const BOSS_LOAD_MIN = 1.0;
|
||||||
const PRESSURE_DIAL_MIN = -10, PRESSURE_DIAL_MAX = 10;
|
const PRESSURE_DIAL_MIN = -10, PRESSURE_DIAL_MAX = 10;
|
||||||
|
|
||||||
function hdScale() { return G.hdRemaster ? 4 : 1; }
|
function hdScale() { return G.hdRemaster ? 4 : 1; }
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "meatslicer",
|
"name": "meatslicer",
|
||||||
"productName": "MeatSlicer",
|
"productName": "MeatSlicer",
|
||||||
"version": "0.9.4",
|
"version": "0.9.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A Butcher's Descent - clear, harvest, descend.",
|
"description": "A Butcher's Descent - clear, harvest, descend.",
|
||||||
"main": "desktop/main.js",
|
"main": "desktop/main.js",
|
||||||
|
|||||||
+11
-3
@@ -84,6 +84,8 @@ function fire(ev, key) {
|
|||||||
function press(key) { fire('keydown', key); }
|
function press(key) { fire('keydown', key); }
|
||||||
function release(key) { fire('keyup', key); }
|
function release(key) { fire('keyup', key); }
|
||||||
function tap(key) { press(key); step(1, 16); release(key); }
|
function tap(key) { press(key); step(1, 16); release(key); }
|
||||||
|
function drainBossLoad() { let g = 0; while (ctx.G.mode === 'bossload' && g++ < 150) step(1, 16); }
|
||||||
|
|
||||||
|
|
||||||
let failures = 0;
|
let failures = 0;
|
||||||
function check(name, cond) {
|
function check(name, cond) {
|
||||||
@@ -216,7 +218,7 @@ check('HD loader loads all images (fallbacks)', ctx.G.imagesLoaded === true);
|
|||||||
let hdFallbackOk = 0;
|
let hdFallbackOk = 0;
|
||||||
for (const name of ctx.SPRITE_MANIFEST) {
|
for (const name of ctx.SPRITE_MANIFEST) {
|
||||||
const urls = attemptedUrls[name];
|
const urls = attemptedUrls[name];
|
||||||
const ok = urls && urls.length === 2 && urls[0] === 'assets/hd/' + name + '.webp?v=59' && urls[1] === 'assets/' + name + '.png?v=59';
|
const ok = urls && urls.length === 2 && urls[0] === 'assets/hd/' + name + '.webp?v=60' && urls[1] === 'assets/' + name + '.png?v=60';
|
||||||
if (ok) hdFallbackOk++;
|
if (ok) hdFallbackOk++;
|
||||||
}
|
}
|
||||||
check('HD loader tries WebP then PNG per sprite', hdFallbackOk === ctx.SPRITE_MANIFEST.length);
|
check('HD loader tries WebP then PNG per sprite', hdFallbackOk === ctx.SPRITE_MANIFEST.length);
|
||||||
@@ -241,7 +243,7 @@ check('SD loader loads all images', ctx.G.imagesLoaded === true);
|
|||||||
let sdUrlOk = 0;
|
let sdUrlOk = 0;
|
||||||
for (const name of ctx.SPRITE_MANIFEST) {
|
for (const name of ctx.SPRITE_MANIFEST) {
|
||||||
const urls = attemptedUrls[name];
|
const urls = attemptedUrls[name];
|
||||||
const ok = urls && urls.length === 1 && urls[0] === 'assets/' + name + '.png?v=59';
|
const ok = urls && urls.length === 1 && urls[0] === 'assets/' + name + '.png?v=60';
|
||||||
if (ok) sdUrlOk++;
|
if (ok) sdUrlOk++;
|
||||||
}
|
}
|
||||||
check('SD loader requests one PNG per sprite', sdUrlOk === ctx.SPRITE_MANIFEST.length);
|
check('SD loader requests one PNG per sprite', sdUrlOk === ctx.SPRITE_MANIFEST.length);
|
||||||
@@ -1946,7 +1948,12 @@ check('floor 2', ctx.G.floor === 2);
|
|||||||
const bossRoom = Object.values(ctx.G.rooms).find(r => r.type === 'boss');
|
const bossRoom = Object.values(ctx.G.rooms).find(r => r.type === 'boss');
|
||||||
check('boss room exists', !!bossRoom);
|
check('boss room exists', !!bossRoom);
|
||||||
ctx.enterRoom(bossRoom.gx, bossRoom.gy);
|
ctx.enterRoom(bossRoom.gx, bossRoom.gy);
|
||||||
step(10, 16);
|
step(1, 16);
|
||||||
|
check('boss room opens a loading screen', ctx.G.mode === 'bossload');
|
||||||
|
drainBossLoad();
|
||||||
|
check('boss loading screen resolves to play', ctx.G.mode === 'play');
|
||||||
|
check('boss sheet warm helper exists', typeof ctx.Sprites.warm === 'function');
|
||||||
|
check('elite summoner count uses 4-6', (() => { const e = ctx.makeEnemy('broodsac', 400, 300, 3, true); return e.elite === true; })());
|
||||||
check('boss spawned', !!ctx.G.boss);
|
check('boss spawned', !!ctx.G.boss);
|
||||||
check('boss hp is finite', ctx.G.boss && isFinite(ctx.G.boss.hp));
|
check('boss hp is finite', ctx.G.boss && isFinite(ctx.G.boss.hp));
|
||||||
check('boss music playing', ctx.Music.current && ctx.Music.current.name.includes('Boss'));
|
check('boss music playing', ctx.Music.current && ctx.Music.current.name.includes('Boss'));
|
||||||
@@ -2255,6 +2262,7 @@ ctx.nextFloor(); step(5, 16);
|
|||||||
check('override survives floor change', ctx.Music.current.name === ctx.Music.PLAYLIST[1]);
|
check('override survives floor change', ctx.Music.current.name === ctx.Music.PLAYLIST[1]);
|
||||||
const jbr = Object.values(ctx.G.rooms).find(r => r.type === 'boss');
|
const jbr = Object.values(ctx.G.rooms).find(r => r.type === 'boss');
|
||||||
ctx.enterRoom(jbr.gx, jbr.gy); step(5, 16);
|
ctx.enterRoom(jbr.gx, jbr.gy); step(5, 16);
|
||||||
|
drainBossLoad();
|
||||||
check('boss track interrupts override', ctx.Music.current.name.includes('Boss'));
|
check('boss track interrupts override', ctx.Music.current.name.includes('Boss'));
|
||||||
ctx.Music.cycle(1); // cycling during a boss must not cut the boss track
|
ctx.Music.cycle(1); // cycling during a boss must not cut the boss track
|
||||||
check('boss track not interrupted', ctx.Music.current.name.includes('Boss'));
|
check('boss track not interrupted', ctx.Music.current.name.includes('Boss'));
|
||||||
|
|||||||
Reference in new issue
Block a user