Files
infrapuzzle/projects/sasch40/AGENTS.md
T

11 KiB
Raw Blame History

AGENTS.md — Saschas Kronkorken Kreuzzug (sasch40)

Note

Domain: Mobile-first 2.5D retro birthday adventure game for King Sascha I.'s 40th jubilee.
Live Production URL: https://sasch40.haumdaucher.de
Kubernetes Manifest: k8s/web/sasch40.yaml (Namespace: web)
Source Code: projects/sasch40/frontend/
Reference Photos: projects/sasch40/source_photos/


🏛️ Architectural Decisions & Non-Negotiable Rules

This document records the foundational design decisions, their rationale ("Why"), and constraints for future AI coding agents. Do not regress or remove these patterns.

1. QR-Code Landing & Atmospheric Prequel (IntroScreen.vue)

  • The Decision: The application MUST ALWAYS start with an informative, atmospheric intro/lore screen before any gameplay begins.
  • Why: Party guests scan a physical QR code on beer bottles or tables with their smartphones. They do not know what the link opens. Without an introductory landing page, users are confused when suddenly dropped into an arcade mini-game.
  • Content Requirements:
    • Title: „Saschas Kronkorken Kreuzzug: 40 Jahre Bass & Gerstensaft“.
    • Royal Avatar: King Sascha I. with sunglasses and golden crown.
    • Lore: Explains the 40-year reign over Haumdaucher, 135 BPM techno culture, the theft of the 40 sacred Kronkorken, and a preview of the upcoming acts.
    • CTA: Prominent button „SPIEL STARTEN / RETTE DAS JUBILÄUM!“.
    • Audio prompt: „Smartphone-Lautstärke aufdrehen für echten 135-BPM-Mittelalter-Techno!“.

2. Dual Game Modes & Dynamic Chassis Layout (GameBoyFrame.vue)

  • The Decision: The GameBoy physical controls (D-Pad and A/B action buttons) MUST dynamically hide and show based on the active game mode.
  • Why: The game alternates between arcade mini-games (Jump & Run, Party Express) and point-and-click / visual-novel adventure scenes (Prologue, Tavern, Finale Certificate). Displaying a chunky D-Pad and large A/B buttons during dialogue or tavern exploration wastes mobile screen space and ruins immersion.
  • The Rule:
    • Arcade Acts (currentAct === 'platformer' or 'party_express'):
      • Show the GameBoy D-Pad and A/B action buttons (arcade-layout).
      • Display the arcade HUD with Korken, Helles, and 135 BPM counter.
    • Adventure / Dialogue Acts (currentAct === 'intro', 'prologue', 'tavern', or 'finale'):
      • Hide the D-Pad and A/B buttons completely (adventure-layout).
      • Display a sleek mini bottom bar with current Korken, Helles, and an inventory drawer button („🎒 Beutel“).
      • On currentAct === 'finale', hide even the mini bar so the certificate occupies the full screen.

3. Protagonist System & Extensibility (characters.ts)

  • The Decision: All character portraits are strictly 1:1 square pixel-art illustrations based directly on real-life friends and family photos, maintained in a centralized registry.
  • Why: Guests at the party must immediately recognize themselves and their friends. Previous attempts at generic AI portraits caused confusion because hair, beards, and styles did not match the actual people.
  • Reference Photos:
    • Stored in projects/sasch40/source_photos/ (sascha_*.jpg, nat.jpg, lutz*.jpg, amelie.jpg, armend.jpg, moritz2.jpg, etc.).
  • In-Game Portraits:
    • Stored in projects/sasch40/frontend/public/assets/characters/<id>/portrait.png.
  • How to Add or Modify Protagonists:
    1. Add a 1:1 square pixel-art portrait into public/assets/characters/<id>/portrait.png.
    2. Register the protagonist in projects/sasch40/frontend/src/config/characters.ts:
      export const CHARACTERS: Record<string, CharacterConfig> = {
        neuer_gast: {
          id: 'neuer_gast',
          name: 'Name (Spitzname)',
          title: 'Königlicher Hof-Titel',
          portrait: '/assets/characters/neuer_gast/portrait.png',
          quote: 'Charakteristischer Spruch'
        }
      }
      
    3. Add an interactive hotspot or convoy sprite in TavernView.vue or PartyExpressView.vue.
  • Portrait Display Rule: Always use aspect-ratio: 1 / 1 and object-fit: cover with rounded borders (border-radius: 8px or 50% for badges). Never allow portrait stretching or horizontal letterboxing.

4. Background Art Integrity & Cleanliness

  • The Decision: Background scenes must remain clean of any AI-hallucinated pseudo-English text or stray UI elements.
  • Why: AI image generators often bake random adventure-game dialogue boxes or text (e.g. [O] I have to tell you, the glow-wurst is excellent!) into the scene.
  • The Rule:
    • public/assets/scenes/tavern.jpg: The bottom area is cleaned with a seamless dark oak wood beam and floor texture.
    • Any scene footer navigation (like .scene-bottom-nav) must be styled as natural wooden wainscoting with clear German labels.

5. Mobile Canvas Safe-Zones & Responsive HUDs

  • The Decision: Critical game HUD elements (score, act title, instructions) must either be drawn as HTML/CSS overlays or placed within a minimum safe margin of 24px on canvas.
  • Why: Smartphones vary widely in screen aspect ratio (16:9, 19.5:9, 20:9). When a 360x480 canvas is rendered with object-fit: cover, the left and right (or top and bottom) edges can be cropped by 10–25px on narrow screens.
  • The Rule:
    • In PlatformerView.vue: The Korken counter and level title are rendered via an absolute HTML overlay (.platformer-hud-top) so they are 100% immune to canvas cropping.
    • In PartyExpressView.vue: The banner title is centered (ctx.textAlign = 'center'), and the progress bar has symmetrical 24px padding.

6. Royal Certificate & Competitive Highscore (CertificateModal.vue)

  • The Decision: The game concludes with a royal certificate featuring a competitive point breakdown and rank.
  • Why: Party guests compare scores at the bar to determine who is the ultimate party knight.
  • Score Breakdown:
    • Kronkorken collected: 100 pts each.
    • Helles tapped: 250 pts each.
    • Guests served in Party Express: 300 pts each.
    • Gstanzl duel victory: 1,500 pts.
    • Discovered secrets (Bridgerton dialogue, Zuluman mirror): 500 pts each.
  • Ranks:
    • S-Rang (Legendär): >= 11,000 points („Unsterblicher Großfürst des Basses“).
    • A-Rang (Erhaben): >= 9,500 points („Meister-Brauer der königlichen Garde“).
    • B-Rang (Ritterlich): >= 7,500 points („Ehren-Ritter der Hopfen-Manufaktur“).
    • C-Rang (Trinkfest): < 7,500 points („Kneipen-Knappe im Ausnüchterungs-Dienst“).
  • Call-to-Action: Includes a screenshot / print button („📸 URKUNDE DRUCKEN / SCREENSHOT“) and Queen Verena's secret postscript.

7. Zero-Dependency Web Audio Synthesizer (sound.ts)

  • The Decision: All sound effects, fanfares, and the 135 BPM techno bassline are synthesized in pure code using the Web Audio API.
  • Why: External audio files (.mp3/.wav) take megabytes of bandwidth, stutter on mobile cellular networks, and trigger browser autoplay blocks.
  • The Rule: The SoundService initializes on the first user interaction (touch/click). Sound effects use oscillators and gain envelopes; 135 BPM kick and sub-bass use recurring periodic intervals.

8. Unhashed Asset Cache Invalidation (nginx.conf)

  • The Decision: Unhashed static files (/assets/scenes/*.jpg, /assets/characters/*/*.jpg) MUST be served with Cache-Control: "no-cache, no-store, must-revalidate" and Expires: -1.
  • Why: In mobile browsers (Safari iOS, Chrome Mobile), default caching headers (expires 30d) cause updated character portraits and scene backgrounds to remain stale for weeks on user devices.
  • The Rule: In nginx.conf, static media matches \.(png|jpg|jpeg|gif|ico|svg|webp|mp3|wav|ogg)$ with no-cache, no-store, must-revalidate. Bundled JS/CSS matches \.(js|css)$ with public, immutable for 1 year.

9. Easter Egg Invisibility Pattern (TavernView.vue)

  • The Decision: Secret hotspots (e.g. secret-tap-hotspot in the Schankraum) must be 100% invisible initially.
  • Why: An Easter egg marked with a glowing button or text badge is not a secret; it invites immediate trivial clicking.
  • The Rule:
    • The button must render with transparent background, no border, no outline, no box shadow, no hover change, and no tooltip (:title="undefined").
    • Only upon the user's first click does it reveal the gold key icon, play a celebratory fanfare, unlock the Easter egg in the game store (+500 points), and transition to the secret room.

10. LocalStorage Persistence & Clean Game Reset (gameStore.ts)

  • The Decision: Game state automatically synchronizes to localStorage (sasch40_game_save_v1) on every mutation and is cleanly wiped when clicking „🔄 NOCHMAL SPIELEN“.
  • Why: Mobile users frequently refresh or switch apps to snap photos. Without persistence, a browser reload resets their progress back to Act I.
  • The Rule:
    • useGameStore automatically initializes from localStorage if valid data exists.
    • store.$subscribe writes the state to localStorage whenever any value changes.
    • resetGame() clears localStorage.removeItem(...), sets all stats to zero, and returns to the Prequel Intro.

🛠️ Build, Test & Deployment Workflow

Fast Docker Packaging (Local ARM64 Compile)

To avoid 10-minute QEMU emulation hangs when building linux/amd64 images on Apple Silicon Macs, the frontend is compiled locally on the host machine before Docker copies the static output:

cd projects/sasch40/frontend

# 1. Compile natively (< 2 seconds)
npm run build

# 2. Package, push & deploy to k8s (< 10 seconds total)
make all

The Makefile targets:

  • compile: Runs npm run build (vue-tsc -b && vite build).
  • build: Runs docker build --platform linux/amd64 -t sasch40:latest . (copies dist/ directly into nginx:alpine).
  • tag: Tags as registry.haumdaucher.de/sasch40:latest.
  • push: Pushes to the Haumdaucher registry.
  • deploy: Executes kubectl rollout restart deployment/sasch40 -n web and monitors rollout status.

Automated Headless Chrome Playtest

Before considering any change complete, run the automated Puppeteer playtest script:

cd projects/sasch40/frontend
node scripts/playtest.mjs

This script:

  1. Navigates to https://sasch40.haumdaucher.de.
  2. Verifies the Intro Screen and clicks Start.
  3. Simulates dialogue options on the Castle Balcony (Prologue).
  4. Tests movement and jump controls in Act II (Hopfen-Pfade).
  5. Interacts with hotspots, dialogue modals, and the Gstanzl duel in Act III (Taverne).
  6. Advances through the Party Express and reaches the Royal Certificate.
  7. Captures 10 screenshots to verify zero visual regressions.
  8. Asserts zero console errors, zero failed HTTP requests, and zero broken images.