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:
@@ -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();
|
||||
Reference in New Issue
Block a user