music: re-enable background loop (default = playful) + profile on/off; coins 2000
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 28s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m8s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 1m8s

- sound.ts: restored startMusic (was a no-op stub) playing the selected track
  through musicGain (in-game mute still applies); default track switched to
  "playful" (per user). Music auto-starts on init when enabled.
- Profile → Audio: re-added a music on/off toggle (so players can disable it
  outside the game too); SFX toggle unchanged.
- Economy tune: starting coins 1000 → 2000 (mock defaultProfile + server
  ProfileDto) so new players start a bit richer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-15 13:23:08 +03:30
parent 6502b17356
commit 6530096994
4 changed files with 35 additions and 7 deletions
+30 -3
View File
@@ -38,7 +38,7 @@ class SoundManager {
sfxEnabled = loadBool(LS_SFX);
musicEnabled = loadBool(LS_MUSIC);
musicTrack: MusicTrack =
(typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "santoor";
(typeof window !== "undefined" && (localStorage.getItem(LS_TRACK) as MusicTrack)) || "playful";
/** Must be called from a user gesture to unlock audio. */
init() {
@@ -210,8 +210,35 @@ class SoundManager {
},
};
/** Background music was removed from the game — these are inert no-ops. */
startMusic() {}
/** Start the ambient loop for the current track (plays through musicGain so
* the in-game mute / music volume apply). Idempotent + needs an unlocked ctx. */
startMusic() {
if (!this.musicEnabled || this.musicTimer || !this.ctx || !this.musicGain) return;
const tick = () => {
if (!this.ctx || !this.musicGain) return;
const cfg = this.TRACKS[this.musicTrack];
const now = this.ctx.currentTime;
const freq = cfg.notes[this.step % cfg.notes.length];
this.step++;
const voice = (f: number, scale = 1) => {
const osc = this.ctx!.createOscillator();
const g = this.ctx!.createGain();
osc.type = cfg.type;
osc.frequency.value = f;
g.gain.setValueAtTime(0.0001, now);
g.gain.linearRampToValueAtTime(cfg.peak * scale, now + cfg.attack);
g.gain.exponentialRampToValueAtTime(0.0001, now + cfg.dur);
osc.connect(g);
g.connect(this.musicGain!);
osc.start(now);
osc.stop(now + cfg.dur + 0.05);
};
voice(freq);
if (cfg.fifth) voice(freq * 1.5, 0.45); // soft fifth above
};
tick();
this.musicTimer = setInterval(tick, this.TRACKS[this.musicTrack].gap);
}
stopMusic() {
if (this.musicTimer) {