docs(process): PLAN — el sonido se NOMBRA, un nombre y punto

Mandato del autor: «yo solo quiero en la semantica en sonido darle el
nombre de un sonido y punto, y me da igual que sea generado o sea un wav;
todo lo demas es sobreingenieria». No es una simplificacion de
conveniencia: restaura la ley que sema.md:390 ya tenia escrita —
«components reference names; URLs and parameters live in one place»— y
que nada vigilaba.

LA MEDIDA que lo justifica, en runtime sobre los 71 packs: 214 reglas
autoran 33 firmas distintas, y las 8 primeras cubren el 76%. Dos firmas
—gain 0.05 y gain 0.03— explican 70 reglas ellas solas. El 43% de la
superficie perceptual del framework es un componente diciendo «mas
bajito». Ejes tocados: gain 165, contour 69, pitch 67, roughness 23 —
contour/pitch/roughness son del INTENT, y contour es un enum, luego 69
reglas borran el contorno que el intent acaba de poner y no pueden hacer
otra cosa. El guard que naci ayer para proteger el intent no mira
contour: esta ciego en el unico eje incomponible.

EL DISEÑO: un catalogo de nombres (fusion de SOUND_LIBRARY y
SOUND_TUNINGS), el tipo de la regla estrechado a nombre|SILENT, y el
nombre aplicado ANTES de los deltas de intent. Con el nombre debajo, D.7
y S-07 dejan de PODER existir — no se arreglan, se disuelven.

§7.1 registra un error mio corregido por el autor: la primera redaccion
abria con una instantanea del estado actual, declarada innegociable. Un
A/B necesita una referencia que merezca conservarse y la salida de hoy no
lo es; ademas git YA es esa instantanea y el compilador es mejor guard.
La instantanea se movio al CATALOGO, que es donde viven las decisiones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-dir-prefs
dev 2 months ago
parent 2f63525da7
commit 7154fb77ef

@ -0,0 +1,367 @@
# PLAN — El sonido se NOMBRA: un nombre y punto
> **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 sigue excluido de escritura.**
- **`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.
Loading…
Cancel
Save

Powered by TurnKey Linux.