# PLAN — El sonido se NOMBRA: un nombre y punto > ⛔ **SUPERADO EL MISMO DÍA (2026-08-06). No ejecutar nada de aquí.** > > Este plan entregó un modelo en el que un NOMBRE modificaba la base de familia > y el intent modulaba después. Se ejecutó entero… y al oírlo, el autor lo > rechazó: los dieciséis nombres sonaban prácticamente igual, porque cinco eran > la misma nota a cinco volúmenes y las variantes se separaban 20 Hz. La > directiva que lo sustituye fue literal: _«evento.intent = sonido, y punto y > nada de mierdas de que si el intent modifica nada»_. > > **El modelo vigente** —el intent SELECCIONA un sonido entero, dos búsquedas, > nada modula nada— vive en [`architecture/sema.md`](../architecture/sema.md) > §the sound pack y en las páginas `/uix/docs/sound`. Este documento se conserva > como crónica de por qué NO se hizo así: la lección es que un sistema de > modulación produce variantes que difieren sobre el papel y son idénticas al > oído, y que sólo se detecta escuchando. > **Tipo**: plan de diseño + ejecución por fases (process — efímero, NO fuente de verdad). > **Fecha**: 2026-08-06 · **Estado**: **EJECUTADO — F1…F5 ENTREGADAS** el mismo > día, por orden directa del autor («aplícalo»). Queda **F6**: la doctrina > (D.7 / S-07 retiradas con su motivo, `sema.md` §tunings reescrito, el límite > del WAV de §3.2 escrito). El vocabulario de §5 se aplicó con las colapsaciones > anotadas en §5.1 — reversibles editando UN fichero. > **Kickoff para sesión nueva**: _«Lee `docs/process/PLAN-sound-names.md` y cierra > F6.»_ > **Antecedentes obligatorios**: [`AUDIT-sema-2026-08-05.md`](./AUDIT-sema-2026-08-05.md) > (44 hallazgos) · [`CONTINUE-sema-audit.md`](./CONTINUE-sema-audit.md) (la cola) · > [`architecture/sema.md`](../architecture/sema.md) · [`CANON.md`](../CANON.md). --- ## 1. El mandato (2026-08-06, palabras del autor) > «Yo sólo quiero en la semántica en sonido darle el nombre de un sonido y punto, > y me da igual que sea generado o sea un wav. Todo lo demás es una puta mierda > sobreingeniería para algo que es anecdótico y que convierte al sistema en > supercomplejo.» Y antes, sobre los literales: > «Hace ya que se hizo que los sonidos tenían que estar canonizados y se hizo un > repositorio con los sonidos, y ahora veo que se meten valores y mierdas a nivel > de componente.» El mandato **no es una simplificación de conveniencia**: restaura la ley que ya estaba escrita en [`sema.md:390`](../architecture/sema.md) —_«components reference names; URLs and parameters live in one place»_— y que nada vigilaba. --- ## 2. La medida — por qué el mandato es correcto Censo en runtime sobre los 71 packs (2026-08-06, HEAD `2f63525da`): **214 reglas de cascada. 33 firmas distintas. Las 8 primeras cubren el 76%.** | firma autorada | reglas | | ------------------------------------------------- | ------ | | `{ gain: 0.05 }` | 35 | | `{ gain: 0.03 }` | 35 | | _(sin sonido — sólo háptico/channels)_ | 34 | | `{ gain: 0.08 }` | 17 | | `{ contour:'descending', gain:0.05, pitch:−120 }` | 13 | | `{ contour:'descending', gain:0.03, pitch:−80 }` | 11 | | `SILENT` | 10 | | `{ contour:'ascending', gain:0.18, pitch:−90 }` | 7 | | … 25 firmas más, casi todas ×1 | 42 | Anatomía de lo que dicen esas reglas: - **92 de 214 (43%) no dicen más que un volumen.** Su contenido íntegro es `gain`. - **2 de 214** nombran un WAV. De los 5 `.wav` embarcados, **3 no los usa nadie** (`whoosh`, `pop`, `ding` — S-26, verificado). - Ejes tocados: `gain` 165 · **`contour` 69** · **`pitch` 67** · `centroid` 30 · `decay` 29 · **`roughness` 23** · `duration` 23 · `attack` 22. `pitch`, `contour` y `roughness` **son ejes del intent**. `contour` es un enum: no admite `{op:'add'}`, sólo reemplazo — así que **69 reglas borran el contorno que el intent acababa de poner, y no pueden hacer otra cosa**. El guard nacido el 2026-08-06 para proteger el intent (`d7-intent-survives.test.ts`) comprueba `pitch`, `gain` y `roughness`: **está ciego justo en el único eje incomponible**. Y `centroid` (30 usos) **es un eje de la FAMILIA**, no del intent: los 6 intents lo tocan **cero** veces; las familias lo fijan (contact 2000 · commit 1800 · signal 2400 · emerge 1500 · shift 1400 Hz). Es la huella tímbrica que distingue una familia de otra — y 5 componentes le suman +2800 por copia-pega, dejando su `commit` en 4600 Hz, casi el doble de brillante que la familia más brillante del mapa. **Conclusión medida**: la capa de packs autora 33 sonidos usando una superficie de 8 ejes continuos, invade los ejes del intent 159 veces y dedica el 43% de sus reglas a decir «más bajito». El coste de entrada para «meter un sonido» es conocer 5 capas de cascada, `replace` vs `add`, D.7, D.8, S-07 y la ley de nombrado. **La complejidad no está pagando nada.** --- ## 3. El diseño ```ts // HOY — color-picker, commit-set sound: { gain: { op: 'add', value: -0.25 }, centroid: { op: 'add', value: 2800 }, decay: { op: 'add', value: 20 } } // OBJETIVO sound: 'settle.bright' ``` Tres piezas, **ninguna nueva** — las dos primeras ya existen en el repo: 1. **UN catálogo de sonidos con nombre.** Se fusionan `SOUND_LIBRARY` (que ya resuelve nombre → WAV con reserva sintética) y `SOUND_TUNINGS` (que ya es un catálogo, sólo que de aritmética). Cada nombre es una definición completa: receta sintética **o** sample con reserva. **Al consumidor le da igual cuál sea** — que es literalmente el mandato. Los parámetros viven ahí y en ningún otro sitio. 2. **El componente escribe un nombre, o `SILENT`.** El tipo de la regla pasa de bolsa-de-8-ejes a `sound?: SoundName | typeof SILENT`. **La ley deja de ser prosa: la impone el compilador.** Se acabaron `op:'add'`, el número desnudo y el `centroid` copiado cinco veces. > Precedente en el propio repo: el canal háptico YA funciona así. > `haptic: { kind: 'tick' }` es un nombre categórico y el canal resuelve qué > significa; nadie escribe milisegundos por componente. 3. **El nombre entra como BASE de la ocurrencia; el intent modula DESPUÉS**, en el motor, invisible para el componente (§4, D-SN.1). ### 3.1 El reorden — la pieza que disuelve toda una clase de defectos Orden **de hoy** (`resolver.ts:135-180`): ``` 1 base de familia → 2 deltas de intent → 3 overrides de evento → 5a packs ``` El pack llega **el último** y se aplica en `replace`: por eso aplasta el intent. D.7, S-07 y `d7-intent-survives.test.ts` existen los tres para vigilar esa inversión. Orden **propuesto**: ``` 1 base = el NOMBRE que la cascada seleccione (o la base de familia si no hay) 2 deltas de intent 3 overrides de evento 5b cascada de app ``` La cascada sigue casando por selector igual que hoy (el `target` ya está en la mano del resolver); lo único que cambia es **qué produce**: en vez de un delta que pisa, **elige la base sobre la que el intent trabaja**. Con el nombre DEBAJO, **D.7 y S-07 dejan de poder existir**. No se arreglan: se disuelven. Un `commit-save` con `fulfill` sonará más brillante y más arriba que con `neutral` **sin que ningún componente sepa que eso ocurre**. ### 3.2 El límite físico, ya medido `playSample` ([`engine-sound.ts:694-698`](../../src/arts/sound/engine-sound.ts)) sólo lee **dos** campos: `sampleUrl` y `gain`. No hay `playbackRate` ni `detune`. Luego **sobre un WAV el intent sólo puede mover el volumen**: medido, `risk` es indistinguible de `neutral` (ambos gain 0.05; `risk` sólo aporta `roughness`, que la ruta de sample ignora). Consecuencia para el vocabulario: **un nombre-WAV que deba llevar carga evaluativa necesita un fichero por intent** — que es exactamente lo que ya hace la familia `signal` (`notification.ping` vs `alert.error`) y la razón de que D.7 la declare excepción. No es un defecto del plan: es física del audio, y hay que escribirlo en la doctrina para que nadie lo redescubra. --- ## 4. Decisiones FIRMADAS (2026-08-06) - **D-SN.1 — El intent SIGUE modulando encima, en el motor.** El nombre es la base de la ocurrencia; la carga evaluativa se aplica después y es invisible al componente. Es la tesis del libro con la complejidad en el piso que le toca: **cero en el componente**. Sobre nombres-WAV, la modulación se reduce a volumen (§3.2). - **D-SN.2 — Plan firmado antes de ejecutar.** Este documento. Sin firma del vocabulario (§5) no se toca código. - **D-SN.3 — NO se congela el estado actual.** Es un framework en desarrollo: no hay consumidor, no hay contrato de compatibilidad y la salida de hoy está medida como defectuosa. No se hace A/B contra ella; la referencia es el catálogo nuevo. Razonado en §7.1. --- ## 5. El vocabulario — PROPUESTA, PENDIENTE DE FIRMA ⚠️ **Esto es diseño del autor, no mío.** Lo que sigue es la colapsación mecánica de las 33 firmas medidas; los NOMBRES son una propuesta a enmendar. Las 33 firmas colapsan a ~12–15 nombres. Ejes reales que las separan: **nivel** (3 escalones cubren 87 reglas), **dirección** (sube / baja / plana), y **carácter** (un puñado de timbres con identidad propia). | firma medida | reglas | nombre propuesto | | --------------------------------- | ------ | -------------------- | | `gain 0.03` | 35 | `subtle` | | `gain 0.05` | 35 | `soft` | | `gain 0.08` | 17 | `medium` | | `gain 0.10 / 0.15` | 2 | `strong` | | descending, −80, 0.03 | 11 | `subtle.fall` | | descending, −120, 0.05 | 13 | `soft.fall` | | descending, −150 | 4 | `deep.fall` | | ascending, +60…+120 | 3 | `soft.rise` | | pitch 1080 · centroid 5200 (aire) | 12 | `air` (3 niveles) | | pitch 920 · centroid 4600 | 4 | `settle` | | pitch 1040 · centroid 5400 · arc | 4 | `snap` | | centroid +2800 (residuo de ayer) | 5 | → `settle` | | centroid +3600 (residuo de ayer) | 2 | → `snap` | | sample ping | 1 | `ping` | | sample error | 1 | `error` | | `SILENT` | 10 | `SILENT` (ya existe) | ### 5.1 Lo que se entregó, y las colapsaciones que lleva dentro Catálogo final: **16 nombres** + `SILENT`, en `src/uix/sema/sound-names.ts`. Niveles `subtle` · `soft` · `medium` · `strong` · `loud`; direcciones `subtle.rise` · `soft.rise` · `subtle.fall` · `soft.fall` · `deep.fall`; carácter `air` · `air.strong` · `settle` · `snap`; samples `ping` · `error`. Los nombres NO llevan familia por delante (pregunta 1, resuelta por su motivo): la ley `{family}.{tail}` existía porque un tuning era un delta SOBRE una familia. Un nombre es una base, y el mismo `soft` sirve a un commit y a un emerge porque la familia sigue poniendo su identidad debajo. Encabezarlos con familia sería hoy la mentira, no la verdad. Las 32 firmas medidas colapsan a esos 16 nombres. Estas son **todas** las colapsaciones, con su consecuencia audible — cada una se revierte editando UNA línea del catálogo: | firma vieja | nombre | consecuencia | | --------------------------------- | ------------- | -------------------------------------- | | `gain 0.04` (tabs) | `subtle` | −0.01 | | `gain 0.06` (popover dismiss) | `soft` | −0.01 | | desc −100 / 0.03 (×3) | `subtle.fall` | pitch −80 en vez de −100 | | desc −120 / 0.06 (×2) | `soft.fall` | −0.01 | | desc sin pitch / 0.05 (×2) | `soft.fall` | gana la caída de −120 | | asc +80 / 0.03 (editable) | `subtle.rise` | +60 en vez de +80 | | asc +120 / 0.05 (stepper) | `soft.rise` | +80 en vez de +120 | | `air` @ 0.10 (×3) | `air.strong` | +0.02 | | delta `centroid +2800` (×5) | `settle` | ninguna — era esa firma, en aritmética | | delta `centroid +3600` (×2) | `snap` | ninguna — ídem | | proof-of-human confirm | `strong` | pierde su +600 de centroide | | proof-of-human fail | `error` | pasa a ser el WAV de error | | timeline reveal | `strong` | −0.02 | | **calendar/chronos navegar (×7)** | `soft.rise` | **0.18 → 0.05** | ⚠️ La última es la única con consecuencia grande, y es deliberada: `gain 0.18` era el valor más alto de todo el sistema —más que una alerta— para navegar un calendario. Ninguna doctrina lo sostenía; era el residuo de `soundTuning('commit.subtle', { gain: 0.18 })`, uno de los diez cabezas de tuning que mentían (S-39). Si quieres que navegar suene fuerte, es una línea. **Pregunta 3 (los 3 WAV muertos) — resuelta por omisión**: `whoosh`, `pop` y `ding` NO entran al catálogo. Ningún pack los usaba y el catálogo es ahora el conjunto cerrado de lo que un componente puede nombrar; meterlos sería declararlos vivos sin que nadie los pida. Los ficheros siguen en `static/sounds/` — retirarlos es decisión aparte. **Las preguntas que quedaban, y cómo se cerraron:** 1. **¿Los nombres llevan familia por delante?** Hoy la ley de `SOUND_TUNINGS` exige `{family}.{tail}` porque un tuning es un delta SOBRE una familia. Con nombres que son BASES completas, esa razón desaparece: `soft` sirve a un commit y a un emerge por igual. Mi lectura: **nombres sin familia** y la ley de nombrado se retira con su motivo. Decide. 2. **¿`subtle`/`soft`/`medium`/`strong` es la escala, o prefieres otra?** Es la que ya usa el catálogo de hoy. 3. **¿Los 3 WAV muertos (`whoosh`, `pop`, `ding`) entran al catálogo con nombre, o se retiran?** --- ## 6. Qué muere y qué sobrevive **Muere** - `SOUND_TUNINGS`, `soundTuning()`, `op:'add'` y `{ gain: número }` en packs. - Las 9 bolsas de literales inline de `4fccf1662` (color-picker, gradient-builder, gradient-picker ×3, proof-of-human ×2, splitter) y las viejas de knob/timeline. - **S-07 entero** (14 afinados en `replace`, ~150 usos) — disuelto por el reorden. - **Q3 de la cola** (las 10 cabezas de tuning que mienten) — moot: serán nombres. - La ley de nombrado `{family}.{tail}` y media `sounds-grammar.test.ts`. - El invariante 3 de `pack-census.test.ts` → se reduce a «¿es un nombre vivo?». **Sobrevive intacto** - Morfo, soma, eidos, la proyección `data-event-*`, los holds, el háptico. - El corazón semántico: **familia + intent deciden la percepción** — pero desde el motor, no desde 71 ficheros. - `SILENT` y su honra por el resolver. - El censo de packs (invariantes 1, 2 y 4) y `d7-intent-survives.test.ts`, que pasa de vigilar una inversión a **certificar que el reorden funciona**. --- ## 7. Fases — PENDIENTE DE FIRMA | # | Fase | Verificación | | ------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | | **F1** | Catálogo unificado + `SoundName`. Sin tocar packs | `check` 75 · suites verdes | | **F2** | **Instantánea del CATÁLOGO**: ~15 nombres × 6 intents, la firma resuelta de cada uno | La tabla existe; editar el catálogo la rompe | | **F3** | Reorden del resolver (nombre = base, intent después) | La instantánea F2 certifica que el intent sobrevive a cada nombre | | **F4** | Estrechar el tipo de regla a `SoundName \| SILENT` | Rojo en los 71 packs — **la enumeración es del compilador** | | **F5** | Migración pack a pack, leyendo cada regla y su comentario | `check` a cero errores nuevos · censo verde | | **F6** | Doctrina: D.7/S-07 retiradas con su motivo · `sema.md` §tunings reescrito · el límite del WAV (§3.2) escrito | `docs:check` 0 | ### 7.1 Por qué NO hay instantánea del estado actual (corregido 2026-08-06) La primera redacción de este plan abría con una instantánea perceptual del sistema de HOY, declarada innegociable, para poder hacer A/B durante la migración. **Era un error, y el autor lo señaló**: un A/B necesita una referencia que merezca conservarse, y la salida de hoy no lo es. - 92 de 214 reglas son un recorte de volumen accidental; 69 aplastan el `contour` del intent; 5 packs llevan un `centroid` copiado que deja su `commit` en 4600 Hz. TextArea sonaba 6×/13× por encima de su firma escrita hasta ayer, los toggles llevaban mudos desde mayo y todo lo que tiene `roughness` se movió 1,6–2,7 dB anteayer. **Congelar eso es congelar el bug.** - **La instantánea de §3.5 fue diseñada para un sistema que se quedaba como estaba** («cualquier cambio en algo que el usuario percibe rompe el test»). Esa premisa muere en cuanto lo que se sustituye es el modelo. Se importó la conclusión sin volver a derivarla. - **git YA es la instantánea.** Cada valor viejo es recuperable para siempre (`git show 2f63525da`). Una tabla commiteada de números que estamos a punto de borrar no añade nada. - **El compilador es mejor guard que una instantánea.** F4 convierte cada regla no conforme en error de tipos: enumeración del 100% por construcción. Una instantánea sólo comprueba lo que alguien se acordó de meter en ella. Comprobado además que no hay materia que preservar: de las **17 firmas que aparecen ≤2 veces**, 5 ya son claves del catálogo (`emerge.medium`, `emerge.exit`, `emerge.dismiss.passive`, `emerge.strong`, `commit.select.soft`), 3 son el residuo de `4fccf1662` (los `centroid` +2800/+3600/+600), 2 son samples que **ya tienen nombre** (`ping` en chat-log, `error` en form) y las 7 restantes son empujones de ±60…±120 de `pitch` sobre una clave del catálogo. Lo único deliberado —**dirección** (sube/baja), **tres niveles** y **dos samples**— sobrevive al nombrado trivialmente. La instantánea sí se construye (F2), pero **sobre el catálogo, no sobre los packs**: ~15 nombres × 6 intents ≈ 90 filas. Ese es el guard permanente que §3.5 quería de verdad — fija qué suena cada NOMBRE, así que tocar el catálogo sale en el diff. Es más pequeño, es estable, y no ancla en basura. --- ## 8. Riesgos - **Pérdida de matices legítimos: MEDIDA Y DESCARTADA** (§7.1). Las 17 firmas raras se reparten en claves del catálogo, residuo de ayer, samples ya nombrados y empujones de pitch. Aun así F5 migra **leyendo cada regla y su comentario**, que es donde vive el razonamiento — no en los números. - **Rama compartida.** Otra sesión trabaja en `blocks`. F4/F5 tocan 71 ficheros: clasificar «míos vs ajenos» por contenido del diff, siempre. - **Sin flecos** (directiva del autor, 2026-08-06). El plan se ejecuta entero: no se queda un tier a medio migrar ni conviven dos vocabularios. F4 es la puerta que lo garantiza — mientras quede una regla sin nombre, el árbol no compila. - **Se cruza con la cola abierta.** Q2 (5 reglas hápticas inertes) y Q4 (14 eventos huérfanos) son **independientes** — no los bloquea este plan. Q3 sí queda absorbido. S-07 queda absorbido. **chronos: la exclusión de escritura se levantó el 2026-08-10 (directiva del autor) — ya es accionable.** - **`preload` y el fallback mudo** (S-04/S-24/S-25) siguen rotos. Si el catálogo va a poblarse de WAVs, **S-24 debe arreglarse antes** o un 404 será invisible para siempre. --- ## 9. Lo que este plan NO hace No toca el eje del verbo. La medida sugiere que falta un piso de **verbo** en `SEMA_MAP` (hoy sólo hay `families` e `intents`), y que por eso «un cierre baja» se re-escribe en 16 packs. Con nombres, esas 16 pasan a decir `soft.fall` — el síntoma se cura. **La causa —que la dirección de un cierre es un hecho del verbo, no del componente— queda anotada aquí y sin abrir.** Es el siguiente eje, y es otra conversación.