feat(sasch40): major overhaul with doodle jump, party bunker, and guitar hero

- Add global settings & pause modal (gear icon) with mute and reset, removing obsolete bag button
- Extend Prologue with Flo Maier (castle fire game over & Löschzug restart) and Viktor (vampire bite game over & Knoblauch restart)
- Rebuild Akt II as 3-level vertical Doodle Jump climber to rescue sisters Saskia, Maja, and Miriam with custom pixel portraits
- Update Akt III Schankraum with seamless oak floor, double-tap 'Luke zum Party Bunker', duel intro modals, and Hansi's departure in Hinterland
- Rebuild Akt IV as 3-lane medieval Guitar Hero rock concert with live crowd meter, combo multipliers, and Hansi duo
- Update AGENTS.md architectural documentation and playtest test suite
This commit is contained in:
Moritz Graf
2026-09-24 18:22:28 +02:00
parent 6374ba9e8a
commit 8a0b65c3fa
22 changed files with 2307 additions and 1250 deletions
@@ -162,6 +162,116 @@ class SoundEngine {
})
}
// SFX: Sister Rescue Fanfare (bright sweet jingle)
public playRescueFanfare() {
if (this.isMuted) return
this.initCtx()
if (!this.ctx) return
const notes = [523.25, 659.25, 783.99, 1046.50] // C5, E5, G5, C6
notes.forEach((freq, idx) => {
if (!this.ctx) return
const now = this.ctx.currentTime + idx * 0.1
const osc = this.ctx.createOscillator()
const gain = this.ctx.createGain()
osc.type = 'sine'
osc.frequency.setValueAtTime(freq, now)
gain.gain.setValueAtTime(0.25, now)
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.35)
osc.connect(gain)
gain.connect(this.ctx.destination)
osc.start(now)
osc.stop(now + 0.35)
})
}
// SFX: Spring / Trampoline Bounce (Doodle Jump super bounce)
public playSpringBounce() {
if (this.isMuted) return
this.initCtx()
if (!this.ctx) return
const now = this.ctx.currentTime
const osc = this.ctx.createOscillator()
const gain = this.ctx.createGain()
osc.type = 'triangle'
osc.frequency.setValueAtTime(220, now)
osc.frequency.exponentialRampToValueAtTime(880, now + 0.25)
gain.gain.setValueAtTime(0.3, now)
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3)
osc.connect(gain)
gain.connect(this.ctx.destination)
osc.start(now)
osc.stop(now + 0.3)
}
// SFX: Hit / Impact / Thud
public playBoarHit() {
if (this.isMuted) return
this.initCtx()
if (!this.ctx) return
const now = this.ctx.currentTime
const osc = this.ctx.createOscillator()
const gain = this.ctx.createGain()
osc.type = 'sawtooth'
osc.frequency.setValueAtTime(160, now)
osc.frequency.exponentialRampToValueAtTime(40, now + 0.2)
gain.gain.setValueAtTime(0.3, now)
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.2)
osc.connect(gain)
gain.connect(this.ctx.destination)
osc.start(now)
osc.stop(now + 0.2)
}
// SFX: Medieval Lute Hero Note (String Index 0: Low, 1: Mid, 2: High)
public playLuteNote(lane: number, success: boolean = true) {
if (this.isMuted) return
this.initCtx()
if (!this.ctx) return
const now = this.ctx.currentTime
const osc = this.ctx.createOscillator()
const gain = this.ctx.createGain()
if (!success) {
// Missed clunk / muted buzz
osc.type = 'sawtooth'
osc.frequency.setValueAtTime(90, now)
gain.gain.setValueAtTime(0.12, now)
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12)
} else {
// Resonant lute string twang
const freqs = [293.66, 392.00, 587.33] // D4, G4, D5
const freq = freqs[lane % 3] || 440
osc.type = 'triangle'
osc.frequency.setValueAtTime(freq, now)
gain.gain.setValueAtTime(0.28, now)
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.32)
}
osc.connect(gain)
gain.connect(this.ctx.destination)
osc.start(now)
osc.stop(now + (success ? 0.32 : 0.12))
}
// 135 BPM Medieval Techno Chiptune Beat
public startBGM() {
if (this.isMuted || this.bgmPlaying) return