docs(sound): el aviso de segundo contexto dice lo que hace, no lo que promete

A-2 de la auditoría (`PLAN-sound-engine` §16). El contador de contextos vivos
sólo ve **los que crea el art**, así que un `new AudioContext()` en cualquier
otro sitio le es invisible: detecta cableado doble de ENGINES, no el antipatrón
del contexto crudo que su propio mensaje describe. Ese hueco es exactamente lo
que dejó al estudio de sema abrir un segundo contexto en silencio hasta ayer
(A-1) — y lo que hizo inútil el plan del handoff anterior, que era «abre la
página y mira el warn».

Cerrarlo de verdad exigiría enganchar el constructor global, es decir parchear
la plataforma. El art no lo va a hacer. Así que se escribe donde prometía de
más, en `consts.ts` y en el README, y se apoya en la salida real: los
consumidores que necesitaban su propio contexto ahora tienen puertas —`decode`
para las muestras, `context` para el grafo— que no les obligan a abrirlo.

Sin cambios de comportamiento. Art 13/13 · `check` en la baseline exacta (73).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 2 months ago
parent 08ee18a8e1
commit cf37aee002

@ -95,7 +95,7 @@ tests miden nodos, no timbre. Si algo sonara distinto, el sospechoso es F1
otra sesión** y esta sesión también lo tocó (dos asserts) — separar al
commitear.
## Auditoría posterior — 5 hallazgos, 1 cerrado y 4 ABIERTOS sobre el art
## Auditoría posterior — 5 hallazgos, 2 cerrados y 3 ABIERTOS sobre el art
Detalle y propuestas en [`PLAN-sound-engine.md` §16](./PLAN-sound-engine.md).
Resumen, porque cambian lo que hay que creerse de este hilo:
@ -105,8 +105,10 @@ Resumen, porque cambian lo que hay que creerse de este hilo:
puerta, para quien quiere las MUESTRAS y no un grafo— y el estudio decodifica
por `uix.sound`. Medido: **1 contexto**, y el análisis sigue siendo correcto
(440 Hz → 441 Hz tras resamplear). §16.3.
- **A-2** — el aviso de «segundo contexto» **no ve** los contextos crudos: sólo
cuenta los que crea el art.
- ~~**A-2**~~ ✅ **DECLARADO** — el aviso **no ve** los contextos crudos: sólo
cuenta los que crea el art. Cerrarlo exigiría parchear el constructor global,
así que se escribe donde prometía de más (`consts.ts` + README) en vez de
fingir que detecta lo que su mensaje describe.
- **A-3** — **S-1 no está cerrado**: sema importa `createEngineSound` como valor,
así que el sintetizador sigue entrando en el bundle con el sonido apagado.
- **A-4** — el canal **tira en silencio** `masterGain` y compañía cuando le

@ -372,7 +372,7 @@ hallazgos corrigen.
| # | Hallazgo | Evidencia |
| --- | --- | --- |
| **A-1** ✅ **CERRADO 2026-07-30** (§16.3) | **El estudio SIGUE abriendo un segundo contexto.** [`analyze.ts:32`](../../web/routes/temas/sema/_lib/analyze.ts) hace `new Ctor()` propio para decodificar el `.wav` importado. Medido inyectando un WAV real por el `input[type=file]`: **1 → 2 contextos** (el segundo se cierra al acabar el decode). La medición de §15 sólo recorría el camino del earcon | navegador |
| **A-2** | **El aviso de «segundo contexto» es ciego a los contextos crudos.** `liveContexts` cuenta sólo los que crea el art, así que un `new AudioContext()` en cualquier otro sitio —A-1, exactamente— es invisible. Detecta cableado doble de *engines*, no el antipatrón que su mensaje nombra | [`engine-sound.ts:60`](../../src/arts/sound/engine-sound.ts) |
| **A-2** ✅ **ESCRITO 2026-07-30** (no se puede «arreglar» sin parchear el constructor global; queda declarado en `consts.ts` y en el README) | **El aviso de «segundo contexto» es ciego a los contextos crudos.** `liveContexts` cuenta sólo los que crea el art, así que un `new AudioContext()` en cualquier otro sitio —A-1, exactamente— es invisible. Detecta cableado doble de *engines*, no el antipatrón que su mensaje nombra | [`engine-sound.ts:60`](../../src/arts/sound/engine-sound.ts) |
| **A-3** | **S-1 NO está cerrado**, y §7 afirma lo contrario. `engine.ts:29` importa `SoundChannel` como valor y `chans/sound.ts:7` importa `createEngineSound` como valor: toda app que construya un `EngineSemantic` sigue metiendo el sintetizador en el bundle con el sonido apagado. La cadena es un salto más larga e igual de estática. Y «sema no importa el art» es inexacto: cierto para la *instancia*, falso para el *grafo de módulos* | [`engine.ts:29`](../../src/uix/sema/engine.ts) · [`sound.ts:7`](../../src/uix/sema/chans/sound.ts) |
| **A-4** | **El canal tira en silencio las opciones de construcción cuando le inyectan el motor**: `masterGain`, `audioContextFactory`, `fetchFn`, `dom`, `timers`, `logger`. Sólo conserva `preferences`. En standalone pasa desde F2; **el cableado de attack de §15.3 lo extiende a attach**, donde `sound: { masterGain: 0.4 }` era honrado y ahora es un no-op mudo. Ningún test lo pinea. Efecto lateral: el nombre de servicio `sound` queda reservado de facto | [`sound.ts:80`](../../src/uix/sema/chans/sound.ts) |
| **A-5** | **`gainScale` —parámetro de UNA reproducción— se escribe en el nodo master COMPARTIDO**, del que cuelgan todos los earcons, incluidos los que ya suenan. Con un consumidor no se nota; con dos —la premisa del art— el ducking del reproductor y la reducción de sema se pisan, y un earcon en vuelo salta de nivel a media nota. El grafo ya tiene su nodo de envolvente, que es el sitio correcto. **`RC-5` pineó ese orden como si fuera diseño** | [`engine-sound.ts:141`](../../src/arts/sound/engine-sound.ts) |

@ -97,6 +97,13 @@ when a second one appears, pointing at the fix (take `uix.sound`, or pass
`soundEngine` to `EngineSemantic`). It is a warn, not an error: two contexts are
degraded, not broken.
**Read the counter for what it is.** It only sees the contexts *this art*
creates, so it catches double-**engine** wiring — not a bare `new AudioContext()`
somewhere else, which is the anti-pattern the message itself describes. Closing
that gap would mean monkey-patching the global constructor, which the art will
not do. It is written down instead, and the consumers who used to need their own
context now have doors that don't require one.
## No side effects until it sounds
The constructor creates no context and registers no listener. An app that never

@ -24,6 +24,16 @@ export const SOUND_LOGS = {
* autoplay unlock gesture is per-context, so one of the two ends up mute.
* The fix is to take the shared engine (`uix.sound`, or `soundEngine` on
* `EngineSemantic`) instead of letting a consumer open its own.
*
* **Blind spot, by construction**: the counter only sees contexts THIS art
* creates. A bare `new AudioContext()` anywhere else is invisible to it, so
* this warn catches double-ENGINE wiring, not the raw-context anti-pattern
* its own message describes. That gap is what let the sema studio open a
* second context in silence until 2026-07-30 (`PLAN-sound-engine.md` §16,
* A-1/A-2). Hooking the global constructor to close it would be
* monkey-patching the platform — so the honest move is to say so here, and
* to give consumers a door (`decode`, `context`) they don't need to work
* around.
*/
MULTIPLE_CONTEXTS:
'a second AudioContext went live in this document — consume the shared engine (uix.sound / EngineSemantic soundEngine) instead of opening your own: browsers cap concurrent contexts and the autoplay unlock gesture is per-context, so one of them will end up mute'

Loading…
Cancel
Save

Powered by TurnKey Linux.