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
@@ -166,7 +166,8 @@ function onRight(pressed: boolean) {
defineExpose({
onJump,
onLeft,
onRight
onRight,
triggerSisterRescue
})
// Touch controls on screen halves
@@ -357,9 +358,11 @@ function drawGame(ctx: CanvasRenderingContext2D) {
// 1. Background sky parallax
if (bgImg && bgImg.complete && bgImg.naturalWidth > 0) {
const scrollFactor = (cameraY / (levelHeight - H))
const bgY = -scrollFactor * (bgImg.naturalHeight - H)
ctx.drawImage(bgImg, 0, bgY, W, bgImg.naturalHeight * (W / bgImg.naturalWidth))
const drawnH = bgImg.naturalHeight * (W / bgImg.naturalWidth)
const maxScroll = Math.max(0, drawnH - H)
const scrollFactor = Math.max(0, Math.min(1, cameraY / (levelHeight - H)))
const bgY = -scrollFactor * maxScroll
ctx.drawImage(bgImg, 0, bgY, W, drawnH)
} else {
// Fallback sky gradient
const skyGrad = ctx.createLinearGradient(0, 0, 0, H)
@@ -603,6 +606,11 @@ function gameLoop() {
onMounted(() => {
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
;(window as any).__platformer = {
triggerSisterRescue,
setRescuedSister: (s: any) => { rescuedSister.value = s },
startLevel
}
initCurrentLevel()
animationFrameId = requestAnimationFrame(gameLoop)
})
@@ -610,6 +618,7 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
delete (window as any).__platformer
if (animationFrameId) cancelAnimationFrame(animationFrameId)
})
</script>
@@ -1270,8 +1270,8 @@ function proceedToExpress() {
/* Trapdoor to Party Bunker Easter Egg */
.trapdoor-hotspot {
position: absolute;
top: 76%;
left: 32%;
top: 86%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(212, 175, 55, 0.25);
border: 2px dashed #ffd700;
@@ -1313,11 +1313,11 @@ function proceedToExpress() {
}
/* 1. Schankraum Hotspots */
.hotspot-verena { top: 44%; left: 10%; }
.hotspot-door-nebenraum { top: 36%; left: 54%; }
.hotspot-armend { top: 58%; left: 22%; }
.hotspot-lutz { top: 48%; left: 38%; }
.hotspot-door-marktplatz { top: 90%; left: 18%; }
.hotspot-verena { top: 16%; left: 36%; }
.hotspot-door-nebenraum { top: 64%; left: 14%; }
.hotspot-armend { top: 62%; left: 84%; }
.hotspot-lutz { top: 22%; left: 83%; }
.hotspot-door-marktplatz { top: 44%; left: 78%; }
/* 2. Nebenraum Hotspots */
.hotspot-mirror-nebenraum { top: 36%; left: 24%; }
@@ -1402,13 +1402,13 @@ function proceedToExpress() {
transform: rotate(20deg);
}
.hotspot-hansi { top: 48%; left: 66%; }
.hotspot-door-hinter-den-bergen { top: 38%; left: 20%; }
.hotspot-hansi { top: 30%; left: 64%; }
.hotspot-door-hinter-den-bergen { top: 34%; left: 24%; }
.hotspot-door-back-marktplatz { top: 80%; left: 16%; }
/* 5. Hinter den Bergen Hotspots */
.hotspot-markus { top: 68%; left: 50%; }
.hotspot-door-back-hinterland { top: 88%; left: 16%; }
.hotspot-markus { top: 70%; left: 52%; }
.hotspot-door-back-hinterland { top: 90%; left: 16%; }
/* 6. Hinterzimmer Hotspots & Strobe */
.strobe-overlay {