feat(sasch40): overhaul Act III tavern schankraum and canonical papa markus scene

- Generate and integrate authentic 16-bit LucasArts taproom scene with Theke, brass taps, bartender, drinking patrons, and bunker trapdoor
- Realign Schankraum hotspots for Verena, Lutz in hot tub, Armend at forge, room transitions, and centered bunker trapdoor
- Update Papa Markus canonical portrait and Taiwan street dining scene to be bald and clean-shaven with glasses and modern attire
- Enforce hotspot zero-occlusion rules for signposts, characters, and mobile room navigation bars
- Document Papa Markus specifications, tavern invariants, and layout guidelines in AGENTS.md
- Add automated sister capture and playthrough recording scripts
This commit is contained in:
Moritz Graf
2026-09-25 08:36:39 +02:00
parent b61cf84082
commit 07fef4547c
9 changed files with 806 additions and 17 deletions
@@ -0,0 +1,82 @@
import puppeteer from 'puppeteer-core';
import path from 'path';
const ARTIFACTS_DIR = '/Users/moritz/.gemini/antigravity/brain/4c86ae52-822a-485e-bcc7-fed5de9e5a4b/playtest_screenshots';
async function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function run() {
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: 'new',
args: ['--no-sandbox', '--window-size=412,869']
});
const page = await browser.newPage();
await page.setViewport({ width: 412, height: 869, deviceScaleFactor: 2, isMobile: true, hasTouch: true });
await page.goto('http://localhost:4173', { waitUntil: 'networkidle0' });
// Advance to platformer
await page.evaluate(() => {
const store = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia._s.get('game');
store.setAct('platformer');
});
await sleep(800);
// Close intro modal
const startBtn = await page.$('.start-act-btn');
if (startBtn) await startBtn.click();
await sleep(1000);
// Gameplay Level 1 with fixed sky background
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '10_act2_02_doodle_jump_level1.png') });
console.log('📸 Updated 10_act2_02_doodle_jump_level1.png');
// Trigger Saskia Rescue
await page.evaluate(() => {
if (window.__platformer) window.__platformer.setRescuedSister('saskia');
});
await sleep(600);
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '11_act2_03_rescue_saskia.png') });
console.log('📸 Captured 11_act2_03_rescue_saskia.png');
// Click proceed button
const proceedBtn1 = await page.$('.proceed-btn');
if (proceedBtn1) await proceedBtn1.click();
await sleep(1000);
// Level 2 Gameplay
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '12_act2_04_doodle_jump_level2.png') });
console.log('📸 Updated 12_act2_04_doodle_jump_level2.png');
// Trigger Maja Rescue
await page.evaluate(() => {
if (window.__platformer) window.__platformer.setRescuedSister('maja');
});
await sleep(600);
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '13_act2_05_rescue_maja.png') });
console.log('📸 Captured 13_act2_05_rescue_maja.png');
// Click proceed button
const proceedBtn2 = await page.$('.proceed-btn');
if (proceedBtn2) await proceedBtn2.click();
await sleep(1000);
// Level 3 Gameplay
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '14_act2_06_doodle_jump_level3.png') });
console.log('📸 Updated 14_act2_06_doodle_jump_level3.png');
// Trigger Miriam Rescue
await page.evaluate(() => {
if (window.__platformer) window.__platformer.setRescuedSister('miriam');
});
await sleep(600);
await page.screenshot({ path: path.join(ARTIFACTS_DIR, '15_act2_07_rescue_miriam.png') });
console.log('📸 Captured 15_act2_07_rescue_miriam.png');
await browser.close();
}
run();
@@ -0,0 +1,683 @@
import puppeteer from 'puppeteer-core';
import path from 'path';
import fs from 'fs';
const ARTIFACTS_DIR = '/Users/moritz/.gemini/antigravity/brain/4c86ae52-822a-485e-bcc7-fed5de9e5a4b';
const SCREENSHOTS_DIR = path.join(ARTIFACTS_DIR, 'playtest_screenshots');
if (!fs.existsSync(SCREENSHOTS_DIR)) {
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
}
async function takeScreenshot(page, filename, delay = 300) {
if (delay > 0) await new Promise(r => setTimeout(r, delay));
const fullPath = path.join(SCREENSHOTS_DIR, filename);
try {
await page.screenshot({ path: fullPath, timeout: 20000 });
console.log(`📸 Saved: ${filename}`);
} catch (err) {
console.warn(`⚠️ Warning: Screenshot ${filename} failed:`, err.message);
}
}
async function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function run() {
console.log('🚀 Starting Full Playthrough & Screenshot Capture for Saschas Kronkorken Kreuzzug...');
const browser = await puppeteer.launch({
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
headless: 'new',
protocolTimeout: 60000,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--window-size=412,869'
]
});
const page = await browser.newPage();
await page.setViewport({
width: 412,
height: 869,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
});
const logs = [];
const errors = [];
const failedRequests = [];
page.on('console', msg => {
const text = msg.text();
logs.push(`[${msg.type().toUpperCase()}] ${text}`);
if (msg.type() === 'error') errors.push(text);
});
page.on('pageerror', err => errors.push(err.toString()));
page.on('requestfailed', req => failedRequests.push(`${req.url()} (${req.failure()?.errorText || 'failed'})`));
try {
const targetUrl = 'http://localhost:4173';
console.log(`🌐 Navigating to ${targetUrl}...`);
await page.goto(targetUrl, { waitUntil: 'networkidle0', timeout: 30000 });
// Clean start: clear local storage and reload
await page.evaluate(() => {
localStorage.clear();
window.location.reload();
});
await page.waitForNavigation({ waitUntil: 'networkidle0' });
await sleep(800);
// ==========================================
// 1. INTRO / PREQUEL SCREEN
// ==========================================
console.log('--- 1. Intro Screen ---');
await takeScreenshot(page, '01_intro_qr_landing.png', 500);
// Test Global Settings Modal
console.log('--- 2. Global Settings Modal ---');
const settingsBtn = await page.$('.settings-toggle-btn');
if (settingsBtn) {
await settingsBtn.click();
await sleep(600);
await takeScreenshot(page, '02_settings_pause_modal.png', 300);
const resumeBtn = await page.$('.resume-btn');
if (resumeBtn) await resumeBtn.click();
await sleep(500);
}
// Click Start Game
console.log('--- 3. Starting Game -> Prologue ---');
await page.click('.start-btn');
await sleep(1000);
// ==========================================
// 2. PROLOGUE (BALCONY SCENE)
// ==========================================
console.log('--- Prologue: NAT Opening ---');
await takeScreenshot(page, '03_prologue_01_nat_balcony.png', 500);
// Click NAT response -> Flo Maier arrives
const natChoice = await page.$('.choice-btn');
if (natChoice) await natChoice.click();
await sleep(800);
console.log('--- Prologue: Flo Maier Feierdeifl ---');
await takeScreenshot(page, '04_prologue_02_flo_feierdeifl.png', 400);
// Test Flo Game Over branch: "Ja freilich, zünd ois o, Flo!"
const fireChoice = await page.$('.choice-btn:nth-child(1)');
if (fireChoice) await fireChoice.click();
await sleep(800);
console.log('--- Prologue: Flo Castle Inferno Game Over ---');
await takeScreenshot(page, '05_prologue_03_flo_game_over.png', 400);
// Reset via Löschzug
const resetFireBtn = await page.$('.game-over-btn');
if (resetFireBtn) await resetFireBtn.click();
await sleep(1000);
// Skip NAT to Flo
await page.waitForSelector('.choice-btn', { timeout: 4000 });
const natChoice2 = await page.$('.choice-btn');
if (natChoice2) await natChoice2.click();
await sleep(800);
// Decline Flo -> Viktor arrives
const declineFloBtn = await page.$('.choice-btn:nth-child(2)');
if (declineFloBtn) await declineFloBtn.click();
await sleep(800);
console.log('--- Prologue: Viktor Partyvampir ---');
await takeScreenshot(page, '06_prologue_04_viktor_partyvampir.png', 400);
// Test Viktor Game Over branch: "Ja geil, mach mi unsterblich!"
const biteChoice = await page.$('.choice-btn:nth-child(1)');
if (biteChoice) await biteChoice.click();
await sleep(800);
console.log('--- Prologue: Viktor Glitter Dust Game Over ---');
await takeScreenshot(page, '07_prologue_05_viktor_game_over.png', 400);
// Reset via Knoblauch-Kur
const resetVampBtn = await page.$('.game-over-btn');
if (resetVampBtn) await resetVampBtn.click();
await sleep(1000);
// Advance safely to Mop: NAT -> decline Flo -> decline Viktor
await page.waitForSelector('.choice-btn', { timeout: 4000 });
const nc = await page.$('.choice-btn');
if (nc) await nc.click();
await sleep(600);
const fc = await page.$('.choice-btn:nth-child(2)');
if (fc) await fc.click();
await sleep(600);
const vc = await page.$('.choice-btn:nth-child(2)');
if (vc) await vc.click();
await sleep(800);
console.log('--- Prologue: Mop Sister Rescue Quest ---');
await takeScreenshot(page, '08_prologue_06_mop_quest.png', 400);
// Click to advance to Akt II (Doodle Jump Schwesternrettung)
const startAkt2Btn = await page.$('.choice-btn');
if (startAkt2Btn) await startAkt2Btn.click();
await sleep(1000);
// ==========================================
// 3. AKT II: DOODLE JUMP SCHWESTERNRETTUNG
// ==========================================
console.log('--- Akt II: Intro Modal ---');
await takeScreenshot(page, '09_act2_01_doodle_jump_intro.png', 500);
// Click Start Act Button on modal
const startDoodleBtn = await page.$('.start-act-btn');
if (startDoodleBtn) await startDoodleBtn.click();
await sleep(1200);
console.log('--- Akt II: Level 1 Gameplay (Saskia) ---');
await takeScreenshot(page, '10_act2_02_doodle_jump_level1.png', 400);
// Trigger Sister 1 Rescue (Saskia)
await page.evaluate(() => {
const el = document.querySelector('.platformer-viewport');
const exposed = el?.__vueParentComponent?.exposed;
if (exposed?.triggerSisterRescue) {
exposed.triggerSisterRescue('saskia');
} else {
// Fallback: search components in tree
const app = document.querySelector('#app')?.__vue_app__;
const findComp = (inst) => {
if (!inst) return null;
if (inst.exposed?.triggerSisterRescue) return inst.exposed;
if (inst.ctx?.triggerSisterRescue) return inst.ctx;
if (inst.subTree?.children) {
for (const c of inst.subTree.children) {
const res = findComp(c?.component);
if (res) return res;
}
}
return null;
};
const target = findComp(app?._instance);
if (target) target.triggerSisterRescue('saskia');
}
});
await sleep(800);
const rescueOverlay1 = await page.$('.rescue-overlay');
if (rescueOverlay1) {
console.log('--- Akt II: Rescued Saskia Modal ---');
await takeScreenshot(page, '11_act2_03_rescue_saskia.png', 400);
const proceedBtn1 = await page.$('.proceed-btn');
if (proceedBtn1) await proceedBtn1.click();
await sleep(1000);
}
console.log('--- Akt II: Level 2 Gameplay (Maja) ---');
await takeScreenshot(page, '12_act2_04_doodle_jump_level2.png', 400);
// Trigger Sister 2 Rescue (Maja)
await page.evaluate(() => {
const el = document.querySelector('.platformer-viewport');
const exposed = el?.__vueParentComponent?.exposed;
if (exposed?.triggerSisterRescue) {
exposed.triggerSisterRescue('maja');
} else {
const app = document.querySelector('#app')?.__vue_app__;
const findComp = (inst) => {
if (!inst) return null;
if (inst.exposed?.triggerSisterRescue) return inst.exposed;
if (inst.ctx?.triggerSisterRescue) return inst.ctx;
if (inst.subTree?.children) {
for (const c of inst.subTree.children) {
const res = findComp(c?.component);
if (res) return res;
}
}
return null;
};
const target = findComp(app?._instance);
if (target) target.triggerSisterRescue('maja');
}
});
await sleep(800);
const rescueOverlay2 = await page.$('.rescue-overlay');
if (rescueOverlay2) {
console.log('--- Akt II: Rescued Maja Modal ---');
await takeScreenshot(page, '13_act2_05_rescue_maja.png', 400);
const proceedBtn2 = await page.$('.proceed-btn');
if (proceedBtn2) await proceedBtn2.click();
await sleep(1000);
}
console.log('--- Akt II: Level 3 Gameplay (Miriam) ---');
await takeScreenshot(page, '14_act2_06_doodle_jump_level3.png', 400);
// Trigger Sister 3 Rescue (Miriam)
await page.evaluate(() => {
const el = document.querySelector('.platformer-viewport');
const exposed = el?.__vueParentComponent?.exposed;
if (exposed?.triggerSisterRescue) {
exposed.triggerSisterRescue('miriam');
} else {
const app = document.querySelector('#app')?.__vue_app__;
const findComp = (inst) => {
if (!inst) return null;
if (inst.exposed?.triggerSisterRescue) return inst.exposed;
if (inst.ctx?.triggerSisterRescue) return inst.ctx;
if (inst.subTree?.children) {
for (const c of inst.subTree.children) {
const res = findComp(c?.component);
if (res) return res;
}
}
return null;
};
const target = findComp(app?._instance);
if (target) target.triggerSisterRescue('miriam');
}
});
await sleep(800);
const rescueOverlay3 = await page.$('.rescue-overlay');
if (rescueOverlay3) {
console.log('--- Akt II: Rescued Miriam Modal ---');
await takeScreenshot(page, '15_act2_07_rescue_miriam.png', 400);
const proceedBtn3 = await page.$('.proceed-btn');
if (proceedBtn3) await proceedBtn3.click();
await sleep(1200);
}
// ==========================================
// 4. AKT III: TAVERNE & VILLAGE HUB
// ==========================================
console.log('--- Akt III: Tavern Room 1 - Schankraum ---');
// Ensure we are in tavern act
await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const store = app?.config?.globalProperties?.$pinia?._s.get('game');
if (store) store.setAct('tavern');
});
await sleep(1000);
await takeScreenshot(page, '16_act3_01_tavern_schankraum.png', 600);
// Verena Dialogue
console.log('--- Schankraum: Verena Dialog ---');
const verenaBtn = await page.$('.hotspot-verena');
if (verenaBtn) {
await verenaBtn.click();
await sleep(600);
await takeScreenshot(page, '17_act3_02_verena_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Armend Dialogue & Beer Tap
console.log('--- Schankraum: Armend Dialog ---');
const armendBtn = await page.$('.hotspot-armend');
if (armendBtn) {
await armendBtn.click();
await sleep(600);
await takeScreenshot(page, '18_act3_03_armend_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Lutz Dialogue
console.log('--- Schankraum: Lutz Dialog ---');
const lutzBtn = await page.$('.hotspot-lutz');
if (lutzBtn) {
await lutzBtn.click();
await sleep(600);
await takeScreenshot(page, '19_act3_04_lutz_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Room 2: Nebenraum (Kemenate / Atelier)
console.log('--- Akt III: Room 2 - Nebenraum ---');
const nrTab = await page.$('.room-tab:nth-child(2)');
if (nrTab) await nrTab.click();
await sleep(800);
await takeScreenshot(page, '20_act3_05_nebenraum_overview.png', 500);
// Amy Dialogue
console.log('--- Nebenraum: Amy Dialog ---');
const amyBtn = await page.$('.hotspot-amy-nebenraum');
if (amyBtn) {
await amyBtn.click();
await sleep(600);
await takeScreenshot(page, '21_act3_06_amy_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Zuluman Magic Mirror
console.log('--- Nebenraum: Zuluman Mirror ---');
const mirrorBtn = await page.$('.hotspot-mirror-nebenraum');
if (mirrorBtn) {
await mirrorBtn.click();
await sleep(600);
await takeScreenshot(page, '22_act3_07_zuluman_mirror.png', 300);
const closeBtn = await page.$('.mirror-box .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Room 3: Marktplatz
console.log('--- Akt III: Room 3 - Marktplatz ---');
const marktplatzTab = await page.$('.room-tab:nth-child(3)');
if (marktplatzTab) await marktplatzTab.click();
await sleep(800);
await takeScreenshot(page, '23_act3_08_marktplatz_overview.png', 500);
// Mario Chainsaw Juggling Dialog
console.log('--- Marktplatz: Mario Dialog ---');
const marioBtn = await page.$('.hotspot-mario');
if (marioBtn) {
await marioBtn.click();
await sleep(600);
await takeScreenshot(page, '24_act3_09_mario_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// NAT Stage Dialog
console.log('--- Marktplatz: NAT Dialog ---');
const natTavernBtn = await page.$('.hotspot-nat');
if (natTavernBtn) {
await natTavernBtn.click();
await sleep(600);
await takeScreenshot(page, '25_act3_10_nat_dialog.png', 300);
// Start Wildschwein Duel Intro
const startBoarIntroBtn = await page.$('.dialog-actions .action-btn');
if (startBoarIntroBtn) {
await startBoarIntroBtn.click();
await sleep(700);
console.log('--- Marktplatz: Wildschwein Duel Intro ---');
await takeScreenshot(page, '26_act3_11_wildschwein_duel_intro.png', 300);
// Start Duel
const startDuelActionBtn = await page.$('.start-duel-action-btn');
if (startDuelActionBtn) {
await startDuelActionBtn.click();
await sleep(600);
// Click Keiler
const keilerBtn = await page.$('.boar-choice-btn:nth-child(1)');
if (keilerBtn) await keilerBtn.click();
await sleep(700);
console.log('--- Marktplatz: Wildschwein Duel Round Clash ---');
await takeScreenshot(page, '27_act3_12_wildschwein_duel_round.png', 300);
// Close duel
const closeDuelBtn = await page.$('.boar-duel-box .duel-close-btn');
if (closeDuelBtn) await closeDuelBtn.click();
await sleep(400);
}
}
}
// Room 4: Alpines Hinterland
console.log('--- Akt III: Room 4 - Alpines Hinterland ---');
const hinterlandTab = await page.$('.room-tab:nth-child(4)');
if (hinterlandTab) await hinterlandTab.click();
await sleep(800);
await takeScreenshot(page, '28_act3_13_hinterland_overview.png', 500);
// Mane Dialog
console.log('--- Hinterland: Mane Dialog ---');
const maneBtn = await page.$('.hotspot-mane');
if (maneBtn) {
await maneBtn.click();
await sleep(600);
await takeScreenshot(page, '29_act3_14_mane_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Hansi Dialog
console.log('--- Hinterland: Hansi Dialog ---');
const hansiBtn = await page.$('.hotspot-hansi');
if (hansiBtn) {
await hansiBtn.click();
await sleep(600);
await takeScreenshot(page, '30_act3_15_hansi_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Room 5: Hinter den Bergen (Taiwan Valley & Papa Markus)
console.log('--- Akt III: Room 5 - Hinter den Bergen ---');
const hdbTab = await page.$('.room-tab:nth-child(5)');
if (hdbTab) await hdbTab.click();
await sleep(1000);
await takeScreenshot(page, '31_act3_16_hinter_den_bergen_overview.png', 500);
// Papa Markus Dialog
console.log('--- Hinter den Bergen: Papa Markus Dialog ---');
const markusBtn = await page.$('.hotspot-markus');
if (markusBtn) {
await markusBtn.click();
await sleep(600);
await takeScreenshot(page, '32_act3_17_markus_dialog.png', 300);
// Start Liar's Dice Intro
const startDiceIntroBtn = await page.$('.dialog-actions .action-btn');
if (startDiceIntroBtn) {
await startDiceIntroBtn.click();
await sleep(700);
console.log('--- Hinter den Bergen: Liar\'s Dice Intro ---');
await takeScreenshot(page, '33_act3_18_liars_dice_intro.png', 300);
// Start Liar's Dice Match
const startDiceMatchBtn = await page.$('.start-duel-action-btn');
if (startDiceMatchBtn) {
await startDiceMatchBtn.click();
await sleep(1000);
console.log('--- Hinter den Bergen: Liar\'s Dice Gameplay ---');
await takeScreenshot(page, '34_act3_19_liars_dice_gameplay.png', 300);
// Place Bid
const bidBtn = await page.$('.bid-action-btn');
if (bidBtn) {
await bidBtn.click();
await sleep(1500); // wait for Papa Markus counter-bid
}
// Call Bluff
const bluffBtn = await page.$('.call-bluff-btn');
if (bluffBtn) {
await bluffBtn.click();
await sleep(1200); // shot animation
console.log('--- Hinter den Bergen: Liar\'s Dice Bluff & Shot ---');
await takeScreenshot(page, '35_act3_20_liars_dice_bluff_shot.png', 300);
}
// Close Liar's Dice
const closeDiceBtn = await page.$('.liars-dice-box .duel-close-btn');
if (closeDiceBtn) await closeDiceBtn.click();
await sleep(400);
}
}
}
// Back to Schankraum: Test Secret Floor Trapdoor (Double-Click)
console.log('--- Schankraum: Double-Click Trapdoor to Party Bunker ---');
const schankraumTab = await page.$('.room-tab:nth-child(1)');
if (schankraumTab) await schankraumTab.click();
await sleep(800);
const trapdoor = await page.$('.trapdoor-hotspot');
if (trapdoor) {
await trapdoor.click();
await sleep(150);
await trapdoor.click();
await sleep(1000);
// Captured Party Bunker
console.log('--- VIP Party Bunker Overview ---');
await takeScreenshot(page, '37_act3_22_party_bunker_overview.png', 600);
// Pü Dialog
const pueBtn = await page.$('.hotspot-pue');
if (pueBtn) {
await pueBtn.click();
await sleep(600);
console.log('--- Party Bunker: Pü Dialog ---');
await takeScreenshot(page, '38_act3_23_pue_dialog.png', 300);
const closeBtn = await page.$('.dialog-actions .action-btn');
if (closeBtn) await closeBtn.click();
await sleep(400);
}
// Return to Schankraum to see revealed trapdoor
const backDoor = await page.$('.hotspot-door-back-vip');
if (backDoor) {
await backDoor.click();
await sleep(800);
console.log('--- Schankraum: Trapdoor Revealed ---');
await takeScreenshot(page, '36_act3_21_trapdoor_revealed.png', 400);
}
}
// Minigame: Bayerisches Gstanzl-Fechten
console.log('--- Tavern: Bayerisches Gstanzl-Fechten ---');
const gstanzlTrigger = await page.$('.duel-trigger');
if (gstanzlTrigger) {
await gstanzlTrigger.click();
await sleep(600);
console.log('--- Gstanzl: Rules Intro ---');
await takeScreenshot(page, '39_act3_24_gstanzl_intro.png', 300);
const startGstanzlBtn = await page.$('.start-duel-action-btn');
if (startGstanzlBtn) {
await startGstanzlBtn.click();
await sleep(700);
console.log('--- Gstanzl: Duel Battle Gameplay ---');
await takeScreenshot(page, '40_act3_25_gstanzl_battle.png', 300);
const closeGstanzl = await page.$('.duel-box .duel-close-btn');
if (closeGstanzl) await closeGstanzl.click();
await sleep(400);
}
}
// ==========================================
// 5. AKT IV: MITTELALTER GUITAR HERO
// ==========================================
console.log('--- Akt IV: Castle Stage Rock Concert ---');
await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const store = app?.config?.globalProperties?.$pinia?._s.get('game');
if (store) store.setAct('party_express');
});
await sleep(1000);
console.log('--- Akt IV: Concert Intro Modal ---');
await takeScreenshot(page, '41_act4_01_lute_hero_intro.png', 500);
const startConcertBtn = await page.$('.start-act-btn');
if (startConcertBtn) await startConcertBtn.click();
await sleep(1500);
// Hit a few notes on the strings
const lane0 = await page.$('.btn-lane-0');
const lane1 = await page.$('.btn-lane-1');
const lane2 = await page.$('.btn-lane-2');
if (lane0) await lane0.click();
await sleep(150);
if (lane1) await lane1.click();
await sleep(150);
if (lane2) await lane2.click();
await sleep(250);
console.log('--- Akt IV: Concert Gameplay ---');
await takeScreenshot(page, '42_act4_02_lute_hero_gameplay.png', 400);
// Trigger Concert Finish
await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const findComp = (inst) => {
if (!inst) return null;
if (inst.finishConcert) return inst;
if (inst.subTree?.children) {
for (const c of inst.subTree.children) {
const res = findComp(c?.component?.ctx);
if (res) return res;
}
}
return null;
};
const target = findComp(app?._instance);
if (target) target.finishConcert();
});
await sleep(800);
console.log('--- Akt IV: Concert Victory Screen ---');
await takeScreenshot(page, '43_act4_03_lute_hero_victory.png', 400);
// ==========================================
// 6. AKT V: FINALE & ROYAL CERTIFICATE
// ==========================================
console.log('--- Akt V: Royal Certificate Screen ---');
await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const store = app?.config?.globalProperties?.$pinia?._s.get('game');
if (store) {
store.addKronkorken(35);
store.serveGuest();
store.serveGuest();
store.setGstanzlWon();
store.unlockEasterEgg('hinterzimmer');
store.unlockEasterEgg('zuluman_mirror');
store.setAct('finale');
}
});
await sleep(1500);
await takeScreenshot(page, '44_act5_01_royal_certificate.png', 800);
// Check for broken images or console errors
const brokenImages = await page.evaluate(() => {
const imgs = Array.from(document.querySelectorAll('img'));
return imgs
.filter(img => !img.complete || img.naturalWidth === 0)
.map(img => ({ src: img.src, alt: img.alt }));
});
console.log('\n==========================================');
console.log('🎉 PLAYTHROUGH COMPLETE!');
console.log(`📸 Total Screenshots Captured: ${fs.readdirSync(SCREENSHOTS_DIR).length}`);
console.log(`⚠️ Console Errors: ${errors.length}`);
if (errors.length > 0) console.log(errors);
console.log(`❌ Failed Requests: ${failedRequests.length}`);
if (failedRequests.length > 0) console.log(failedRequests);
console.log(`🖼️ Broken Images: ${brokenImages.length}`);
if (brokenImages.length > 0) console.log(brokenImages);
console.log('==========================================\n');
} catch (err) {
console.error('Fatal error during playthrough:', err);
await takeScreenshot(page, '99_fatal_error.png', 100);
} finally {
await browser.close();
console.log('🏁 Browser closed.');
}
}
run();