Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,46 @@ HP strip visible), stage-1 tally rows + night clock, bright nonspell
reference, 夜盲 full circle, shrunk circle, still-dark bridge gap, and
the Last Spell singing in the dark.

### 2026-08-24 boss-presentation visual pass (headless-Chromium host; draw-only)

Host note: this VM has NO wine and NO podman, but DOES have local headless
Chromium (`/opt/pw-browsers`), so dev-shot/visual acceptance runs locally.
Baseline re-run of committed main on this host: stage 1 EARLIEST DIVERGENCE
f3192, stage 2 f3367 — the 2026-08-25 entry's "none observed" claim does
NOT reproduce here; treat f3192/f3367 as the standing advisory numbers.

Boss-fight fixes, each verified by frame-stepped browser captures of the
full Wriggle and Mystia fights (probe: alternate held/released Z per
30-frame batch — the dialogue machine confirms on held-bit RISING edges,
so a continuously held Z never advances the pre-boss chat):

1. **Effect-39 spell ring settle/tracking (the user-visible boss-fight
presentation break).** drawSpellRing parked the ring at a 240px
playfield-wide annulus at the DECLARATION-time boss position, at full
alpha, forever. The authored script's scale interp is 192→15 over the
120-frame settle and its fade-in tops at alpha 192/255; at the measured
declaration-time ~600px screen radius (3.125 px/scale), the settled
radius is 46.875px — the ring shrinks ONTO the boss and spins there,
and the VM rides its owner (FUN_004152a0 arms it on the boss,
all.c:9110-9126) so the draw now follows the live boss position. The
bake path uses the same settled radius. Verified on 灯符 (familiars
arranged around the settled ring, native look), 蠢符, and Mystia's
猛毒/鷹符/夜盲 cards.
2. **?stage=2 test boot** (main.ts, test-only): direct probe boot of the
Stage-2 slice so the Mystia fight is reachable without chaining a
Stage-1 clear. snapshot.ts bulletDump cap raised 64→512 (probe
visibility into dense boss patterns).

Non-defects confirmed this pass (do not re-chase): Wriggle N1's rice
"wing" hugging the boss is the authored EX chain (0x40 decel 60-90f →
turn -1.83rad → 0x10 accel [10023]≈0.045/f — frame-tracked working); the
purple rounded-rect plate behind bosses is the authored enemy.anm boss
aura; the vertical strips + kanji-card sprites above the player are the
Border-Team homing-amulet family; the kanji item boxes (刻/点) are the
authored etama2 item row. Mystia's card chain (声符 midboss → 猛毒 →
鷹符 → 夜盲, finale quota-gated) and 夜盲's player-centred vision circle
render native-faithful in the browser.

### 2026-08-24 draw-economy audit pass (port-side only; podman runner)

A full static/exe audit of the standing stage-2 f677..1237 +8 u16 draw
Expand Down
14 changes: 11 additions & 3 deletions src/game/eclvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ export const TH08_RAW_OPCODE_COVERAGE = new Set<number>([
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 139, 140, 144, 148, 153, 158, 160, 173, 174, 175,
176, 184
134, 135, 136, 139, 140, 142, 144, 145, 146, 148, 152, 153, 158, 160, 168,
173, 174, 175, 176, 184
]);

// Th08.exe .rdata table @ VA 0x4b4ad8 (file offset 0xb44d8): 21 bullet
Expand Down Expand Up @@ -603,6 +603,7 @@ export class StageRuntime {
// latter and only appears to improve survival by removing danmaku.
hp: hasLife ? life | 0 : 1,
maxHp: hasLife ? life | 0 : 1,
phaseHpCeiling: hasLife ? life | 0 : 1,
pendingShotDmg: 0,
pendingBombDmg: 0,
// DAT_018b8a24 is 40 in the v1.00d Border-Team replay runtime; the
Expand Down Expand Up @@ -665,6 +666,7 @@ export class StageRuntime {
e.ecl.itemDrop = item;
if (hasScore) e.score = score | 0;
e.maxHp = Math.max(1, e.hp);
e.phaseHpCeiling = e.maxHp;
return e;
}

Expand Down Expand Up @@ -1394,6 +1396,7 @@ export class StageRuntime {
const t = s.lifeThresholds[i];
if (t.threshold >= 0 && t.sub >= 0 && e.hp < t.threshold) {
e.hp = t.threshold;
e.phaseHpCeiling = Math.max(1, t.threshold);
t.threshold = -1;
s.timerCallbackThreshold = -1; // exe: cleared on every life-cb fire
s.timerCallbackSub = s.deathCallbackSub;
Expand All @@ -1408,7 +1411,11 @@ export class StageRuntime {
for (let i = 0; i < 4; i++) {
if (s.lifeThresholds[i].threshold > best) { best = s.lifeThresholds[i].threshold; bestIdx = i; }
}
if (best > 0 && bestIdx >= 0) { e.hp = best; s.lifeThresholds[bestIdx].threshold = -1; }
if (best > 0 && bestIdx >= 0) {
e.hp = best;
e.phaseHpCeiling = Math.max(1, best);
s.lifeThresholds[bestIdx].threshold = -1;
}
s.timerCallbackThreshold = -1;
s.timerCallbackSub = s.deathCallbackSub;
s.bossTimer = 0;
Expand Down Expand Up @@ -4104,6 +4111,7 @@ export class StageRuntime {
case 131: { // set the bullet pool ("HP") + copies
e.hp = gi(0);
e.maxHp = Math.max(1, e.hp);
e.phaseHpCeiling = e.maxHp;
t.poolCopyA = e.hp;
t.poolCopyB = e.hp;
return null;
Expand Down
2 changes: 1 addition & 1 deletion src/game/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function stageSnapshot(scene: StageScene): Record<string, unknown> {
}
return h;
}, {}),
bulletDump: scene.enemyBullets.slice(0, 64).map((b) => ({
bulletDump: scene.enemyBullets.slice(0, 512).map((b) => ({
id: b.id,
x: Math.round(b.x),
y: Math.round(b.y),
Expand Down
Loading
Loading