feat(sasch40): improve Act III, add state persistence and fix portraits

- Fix character portrait crops and version assets to portrait_v2, ensuring correct identities (resolving Nat/Verena mismatch)
- Update Schankraum scene image and configure Nginx cache-control to prevent stale asset caching
- Make secret beer tap completely invisible in tavern until clicked
- Reposition room hotspots in Act III to eliminate overlapping click targets
- Add exit button [✕] to Bavarian Gstanzl duel minigame
- Implement localStorage persistence in gameStore to maintain progress across page reloads
- Enable clean game state and storage reset when clicking "Nochmal spielen"
- Expand automated playtest script to cover Nat dialogue, duel exit, reload persistence, and game restart
This commit is contained in:
Moritz Graf
2026-09-24 11:03:22 +02:00
parent 7a7038e2c2
commit e45dbc2e19
34 changed files with 363 additions and 78 deletions
+55 -10
View File
@@ -171,6 +171,18 @@ async function runPlaytest() {
if (actionBtn) await actionBtn.click();
await new Promise(r => setTimeout(r, 400));
}
// Click NAT (Josef)
const natBtn = await page.$('.hotspot-nat');
if (natBtn) {
await natBtn.click();
await new Promise(r => setTimeout(r, 700));
console.log('📸 09b: Capturing NAT (Josef) Dialog...');
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') });
const actionBtn = await page.$('.dialog-actions .action-btn');
if (actionBtn) await actionBtn.click();
await new Promise(r => setTimeout(r, 400));
}
}
// 7. TAVERN: HINTERLAND (Mane hiking)
@@ -196,7 +208,7 @@ async function runPlaytest() {
}
// 8. TAVERN: SECRET HINTERZIMMER (VIP Rave via Secret Tap)
console.log('🗝️ Returning to Schankraum to trigger Secret Tap...');
console.log('🗝️ Returning to Schankraum to check Secret Tap (initially invisible)...');
const schankraumTab = await page.$('.room-tab:nth-child(1)');
if (schankraumTab) {
await schankraumTab.click();
@@ -242,23 +254,46 @@ async function runPlaytest() {
await new Promise(r => setTimeout(r, 400));
}
console.log('⚔️ Testing Gstanzl-Duel...');
console.log('⚔️ Testing Gstanzl-Duel and [✕] Exit Button...');
const duelBtn = await page.$('.duel-trigger');
if (duelBtn) {
await duelBtn.click();
await new Promise(r => setTimeout(r, 700));
console.log('📸 15: Capturing Gstanzl Duel...');
console.log('📸 15: Capturing Gstanzl Duel with [✕] button...');
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15_tavern_gstanzl_duel.png') });
const firstAnswer = await page.$('.choice-btn');
if (firstAnswer) {
await firstAnswer.click();
await new Promise(r => setTimeout(r, 500));
// Test the [✕] close button
const closeDuelBtn = await page.$('.duel-close-btn');
if (closeDuelBtn) {
console.log('Testing duel exit via [✕] button...');
await closeDuelBtn.click();
await new Promise(r => setTimeout(r, 600));
const duelClosed = await page.$('.duel-box') === null;
console.log('Duel modal closed successfully by [✕]:', duelClosed);
}
// Close duel if won or modal
const actionBtn = await page.$('.duel-box .action-btn');
if (actionBtn) await actionBtn.click();
}
// Test reload persistence in Tavern Act
console.log('💾 Testing game state reload persistence...');
const stateBeforeReload = await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const pinia = app?.config?.globalProperties?.$pinia;
const s = pinia?._s.get('game');
return s ? { currentAct: s.currentAct, kronkorken: s.kronkorken, beers: s.beers } : null;
});
console.log('State before reload:', stateBeforeReload);
await page.reload({ waitUntil: 'networkidle0' });
await new Promise(r => setTimeout(r, 1000));
const stateAfterReload = await page.evaluate(() => {
const app = document.querySelector('#app')?.__vue_app__;
const pinia = app?.config?.globalProperties?.$pinia;
const s = pinia?._s.get('game');
return s ? { currentAct: s.currentAct, kronkorken: s.kronkorken, beers: s.beers } : null;
});
console.log('State after reload:', stateAfterReload);
// 10. PARTY EXPRESS (Act IV)
console.log('🚂 Advancing to Party Express (Act IV)...');
await page.evaluate(() => {
@@ -310,6 +345,16 @@ async function runPlaytest() {
console.log('📸 18: Capturing Certificate Modal...');
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '18_certificate_screen.png') });
// Test NOCHMAL SPIELEN button
console.log('🔄 Testing NOCHMAL SPIELEN button...');
const restartBtn = await page.$('.restart-btn');
if (restartBtn) {
await restartBtn.click();
await new Promise(r => setTimeout(r, 1000));
console.log('📸 19: Capturing post-restart Intro screen...');
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '19_post_restart_intro.png') });
}
// Verify all images loaded properly
const brokenImages = await page.evaluate(() => {
const imgs = Array.from(document.querySelectorAll('img'));