claude-code-arcane 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +57 -55
  3. package/agents/audio/audio-director.md +90 -0
  4. package/agents/audio/audio-programmer.md +97 -0
  5. package/agents/audio/audio-qa.md +95 -0
  6. package/agents/audio/composer.md +88 -0
  7. package/agents/audio/technical-sound-designer.md +90 -0
  8. package/agents/audio/voice-director.md +96 -0
  9. package/agents/career/career-strategist.md +88 -0
  10. package/agents/career/cv-reviewer.md +81 -0
  11. package/agents/career/hiring-manager.md +68 -0
  12. package/agents/career/mock-interviewer.md +76 -0
  13. package/agents/freelance/client-screener.md +84 -0
  14. package/agents/freelance/discovery-call.md +65 -0
  15. package/agents/freelance/pipeline-strategist.md +99 -0
  16. package/agents/freelance/proposal-reviewer.md +82 -0
  17. package/dist/cli.js +52 -13
  18. package/docs/RELEASE-SETUP.md +1 -1
  19. package/docs/SKILLS-CATALOG.md +47 -3
  20. package/docs/USER-GUIDE.md +77 -72
  21. package/docs/agent-hierarchy.md +21 -1
  22. package/docs/presentations/build_arcane_deck.py +2 -2
  23. package/docs/presentations/build_arcane_deck_12.py +2 -2
  24. package/package.json +2 -2
  25. package/profiles/audio.yaml +45 -0
  26. package/profiles/freelance.yaml +43 -0
  27. package/profiles/job-hunt.yaml +5 -2
  28. package/rules/drafter-reviewer.md +16 -3
  29. package/rules/freelance-guardrails.md +53 -0
  30. package/rules/gamedev/audio-code.md +45 -0
  31. package/skills/adaptive-music/SKILL.md +171 -0
  32. package/skills/adaptive-music/references/anti-patterns.md +53 -0
  33. package/skills/audio-audit/SKILL.md +164 -0
  34. package/skills/audio-audit/references/anti-patterns.md +65 -0
  35. package/skills/audio-audit/scripts/check_assets.py +208 -0
  36. package/skills/audio-bible/SKILL.md +119 -0
  37. package/skills/audio-bible/references/anti-patterns.md +39 -0
  38. package/skills/audio-mix/SKILL.md +151 -0
  39. package/skills/audio-mix/references/anti-patterns.md +65 -0
  40. package/skills/audio-spec/SKILL.md +107 -0
  41. package/skills/audio-spec/references/anti-patterns.md +45 -0
  42. package/skills/career-registry/SKILL.md +95 -0
  43. package/skills/career-registry/scripts/career_registry.py +488 -0
  44. package/skills/client-screen/SKILL.md +83 -0
  45. package/skills/client-screen/references/red-flags.md +95 -0
  46. package/skills/cover-letter/SKILL.md +4 -3
  47. package/skills/cv-tailor/SKILL.md +11 -2
  48. package/skills/freelance-deliver/SKILL.md +90 -0
  49. package/skills/freelance-hunt/SKILL.md +126 -0
  50. package/skills/freelance-hunt/references/templates/Connects.md +39 -0
  51. package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
  52. package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
  53. package/skills/freelance-kickoff/SKILL.md +94 -0
  54. package/skills/freelance-pipeline/SKILL.md +109 -0
  55. package/skills/freelance-pipeline/scripts/import_history.py +425 -0
  56. package/skills/freelance-profile/SKILL.md +93 -0
  57. package/skills/freelance-proposal/SKILL.md +112 -0
  58. package/skills/freelance-scan/SKILL.md +196 -0
  59. package/skills/freelance-scan/references/dedup.md +35 -0
  60. package/skills/freelance-scan/references/platforms.md +167 -0
  61. package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
  62. package/skills/interview-prep/SKILL.md +11 -3
  63. package/skills/job-aplicar/SKILL.md +5 -1
  64. package/skills/job-hunt/SKILL.md +17 -0
  65. package/skills/job-outcome/SKILL.md +15 -1
  66. package/skills/job-upskill/SKILL.md +10 -2
  67. package/skills/middleware-integration/SKILL.md +186 -0
  68. package/skills/middleware-integration/references/anti-patterns.md +69 -0
  69. package/skills/midi-compose/SKILL.md +136 -0
  70. package/skills/midi-compose/references/smf-format.md +129 -0
  71. package/skills/midi-compose/scripts/compose_midi.py +326 -0
  72. package/skills/midi-compose/templates/example-composition.json +69 -0
  73. package/skills/music-composition/SKILL.md +106 -0
  74. package/skills/music-composition/references/form.md +57 -0
  75. package/skills/music-composition/references/game-music.md +82 -0
  76. package/skills/music-composition/references/harmony.md +82 -0
  77. package/skills/music-composition/references/melody.md +48 -0
  78. package/skills/music-composition/references/orchestration.md +69 -0
  79. package/skills/music-composition/references/rhythm-groove.md +49 -0
  80. package/skills/procedural-audio/SKILL.md +157 -0
  81. package/skills/procedural-audio/references/anti-patterns.md +45 -0
  82. package/skills/sfx-design/SKILL.md +130 -0
  83. package/skills/sfx-design/references/anti-patterns.md +53 -0
  84. package/skills/spatial-audio/SKILL.md +139 -0
  85. package/skills/spatial-audio/references/anti-patterns.md +53 -0
  86. package/skills/ui-sound-design/SKILL.md +133 -0
  87. package/skills/ui-sound-design/references/anti-patterns.md +57 -0
  88. package/skills/voice-pipeline/SKILL.md +150 -0
  89. package/skills/voice-pipeline/references/anti-patterns.md +69 -0
@@ -4,7 +4,7 @@ description: "Prepare for a job interview: company research, likely question ban
4
4
  argument-hint: "[application-note | company + role + round]"
5
5
  category: "career"
6
6
  user-invocable: true
7
- allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch
7
+ allowed-tools: Read, Glob, Grep, Write, Edit, WebFetch, Task
8
8
  ---
9
9
 
10
10
  # Interview Prep - Preparación de entrevista
@@ -57,13 +57,21 @@ Preparás al usuario para una entrevista concreta: research de la empresa, banco
57
57
  1. Leer aplicación + empresa + perfil. WebFetch del sitio/notas de la empresa.
58
58
  2. Crear nota en `06-Entrevistas/` con el template `Entrevista`.
59
59
  3. Llenar research, banco de preguntas (marcando las top), STAR de las 3-5 historias clave, preguntas a hacer, red flags.
60
- 4. Ofrecer un **mock**: vos hacés de entrevistador y das feedback sobre las respuestas.
60
+ 4. Ofrecer un **mock** con el agente `mock-interviewer` (ver abajo).
61
61
  5. Post-entrevista: ayudar a registrar feedback y definir próximo paso / follow-up (con `/cold-outreach`).
62
62
 
63
+ ## El mock va en un agente, no en esta sesión
64
+
65
+ El mock lo corre el agente `mock-interviewer`, no vos. La razón es dura: vos acabás de escribir las respuestas STAR del usuario, así que ibas a preguntar exactamente lo que preparó, iba a salir perfecto, y el usuario iba a entrar a la entrevista real con confianza falsa.
66
+
67
+ Al lanzarlo, pasale **inline**: el JD, la ronda, y el CV que se mandó. **No** le pases el perfil maestro ni la nota de `06-Entrevistas/` con las respuestas preparadas - eso invalida el simulacro, y el agente tiene instrucción de rechazarlos.
68
+
69
+ Cuando devuelve el feedback, tu trabajo es registrarlo: agregá en la nota de `06-Entrevistas/` qué respuestas hay que rehacer y por qué. Ese es el valor que queda para la próxima ronda.
70
+
63
71
  ## Idioma
64
72
 
65
73
  En el idioma de la entrevista. Preparar respuestas en inglés si el rol es internacional, aunque la prep se converse en español.
66
74
 
67
75
  ## Handoff
68
76
 
69
- Pedí aprobación (approval) antes de escribir la nota en `06-Entrevistas/`. Cuando la prep está READY, ofrecé un mock; post-entrevista, seguí con `/cold-outreach` para el follow-up.
77
+ Pedí aprobación (approval) antes de escribir la nota en `06-Entrevistas/`. Cuando la prep está READY, ofrecé el mock con `mock-interviewer` y registrá su feedback; post-entrevista, seguí con `/cold-outreach` para el follow-up.
@@ -54,7 +54,11 @@ Usar el skill **cover-letter** según el canal de aplicación (formulario ATS
54
54
 
55
55
  ## Paso 7 - Reviewer (regla drafter-reviewer, obligatorio)
56
56
 
57
- Seguir `.claude/rules/drafter-reviewer.md`: lanzar UN agente general-purpose de contexto fresco con el JD + borradores inline; lectura acotada al perfil; research web breve de la empresa. Aplicar la Parte A (reemplazos exactos) e incorporar lo relevante de la Parte B. Una sola ronda. Never stuff keywords: los gaps se reconocen en el cover, no se rellenan en el CV. Normalizar guiones largos del texto sugerido.
57
+ Seguir `.claude/rules/drafter-reviewer.md`: lanzar el agente **`cv-reviewer`** de contexto fresco con el JD + borradores inline y el perfil usado. Aplicar la Parte A (reemplazos exactos) e incorporar lo relevante de la Parte B. Una sola ronda. Never stuff keywords: los gaps se reconocen en el cover, no se rellenan en el CV. Normalizar guiones largos del texto sugerido.
58
+
59
+ **Segunda lente si el rol es senior o la postulación importa:** lanzar `hiring-manager` **en paralelo** con `cv-reviewer` (no en cadena). Contesta si el CV convence a quien contrata, que es una pregunta distinta a si pasa el filtro. Sus 3 preguntas van a `## Notas` de la nota: son material directo de `/interview-prep`.
60
+
61
+ Si el directorio de agentes no está instalado, el fallback con `general-purpose` está en la regla.
58
62
 
59
63
  ## Paso 8 - CONFIRMACIÓN 2: aprobar CV final
60
64
 
@@ -67,6 +67,7 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
67
67
  | Corrida de búsqueda con CLIs y dedup entre corridas | `/job-scrape` |
68
68
  | Postulación completa (nota + CV + cover + review + PDF) | `/job-aplicar` |
69
69
  | Registrar resultado/avance de una postulación | `/job-outcome` |
70
+ | ¿Ya me postulé acá? / exportar el registro a CSV | `/career-registry` |
70
71
  | Gaps de skills agregados + plan de estudio | `/job-upskill` |
71
72
  | Adaptar CV a una oferta | `/cv-tailor` |
72
73
  | Exportar CV a PDF ATS | `/cv-ats-export` |
@@ -75,6 +76,22 @@ Según el estado del workspace, recomendar el skill siguiente (ver routing).
75
76
  | Optimizar perfil de LinkedIn | `/linkedin-optimize` |
76
77
  | Crear/actualizar portfolio web | `/portfolio-site` |
77
78
  | Preparar una entrevista | `/interview-prep` |
79
+ | Diagnosticar por qué se frena la búsqueda | agente `career-strategist` (vía `/job-upskill`) |
80
+
81
+ ## Los 4 agentes del perfil
82
+
83
+ El perfil instala `agents/career/`. Los agentes existen por una sola razón: **hay lecturas que solo sirven con contexto fresco**. Vos no podés revisar un CV que acabás de escribir, ni entrevistar a alguien cuyas respuestas preparadas leíste. Son read-only: devuelven análisis, los archivos los escribe la sesión principal.
84
+
85
+ | Agente | Contesta | Lo lanza |
86
+ |---|---|---|
87
+ | `cv-reviewer` | ¿Pasa el filtro del ATS y del recruiter? | `/job-aplicar`, `/cv-tailor`, `/cover-letter` |
88
+ | `hiring-manager` | ¿Convence a quien contrata? (evidencia de impacto, no keywords) | `/job-aplicar`, `/cv-tailor` (opcional) |
89
+ | `mock-interviewer` | ¿Aguanta la entrevista real? | `/interview-prep` |
90
+ | `career-strategist` | ¿Por qué me frenan siempre en la misma etapa? | `/job-upskill` (10+ aplicaciones resueltas) |
91
+
92
+ `cv-reviewer` y `hiring-manager` se lanzan **en paralelo** cuando van los dos: son lentes independientes, encadenarlos solo suma latencia.
93
+
94
+ Si el usuario instaló el perfil sin el directorio de agentes, la regla `drafter-reviewer` documenta el fallback con `general-purpose`.
78
95
 
79
96
  ## Workflow estándar para una nueva oportunidad
80
97
 
@@ -35,14 +35,28 @@ Empresa/oferta indicada: `$ARGUMENTS`
35
35
  - `rechazado` con feedback → anotar la lección en la nota; si se acumulan 3+ resoluciones, ofrecer `/job-upskill` y revisar los criterios de búsqueda contra los resultados reales.
36
36
  - `sin_respuesta` a los 10-14 días → ofrecer follow-up con `/cold-outreach`.
37
37
  - `contratado` → 🎉 y actualizar el resto de las aplicaciones abiertas (¿retirarse de procesos?).
38
+ - **Freelance** - `en_conversacion` → ofrecer ensayar con el agente `discovery-call`; `contrato_activo` → crear la nota en `08-Contratos/`; `sin_respuesta` o `rechazado` acumulados (10+ propuestas resueltas) → ofrecer `/freelance-pipeline`, que es donde se ve si el problema es la propuesta o el targeting.
38
39
 
39
40
  6. **Confirmar** al usuario qué se registró (nota, campos tocados, fila del dashboard).
40
41
 
41
42
  ## Vocabulario de `estado` (frontmatter)
42
43
 
44
+ Depende del `tipo:` de la nota. Leelo primero: son dos pipelines distintos y confundirlos rompe el dashboard y los reportes.
45
+
46
+ **`tipo: aplicacion` (empleo, perfil `+job-hunt`):**
47
+
43
48
  `interesado → aplicado → entrevista → oferta` y cierra en: `contratado | rechazado | sin_respuesta | declinada | descartado`
44
49
 
45
- Emojis del dashboard: ⬜ interesado · ✅ aplicado · 📅 entrevista/oferta · ❌ rechazado/sin_respuesta/declinada · 🚫 descartado/cerrada
50
+ **`tipo: freelance` (propuestas, perfil `+freelance`):**
51
+
52
+ `interesado → screeneado → propuesta_enviada → en_conversacion → contrato_activo → entregado` y cierra en: `ganado_cerrado | sin_respuesta | rechazado | descartado | disputa`
53
+
54
+ En freelance hay dos registros extra que no existen en empleo, y sin ellos `/freelance-pipeline` no puede calcular nada:
55
+
56
+ - Al marcar `propuesta_enviada`, preguntar y registrar **cuántos Connects costó** (frontmatter `connects_gastados`) y agregar la fila al ledger de `07-Recursos/Connects - <año>.md`.
57
+ - Si la oferta se cerró sin que el cliente contratara a nadie, el resultado en el ledger es `oferta_nunca_contrato`, no `sin_respuesta`. Son cosas distintas: la primera es Connects perdidos por mal screening, la segunda es tu propuesta que no convenció.
58
+
59
+ Emojis del dashboard: ⬜ interesado/screeneado · ✅ aplicado/propuesta_enviada · 📅 entrevista/oferta/en_conversacion · 🔨 contrato_activo/entregado · ❌ rechazado/sin_respuesta/declinada · 🚫 descartado/cerrada · ⚠️ disputa
46
60
 
47
61
  ## Reglas
48
62
 
@@ -4,7 +4,7 @@ description: "Aggregate skill gaps across all scored job applications, weigh the
4
4
  argument-hint: "[url | nota de aplicación]"
5
5
  category: "career"
6
6
  user-invocable: true
7
- allowed-tools: Read, Glob, Grep, Write, Bash, WebFetch, WebSearch
7
+ allowed-tools: Read, Glob, Grep, Write, Bash, WebFetch, WebSearch, Task
8
8
  ---
9
9
 
10
10
  # /job-upskill - Análisis de gaps y plan de estudio
@@ -13,6 +13,14 @@ Detecta qué skills te están costando puntos de match en las ofertas reales y a
13
13
 
14
14
  Argumento: `$ARGUMENTS` (vacío = modo agregado; URL o nota = modo targeted)
15
15
 
16
+ ## Antes del plan: ¿el gap es real o de presentación?
17
+
18
+ Este skill asume que la respuesta es estudiar. A veces no lo es: si la experiencia está en el perfil maestro pero no llega al CV, el plan de estudio manda al usuario a aprender meses lo que ya sabe.
19
+
20
+ En **modo agregado con 10+ aplicaciones resueltas**, lanzar primero el agente `career-strategist` (contexto fresco, read-only). Diagnostica el embudo completo y separa gap real de gap de presentación. Si el punto de fuga no es la etapa técnica, decílo antes del heatmap: el plan de estudio no es la palanca y conviene ir a `/cv-tailor` o `/interview-prep`.
21
+
22
+ Con menos de 10 resueltas, saltear: no hay volumen para un embudo y el propio agente lo va a rechazar. Seguí directo al modo agregado.
23
+
16
24
  ## Modo agregado (sin argumento)
17
25
 
18
26
  1. **Recolectar evidencia.** Grep de `match_score:` en `03-Aplicaciones/*.md` (excluir `_index`; aceptar también el campo legacy `score:` de notas viejas). De cada nota leer: `match_score`, `estado`, `perfil`, y los gaps mencionados en las secciones "Match con mi perfil" / "Gaps reales" y los gaps reconocidos en `## Notas`.
@@ -55,4 +63,4 @@ Argumento: `$ARGUMENTS` (vacío = modo agregado; URL o nota = modo targeted)
55
63
 
56
64
  ## Handoff
57
65
 
58
- Reporte COMPLETE en `07-Recursos/`. Retomar el pipeline con `/job-aplicar` en las ofertas abiertas, o `/job-scrape` para una corrida nueva apuntando a los gaps que ya se están cerrando.
66
+ Reporte COMPLETE en `07-Recursos/`. Retomar el pipeline con `/job-aplicar` en las ofertas abiertas, o `/job-scrape` para una corrida nueva apuntando a los gaps que ya se están cerrando. Si el `career-strategist` ubicó la fuga fuera de la etapa técnica, el siguiente paso es ese (`/cv-tailor`, `/interview-prep` o `/job-search`), no el plan de estudio.
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: middleware-integration
3
+ description: "Integracion de middleware de audio (Wwise/FMOD): containers, eventos, Switches/States/RTPCs, atenuaciones, soundbanks, streaming vs memoria, y el contrato con el codigo de gameplay. Usar para: Wwise, FMOD, event authoring, RTPC, soundbanks, arquitectura de audio."
4
+ category: "audio"
5
+ argument-hint: "[structure | events | rtpc | banks | contract]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Write, Edit
8
+ ---
9
+ # middleware-integration — Wwise / FMOD
10
+
11
+ Diseña la estructura del proyecto de middleware y el contrato entre el audio y el código de gameplay.
12
+
13
+ Conceptos agnósticos donde se puede; nomenclatura de Wwise como referencia principal, con el equivalente FMOD anotado, porque los dos modelan lo mismo con nombres distintos.
14
+
15
+ ## Cuándo usar
16
+
17
+ - Estructurar un proyecto de Wwise/FMOD desde cero
18
+ - Definir el contrato de eventos que va a postear el código
19
+ - Diseñar la organización de soundbanks
20
+ - El proyecto de middleware creció desordenado y hay que reorganizarlo
21
+ - Decidir qué va en memoria y qué va en streaming
22
+
23
+ ## Input
24
+
25
+ 1. Leer `/audio-spec` (event lists), `/audio-mix` (buses) y `/adaptive-music` (matriz de transiciones) si existen
26
+ 2. Determinar el middleware y su versión
27
+ 3. Preguntar al usuario: plataformas target, budget de memoria de audio, y si el proyecto ya existe o se arranca limpio
28
+
29
+ ---
30
+
31
+ ## Equivalencias Wwise ↔ FMOD
32
+
33
+ | Concepto | Wwise | FMOD Studio |
34
+ |---|---|---|
35
+ | Unidad que dispara el código | Event | Event |
36
+ | Contenedor de variación | Random / Sequence Container | Multi-instrument, Scatterer |
37
+ | Selección por estado discreto | Switch Container | Parameter (labeled) |
38
+ | Mezcla continua entre variantes | Blend Container | Parameter con automatización |
39
+ | Parámetro continuo | RTPC (Game Parameter) | Parameter (continuous) |
40
+ | Estado global | State | Global parameter / Snapshot |
41
+ | Agrupación de mezcla | Actor-Mixer / Bus | Group bus |
42
+ | Snapshot de mezcla | State con propiedades de bus | Snapshot |
43
+ | Curva de distancia | Attenuation ShareSet | 3D panner + curvas |
44
+ | Empaquetado | SoundBank | Bank |
45
+ | Música interactiva | Music Switch / Playlist Container | Event con transition regions |
46
+
47
+ ## Estructura del proyecto
48
+
49
+ Organizar por **sistema de gameplay**, no por tipo de sonido. `Weapons/Shotgun/` es navegable; `Impacts/` con todo mezclado no.
50
+
51
+ ```
52
+ Actor-Mixer Hierarchy
53
+ ├── Player (locomoción, armas, voz del jugador)
54
+ ├── Enemies (por arquetipo)
55
+ ├── World (props, entorno, destructibles)
56
+ ├── Ambience (por zona)
57
+ └── UI
58
+
59
+ Interactive Music
60
+ └── (por estado: Explore, Combat, Victory...)
61
+
62
+ Events
63
+ └── (espeja la jerarquía de Actor-Mixer)
64
+ ```
65
+
66
+ **Reglas que evitan el desorden:**
67
+ - Un evento por acción de gameplay, no por sonido. `Play_Weapon_Fire` con un Switch de tipo de arma le gana a un evento por arma
68
+ - Los ShareSets (atenuación, conversión, efectos) se definen una vez y se reusan. Un ShareSet por objeto es la causa número uno de proyectos inmanejables
69
+ - Work units separados por sistema: es lo que permite que dos personas trabajen sin conflictos de merge
70
+
71
+ ## Containers
72
+
73
+ | Container | Cuándo | Cuidado |
74
+ |---|---|---|
75
+ | **Random** | Variación de un mismo sonido | Usar *shuffle* (round-robin), no random puro: random repite |
76
+ | **Sequence** | Sonidos que tienen orden (carga → disparo) | Definir qué pasa al interrumpir |
77
+ | **Switch** | Selección por estado discreto (superficie, tipo de arma) | Definir el default: si el Switch no está seteado, el evento no suena |
78
+ | **Blend** | Mezcla continua por parámetro (RPM de motor) | Cuidar el solapamiento de las curvas o hay huecos |
79
+
80
+ ## Switches, States y RTPCs
81
+
82
+ Tres cosas distintas, y confundirlas es un error de arquitectura frecuente:
83
+
84
+ | | Alcance | Tipo | Ejemplo |
85
+ |---|---|---|---|
86
+ | **Switch** | Por objeto de juego | Discreto | Superficie bajo *este* personaje |
87
+ | **State** | Global | Discreto | El juego está en pausa |
88
+ | **RTPC** | Global o por objeto | Continuo | Salud del jugador, intensidad de combate |
89
+
90
+ **Regla:** si es una propiedad de un objeto específico, es Switch. Si es una condición del juego entero, es State. Si es un número que varía suave, es RTPC.
91
+
92
+ Usar un State para algo que es por objeto es el bug que produce "todos los personajes caminan sobre la superficie del jugador".
93
+
94
+ **Contrato de RTPCs:** todo RTPC necesita rango declarado, curva, y valor por default. El código **normaliza y clampea antes de enviarlo** — el middleware no debería recibir nunca un valor fuera de rango.
95
+
96
+ ## Soundbanks
97
+
98
+ Estrategia por defecto que funciona en la mayoría de los proyectos:
99
+
100
+ | Bank | Contenido | Cuándo carga |
101
+ |---|---|---|
102
+ | `Init` | Estructura del proyecto, buses | Al arrancar, siempre |
103
+ | `Global` | UI, voz del jugador, música persistente | Al arrancar, siempre |
104
+ | `Level_XX` | Ambiente y props del nivel | Al cargar el nivel |
105
+ | `Character_XX` | Por personaje/enemigo | Con el spawn del personaje |
106
+
107
+ **Streaming vs memoria:**
108
+
109
+ | Va en memoria | Va en streaming |
110
+ |---|---|
111
+ | Todo lo corto y frecuente (SFX, UI, footsteps) | Música |
112
+ | Cualquier cosa que necesite latencia cero | Diálogo largo |
113
+ | | Ambientes largos |
114
+
115
+ El streaming ahorra memoria y cuesta latencia y accesos a disco. Un SFX en streaming llega tarde; la música en memoria se come el budget.
116
+
117
+ **Prefetch** para lo streameado que necesita arrancar rápido: los primeros ms van en memoria y el resto se stremea.
118
+
119
+ ## Contrato con el código
120
+
121
+ Lo que el código tiene que poder hacer, y nada más:
122
+
123
+ ```
124
+ PostEvent(eventName, gameObject) → dispara
125
+ StopEvent(playingId, fadeMs) → detiene lo que loopea
126
+ SetSwitch(group, value, gameObject) → propiedad de objeto
127
+ SetState(group, value) → condición global
128
+ SetRTPC(param, value01, gameObject?) → parámetro, normalizado
129
+ RegisterGameObject / Unregister → ciclo de vida
130
+ ```
131
+
132
+ **Reglas del contrato** (las mismas que enforcea `rules/gamedev/audio-code.md`):
133
+
134
+ - Gameplay **nunca** referencia assets, solo nombres de evento
135
+ - Todo loop tiene un stop explícito, y se detiene al destruirse el objeto dueño
136
+ - Los RTPCs llegan normalizados y clampeados
137
+ - Un evento que falla (bank no cargado) **no rompe el frame**: degrada en silencio
138
+ - Registrar y des-registrar game objects es obligatorio — objetos filtrados son un leak clásico
139
+
140
+ ## Profiling
141
+
142
+ El middleware trae el profiler, y es la única fuente de verdad sobre lo que pasa en runtime:
143
+
144
+ - **Voces activas** vs el límite configurado, y qué se está descartando
145
+ - **CPU de audio** contra el 5-10% del frame
146
+ - **Memoria** por bank y total
147
+ - **Valores de RTPC en vivo** — sirve para descubrir que el código manda un rango equivocado
148
+ - **Árbol de contribución de voz** — por qué un sonido se oye más o menos de lo esperado
149
+ - Capturas exportables para análisis offline
150
+
151
+ Para profilear objetos de juego hay que tener la captura activa en la vista de Game Object antes de disparar los eventos, o no se registra nada.
152
+
153
+ ---
154
+
155
+ ## Proceso
156
+
157
+ 1. **Definir la jerarquía** por sistema de gameplay, con work units separados
158
+ 2. **Crear los ShareSets** de atenuación y conversión antes de los objetos, para que se reusen
159
+ 3. **Autorear los eventos** desde la event list de `/audio-spec`, uno por acción
160
+ 4. **Definir Switch/State/RTPC** con su alcance correcto, rango y default
161
+ 5. **Diseñar los buses** desde `/audio-mix`
162
+ 6. **Definir los banks** y qué va en streaming
163
+ 7. **Documentar el contrato** para el equipo de código
164
+ 8. **Escribir** — preguntar "¿Escribo la arquitectura a `<path>`?" antes de usar Write
165
+
166
+ ### Nota sobre automatización
167
+
168
+ Wwise expone WAAPI y FMOD tiene su API de scripting, así que la autoría en batch es automatizable. Hay servidores MCP de terceros que exponen WAAPI a un agente, pero al momento de escribir esto el más completo se declara **EXPERIMENTAL y explícitamente no recomendado para proyectos de producción**. Si querés explorarlo, hacelo en un proyecto de prueba, nunca sobre el proyecto real.
169
+
170
+ ---
171
+
172
+ ## Verdict
173
+
174
+ - **READY** — jerarquía por sistema, ShareSets reusados, cada parámetro con el alcance correcto, banks definidos, contrato documentado
175
+ - **CONCERNS** — la estructura existe pero hay ShareSets duplicados, parámetros con alcance equivocado (State donde va Switch), o banks sin estrategia de carga
176
+ - **BLOCKED** — falta la event list de `/audio-spec` o la estructura de buses de `/audio-mix`
177
+
178
+ ## Anti-patterns
179
+
180
+ > → Read references/anti-patterns.md
181
+
182
+ ## Next steps
183
+
184
+ - `/audio-audit` para profilear voces, CPU y memoria en runtime
185
+ - `/adaptive-music` si falta la matriz de transiciones de música
186
+ - `/audio-spec` si la event list no existe todavía
@@ -0,0 +1,69 @@
1
+ # Middleware Integration — Anti-patterns
2
+
3
+ ## Un evento por sonido
4
+
5
+ 500 eventos donde alcanzaban 40 con Switches. El código termina conociendo el nombre de cada asset, que es exactamente lo que el middleware venía a evitar. Un evento por *acción de gameplay*.
6
+
7
+ ## ShareSet por objeto
8
+
9
+ Cada sonido con su propia atenuación y su propia conversión. Cambiar el rango de audición del juego pasa de ser un cambio a ser 300. Definir los ShareSets primero y reusarlos.
10
+
11
+ ## State donde va Switch
12
+
13
+ Usar un State global para la superficie bajo el personaje produce el bug clásico: todos los personajes caminan sobre la superficie del jugador. Propiedad de objeto = Switch.
14
+
15
+ ## Switch sin default
16
+
17
+ Si el Switch no está seteado cuando llega el evento, el sonido no suena, y el bug es invisible hasta que alguien nota el silencio. Todo Switch group con default.
18
+
19
+ ## Random puro en vez de shuffle
20
+
21
+ Random repite: con 4 variantes hay 25% de escuchar la misma dos veces seguidas. Shuffle/round-robin excluyendo la última.
22
+
23
+ ## RTPC sin clamp del lado del código
24
+
25
+ Un valor de salud que llega en 150 cuando el RTPC espera 0-100 deja el parámetro pegado al extremo. Normalizar y clampear en el boundary de gameplay.
26
+
27
+ ## Loops sin stop explícito
28
+
29
+ La fuente número uno de audio colgado. Todo sonido que loopea necesita un stop, y tiene que detenerse cuando el objeto dueño se destruye.
30
+
31
+ ## Game objects sin des-registrar
32
+
33
+ Cada enemigo muerto deja su game object registrado. A las dos horas de juego hay miles. Leak clásico, y arruina el profiling.
34
+
35
+ ## Todo en un bank
36
+
37
+ Un bank monolítico se carga entero aunque el nivel use el 10%. Y cualquier cambio obliga a redistribuirlo completo. Init + Global + por nivel + por personaje.
38
+
39
+ ## SFX en streaming
40
+
41
+ Un sonido corto y frecuente en streaming llega tarde y genera accesos a disco. Streaming es para música, diálogo largo y ambientes.
42
+
43
+ ## Música en memoria
44
+
45
+ Se come el budget de audio entero. Streaming con prefetch.
46
+
47
+ ## Fallo de evento que rompe el frame
48
+
49
+ Un bank no cargado tira una excepción que llega a gameplay. El audio degrada en silencio, siempre.
50
+
51
+ ## Jerarquía por tipo de sonido
52
+
53
+ `Impacts/` con todos los impactos del juego mezclados no se navega ni se mezcla. Organizar por sistema de gameplay.
54
+
55
+ ## Un solo work unit
56
+
57
+ Dos personas trabajando producen conflictos de merge irresolubles en un archivo binario o XML gigante. Work units por sistema.
58
+
59
+ ## Confiar en el editor en vez del profiler
60
+
61
+ Disparar eventos a mano desde el editor no dice nada sobre voces activas, CPU o memoria en gameplay real. El profiler es la única fuente de verdad.
62
+
63
+ ## Profilear game objects sin activar la captura
64
+
65
+ Sin la captura activa en la vista de Game Object antes de disparar los eventos, no se registra nada y parece que el sistema no funciona.
66
+
67
+ ## Automatizar sobre el proyecto de producción
68
+
69
+ Las herramientas de autoría en batch vía WAAPI son potentes y las de terceros están mayormente en estado experimental. Probar en un proyecto descartable; el proyecto real no es el lugar.
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: midi-compose
3
+ description: "Genera archivos MIDI (.mid) desde un spec JSON con scripts Python stdlib-only. Sin dependencias: escribe el Standard MIDI File binario directo. Usar para: exportar una composicion a MIDI, bocetar stems, generar loops o capas para middleware."
4
+ category: "audio"
5
+ argument-hint: "[spec-path | new | validate]"
6
+ user-invocable: true
7
+ allowed-tools: Read, Glob, Grep, Write, Edit, Bash
8
+ ---
9
+ # midi-compose — Composición a MIDI
10
+
11
+ Baja decisiones musicales a un `.mid` real. Las decisiones las toma `/music-composition`; esta skill las escribe.
12
+
13
+ **Sin dependencias.** El Standard MIDI File es un formato binario documentado y `scripts/compose_midi.py` lo emite con la stdlib de Python. No hay que instalar `midiutil`, `music21` ni nada. Corre en la máquina del usuario tal como se copia.
14
+
15
+ ## Alcance
16
+
17
+ **Sí:** escribir `.mid` multipista con tempo, compás, tonalidad, programas General MIDI, velocities y percusión.
18
+
19
+ **No:** renderizar a audio (requiere FluidSynth + soundfonts, dependencias de sistema fuera de scope), tomar decisiones musicales (→ `/music-composition`), notación.
20
+
21
+ ## Cuándo usar
22
+
23
+ - Ya hay material decidido (progresión, melodía, groove) y hace falta el archivo
24
+ - Bocetar stems para un sistema de capas de `/adaptive-music`
25
+ - Generar variantes de un tema por transformación (aumentación, cambio de modo)
26
+ - Entregar MIDI a un compositor o importarlo a un DAW/middleware
27
+
28
+ ---
29
+
30
+ ## Modos
31
+
32
+ | Modo | Qué hace |
33
+ |---|---|
34
+ | `spec-path` | Genera el `.mid` desde un spec JSON existente |
35
+ | `new` | Arma el spec desde cero a partir de material musical, y después genera |
36
+ | `validate` | Solo valida el spec y reporta, sin escribir nada |
37
+
38
+ ## Workflow
39
+
40
+ ### 1. Armar el spec
41
+
42
+ Un objeto JSON. Todos los tiempos están en **beats** (negras), no en ticks — el script convierte.
43
+
44
+ ```json
45
+ {
46
+ "name": "Explore Calm",
47
+ "tempo": 84,
48
+ "ppq": 480,
49
+ "time_signature": [4, 4],
50
+ "key": "D",
51
+ "mode": "minor",
52
+ "tracks": [
53
+ {
54
+ "name": "Bed (strings)",
55
+ "channel": 0,
56
+ "program": 48,
57
+ "notes": [
58
+ { "pitch": "D3", "start": 0, "duration": 4, "velocity": 62 }
59
+ ]
60
+ }
61
+ ]
62
+ }
63
+ ```
64
+
65
+ Hay un ejemplo completo de tres pistas en `templates/example-composition.json`.
66
+
67
+ **Campos del spec**
68
+
69
+ | Campo | Default | Notas |
70
+ |---|---|---|
71
+ | `name` | `"untitled"` | Va al meta de nombre de secuencia |
72
+ | `tempo` | `120` | BPM, 1-600 |
73
+ | `ppq` | `480` | Ticks por negra, 24-32767 |
74
+ | `time_signature` | `[4, 4]` | Denominador potencia de 2 |
75
+ | `key` / `mode` | `"C"` / `"major"` | Solo para el meta de tonalidad; no transpone |
76
+ | `tracks[].channel` | índice del track | 0-15, único por track |
77
+ | `tracks[].program` | `0` | Programa General MIDI 0-127 |
78
+ | `tracks[].drums` | `false` | Declaralo en el track del canal 9 |
79
+ | `notes[].pitch` | — | `60`, `"C4"`, `"F#3"`, `"Bb5"`. C4 = 60 |
80
+ | `notes[].start` | `0` | En beats desde el inicio |
81
+ | `notes[].duration` | `1` | En beats, > 0 |
82
+ | `notes[].velocity` | `90` | 1-127 |
83
+
84
+ ### 2. Validar antes de escribir
85
+
86
+ ```bash
87
+ python skills/midi-compose/scripts/compose_midi.py spec.json --validate
88
+ ```
89
+
90
+ Salida `[PASS]` con el resumen, más `[WARN]` por cada problema no fatal. Un `[FAIL]` nombra el track y la nota exactos.
91
+
92
+ ### 3. Generar
93
+
94
+ ```bash
95
+ python skills/midi-compose/scripts/compose_midi.py spec.json -o salida.mid
96
+ ```
97
+
98
+ Antes de escribir el `.mid` o el spec a disco, preguntar al usuario "¿Escribo esto a `<path>`?" — sobre todo si el archivo ya existe.
99
+
100
+ ---
101
+
102
+ ## Límites de MIDI que importan
103
+
104
+ - **16 canales.** El canal 9 (10 en base 1) está reservado a percusión por General MIDI, así que quedan **15 pistas melódicas**. Si necesitás más, son varios archivos.
105
+ - **Un programa por canal a la vez.** Cambiar de instrumento a mitad de pista pide otro canal.
106
+ - **Velocity no es volumen.** Es intensidad de ataque; el sampler decide qué hacer con ella.
107
+ - **MIDI no lleva timbre.** El programa GM es una sugerencia; cómo suena depende del soundfont o del instrumento del DAW.
108
+ - **`pitch` en el canal de percusión es el instrumento**, no una altura: 36 = bombo, 38 = redoblante, 42 = hi-hat cerrado, 46 = hi-hat abierto.
109
+
110
+ ## Feel y microtiming
111
+
112
+ El spec acepta `start` fraccional, así que el feel se escribe directo: para atrasar un backbeat 15 ms a 84 BPM, `15 / (60000 / 84 / 1) ≈ 0.021` beats. Un desvío de 10-30 ms es feel; arriba de ~50 ms se percibe como error. Ver `references/smf-format.md` para la relación tick/ms.
113
+
114
+ ## Formato
115
+
116
+ > → Read references/smf-format.md para la estructura binaria del SMF, el encoding VLQ y la tabla de programas GM más usados
117
+
118
+ ---
119
+
120
+ ## Verdict
121
+
122
+ - **COMPLETE** — el `.mid` se escribió y la verificación de estructura pasa
123
+ - **CONCERNS** — se escribió pero hay `[WARN]`: contenido melódico en el canal de percusión, o pistas cerca del límite de 15
124
+ - **FAIL** — el spec no valida; el mensaje nombra el track y la nota
125
+
126
+ Verificación rápida de que el archivo es un SMF:
127
+
128
+ ```bash
129
+ python -c "print(open('salida.mid','rb').read(4) == b'MThd')" # True
130
+ ```
131
+
132
+ ## Next steps
133
+
134
+ - `/music-composition` si el material musical todavía no está decidido
135
+ - `/adaptive-music` para armar el sistema de capas que consume estos stems
136
+ - `/middleware-integration` para importar los segmentos a Wwise/FMOD
@@ -0,0 +1,129 @@
1
+ # Standard MIDI File — formato binario
2
+
3
+ Referencia de lo que emite `scripts/compose_midi.py`. Útil para debuggear un `.mid` a mano o extender el script.
4
+
5
+ ## Estructura general
6
+
7
+ Un SMF es una secuencia de chunks. Cada chunk: 4 bytes de tipo + 4 bytes de longitud (big-endian) + cuerpo.
8
+
9
+ ```
10
+ MThd <len=6> <format:2> <ntrks:2> <division:2>
11
+ MTrk <len> <eventos...>
12
+ MTrk <len> <eventos...>
13
+ ```
14
+
15
+ **Formatos:**
16
+
17
+ | Formato | Qué es |
18
+ |---|---|
19
+ | 0 | Un solo track con todo mezclado |
20
+ | 1 | Múltiples tracks simultáneos; el track 0 es el conductor (tempo/compás) |
21
+ | 2 | Tracks independientes, secuenciales. Casi no se usa |
22
+
23
+ El script emite **formato 1**: track 0 con tempo, compás y tonalidad sin notas, y un `MTrk` por pista musical. Es lo que esperan los DAWs.
24
+
25
+ **División** — con el bit alto en 0, es *ticks por negra* (PPQ). 480 es el valor estándar de la industria y divide bien tresillos (160) y semicorcheas (120).
26
+
27
+ ## Delta-times y VLQ
28
+
29
+ Cada evento arranca con un delta-time: cuántos ticks pasaron desde el evento anterior *en ese track*. Se codifica como Variable-Length Quantity: 7 bits de datos por byte, MSB = 1 en todos los bytes menos el último.
30
+
31
+ | Valor | Bytes |
32
+ |---|---|
33
+ | 0 | `00` |
34
+ | 127 | `7F` |
35
+ | 128 | `81 00` |
36
+ | 480 | `83 60` |
37
+ | 8192 | `C0 00` |
38
+
39
+ Un delta mal codificado desplaza todo lo que sigue: es la causa número uno de un `.mid` que "suena raro a partir de la mitad".
40
+
41
+ ## Eventos de canal
42
+
43
+ `n` es el canal, 0-15.
44
+
45
+ | Evento | Bytes | Notas |
46
+ |---|---|---|
47
+ | Note Off | `8n kk vv` | `kk` nota, `vv` velocity de release |
48
+ | Note On | `9n kk vv` | `vv = 0` equivale a Note Off |
49
+ | Control Change | `Bn cc vv` | cc 7 = volumen, 10 = pan, 11 = expresión |
50
+ | Program Change | `Cn pp` | Instrumento GM |
51
+ | Pitch Bend | `En ll mm` | 14 bits, centro en `00 40` |
52
+
53
+ **Orden en el mismo tick:** el Note Off tiene que ir antes del Note On cuando se reataca la misma nota, o el sintetizador corta la nota nueva. El script ordena por `(tick, 0=off, 1=on)` justamente por esto.
54
+
55
+ ## Meta eventos
56
+
57
+ Todos arrancan con `FF`, después tipo, después longitud en VLQ.
58
+
59
+ | Meta | Bytes | Contenido |
60
+ |---|---|---|
61
+ | Nombre de secuencia/track | `FF 03 <len> <texto>` | |
62
+ | Tempo | `FF 51 03 tttttt` | Microsegundos por negra = `60000000 / BPM` |
63
+ | Compás | `FF 58 04 nn dd cc bb` | `dd` = log2(denominador); `cc` = 24; `bb` = 8 |
64
+ | Tonalidad | `FF 59 02 sf mi` | `sf` sostenidos (+) o bemoles (−), signed; `mi` 0 mayor / 1 menor |
65
+ | End of Track | `FF 2F 00` | **Obligatorio** al final de cada `MTrk` |
66
+
67
+ Sin `End of Track` muchos lectores rechazan el archivo entero.
68
+
69
+ ## Conversión de tiempo
70
+
71
+ ```
72
+ ticks = beats × PPQ
73
+ ms_por_tick = 60000 / (BPM × PPQ)
74
+ ```
75
+
76
+ A 120 BPM con PPQ 480: 1 tick = 1.041 ms. A 84 BPM: 1 tick = 1.488 ms.
77
+
78
+ Para escribir un desvío de feel en beats: `beats = ms × BPM / 60000`.
79
+
80
+ ## Programas General MIDI usados seguido
81
+
82
+ | # | Instrumento | # | Instrumento |
83
+ |---|---|---|---|
84
+ | 0 | Acoustic Grand Piano | 48 | String Ensemble 1 |
85
+ | 4 | Electric Piano 1 | 49 | String Ensemble 2 |
86
+ | 11 | Vibraphone | 52 | Choir Aahs |
87
+ | 19 | Church Organ | 56 | Trumpet |
88
+ | 24 | Nylon Guitar | 57 | Trombone |
89
+ | 25 | Steel Guitar | 60 | French Horn |
90
+ | 32 | Acoustic Bass | 68 | Oboe |
91
+ | 33 | Finger Bass | 71 | Clarinet |
92
+ | 40 | Violin | 73 | Flute |
93
+ | 42 | Cello | 89 | Pad 2 (warm) |
94
+ | 46 | Orchestral Harp | 94 | Pad 7 (halo) |
95
+
96
+ ## Percusión (canal 9)
97
+
98
+ El `pitch` es el instrumento, no la altura.
99
+
100
+ | # | Instrumento | # | Instrumento |
101
+ |---|---|---|---|
102
+ | 35 | Acoustic Bass Drum | 44 | Pedal Hi-Hat |
103
+ | 36 | Bass Drum 1 | 46 | Open Hi-Hat |
104
+ | 38 | Acoustic Snare | 49 | Crash Cymbal 1 |
105
+ | 39 | Hand Clap | 51 | Ride Cymbal 1 |
106
+ | 40 | Electric Snare | 54 | Tambourine |
107
+ | 41 | Low Floor Tom | 56 | Cowbell |
108
+ | 42 | Closed Hi-Hat | 76 | Hi Wood Block |
109
+
110
+ ## Verificar un .mid a mano
111
+
112
+ ```bash
113
+ python - <<'EOF'
114
+ import struct
115
+ data = open("salida.mid", "rb").read()
116
+ assert data[:4] == b"MThd"
117
+ length, fmt, ntrks, div = struct.unpack(">IHHH", data[4:14])
118
+ print("format", fmt, "| tracks", ntrks, "| ppq", div)
119
+ pos, seen = 14, 0
120
+ while pos < len(data):
121
+ name, size = data[pos:pos+4], struct.unpack(">I", data[pos+4:pos+8])[0]
122
+ body = data[pos+8:pos+8+size]
123
+ assert name == b"MTrk" and body[-3:] == b"\xff\x2f\x00", f"track {seen} malformado"
124
+ pos += 8 + size
125
+ seen += 1
126
+ assert seen == ntrks and pos == len(data)
127
+ print("SMF valido")
128
+ EOF
129
+ ```