Muzica ambient T10: arpegiu calm care accelereaza sub 1 min
Opt-in din builder (checkbox music, default off). Orchestrator-only: parintele detine AudioContext (reutilizeaza beep._ctx); camerele nu stiu de muzica. - arpegiu pentatonica minora (oscilatoare sine scurte), tempo ~1.8x pe ultimul minut (legat de _deadline-ul Timer Calm); fara timer -> loop calm fara accelerare - buton 🎵/🔇 in bara chrome (#btn-music) - duck pe voce: voiceSay onstart/onend regleaza gain (vocea are prioritate) - fallback fara AudioContext -> no-op, buton ascuns (zero penalizare) - porneste la start + resume; stop la showFinale + toggle - hook test window.__music; exemplu-campanie.html regenerat (ramane opt-in off) Smoke 30/30 (test nou "muzica ambient": opt-in, start, tempo sub 1 min, duck, toggle). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ Smoke + regresie + campanie E2E pentru jocurile generate. Verifică faptic: fiec
|
||||
până la ecranul final, fără erori de consolă.
|
||||
|
||||
## Ownership
|
||||
- `tests/smoke.mjs` — unicul fișier de teste (~29 teste).
|
||||
- `tests/smoke.mjs` — unicul fișier de teste (~30 teste).
|
||||
- `playwright.config.mjs` (la root, **gitignored**) — config dev.
|
||||
|
||||
## Local Contracts
|
||||
@@ -18,9 +18,9 @@ până la ecranul final, fără erori de consolă.
|
||||
fiecare test asertează `errors.length === 0` la final.
|
||||
- **Tag-uri:** `@regresie` (16 — exemplu-*.html + edge cases + mobil 320px + regenerare via gameHTML +
|
||||
stil top-level invalid la import + bomberman gameplay + bomberman rază/powerup-uri) și `@campanie`
|
||||
(15 — intro→hartă→camere→final, resume, cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil,
|
||||
audio S1, voce/narațiune D10, a11y tap/aria/reduced-motion, navigare overworld, timer calm T10).
|
||||
- **Status țintă: 29/29 PASS.**
|
||||
(16 — intro→hartă→camere→final, resume, cameră moartă, idempotență ușă, `$`/`$&`, beep, mobil,
|
||||
audio S1, voce/narațiune D10, a11y tap/aria/reduced-motion, navigare overworld, timer calm T10, muzica ambient T10).
|
||||
- **Status țintă: 30/30 PASS.**
|
||||
|
||||
## Work Guidance
|
||||
- După modificări la motoare (`escape-builder.html`): rulează suita completă; extinde `@regresie` dacă
|
||||
@@ -29,7 +29,7 @@ până la ecranul final, fără erori de consolă.
|
||||
|
||||
## Verification
|
||||
```bash
|
||||
npx playwright test tests/smoke.mjs # 29/29
|
||||
npx playwright test tests/smoke.mjs # 30/30
|
||||
npx playwright test tests/smoke.mjs --grep @regresie
|
||||
npx playwright test tests/smoke.mjs --grep @campanie
|
||||
```
|
||||
|
||||
@@ -739,6 +739,58 @@ test.describe('Campanie E2E @campanie', () => {
|
||||
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('muzica ambient — opt-in, porneste la start, tempo accelereaza sub 1 min, duck pe voce, toggle (T10)',
|
||||
async ({ page }) => {
|
||||
const errors = trackErrors(page);
|
||||
const cfg = campaignCfg(3, 'classic');
|
||||
cfg.music = true;
|
||||
cfg.timerMin = 1; /* timer pornit → tempo poate accelera */
|
||||
const tmpPath = await writeCampaignHtml(page, cfg, 'music');
|
||||
const gp = await page.context().newPage();
|
||||
|
||||
try {
|
||||
await gp.goto('file://' + tmpPath);
|
||||
|
||||
// Butonul de muzica vizibil (opt-in activ); muzica inca neporita
|
||||
await expect(gp.locator('#btn-music')).toBeVisible();
|
||||
expect(await gp.evaluate(() => window.__music.state().playing), 'inca neporita pe intro').toBe(false);
|
||||
|
||||
await gp.locator('#btn-start').click();
|
||||
|
||||
// Dupa start: muzica ruleaza, buton apasat
|
||||
await gp.waitForFunction(() => window.__music.state().playing === true, null, { timeout: 4000 });
|
||||
await expect(gp.locator('#btn-music')).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
// Tempo: 1.0 cand >60s ramase; creste progresiv sub 1 min (citit determinist)
|
||||
const tempos = await gp.evaluate(() => {
|
||||
const f = window.__music.tempo;
|
||||
_deadline = Date.now() + 90000; const t90 = f();
|
||||
_deadline = Date.now() + 30000; const t30 = f();
|
||||
_deadline = Date.now() + 1000; const t1 = f();
|
||||
return { t90, t30, t1 };
|
||||
});
|
||||
expect(tempos.t90).toBeCloseTo(1, 1);
|
||||
expect(tempos.t30, 'accelereaza sub 1 min').toBeGreaterThan(tempos.t90);
|
||||
expect(tempos.t1, 'mai rapid spre expirare').toBeGreaterThan(tempos.t30);
|
||||
|
||||
// Duck: vocea are prioritate → atenueaza muzica
|
||||
const ducked = await gp.evaluate(() => { duckMusic(true); return window.__music.state().duck; });
|
||||
const unducked = await gp.evaluate(() => { duckMusic(false); return window.__music.state().duck; });
|
||||
expect(ducked, 'duck activ < 1').toBeLessThan(1);
|
||||
expect(unducked, 'duck dezactivat = 1').toBe(1);
|
||||
|
||||
// Toggle off din buton → se opreste
|
||||
await gp.locator('#btn-music').click();
|
||||
await expect(gp.locator('#btn-music')).toHaveAttribute('aria-pressed', 'false');
|
||||
expect(await gp.evaluate(() => window.__music.state().playing), 'oprit dupa toggle').toBe(false);
|
||||
|
||||
} finally {
|
||||
await gp.close();
|
||||
try { unlinkSync(tmpPath); } catch (_) {}
|
||||
}
|
||||
expect(errors, errors.join('\n')).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Test 3: Camera moartă — timeout 4s → skip-banner + cod eroare
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user