Cinemático de Inicio de Hermes
Hermes Desktop ya incluye una película introductoria de 22 segundos — siete compases, una vista de cubo en vivo, agentes paralelos, la marca Nous — con sonido sintetizado. Existe, pero casi nadie la ve dos veces: la elegibilidad es guestOnboardingEnabled && !seen.
Esta habilidad hace que la película se reproduzca en cada inicio y permite superponer tu propia pista de voz sobre los cues incorporados.
Cuándo usarlo
- "Haz que la animación de inicio de Hermes se reproduzca cada vez."
- "Pon mi audio en la animación de arranque — superpuesto, sin reemplazar los efectos de sonido."
- Cualquier cambio en
apps/desktop/src/components/intro-reveal/.
Qué ya es la película
| Pieza | Ruta |
|---|---|
| Puerta (cuándo se reproduce) | apps/desktop/src/components/intro-reveal/index.tsx |
| Elegibilidad + tienda de fase | apps/desktop/src/store/intro-reveal.ts |
| Ventana de superposición de arranque | src/components/intro-reveal/intro-root.tsx |
| Reloj, puntuación, lienzo | use-intro-clock.ts, timeline.ts, viewport-cube.ts |
| Cues sintéticos | sound.ts |
| Ventana nativa | electron/intro-reveal-window.ts, electron/main.ts |
Matemáticas de sincronización: INTRO_TOTAL_MS = 17 600 es tiempo de partitura; el tiempo real es × INTRO_PACE (1.25) = 22 000 ms, más INTRO_EXIT_MS 900 ms → 23 125 ms en total.
Reproducción en cada inicio — tres ediciones, todas necesarias
src/store/intro-reveal.ts—isIntroRevealEnabled()debe dejar de requerir incorporación de invitados;shouldPlayFirstRunIntro()devuelve!playedThisSessionfuera de incorporación. Una banderaplayedThisSessiona nivel de módulo es obligatoria: el efecto de la puerta se vuelve a ejecutar cada vez que la fase vuelve a'hidden', que es exactamente lo que hace terminar la película — sin la bandera, se repetiría infinitamente.src/components/intro-reveal/intro-root.tsx— la ventana de superposición se protege conisOnboardingEnabled()y devolvería antes de pintar. Cámbiala al mismo predicado que el renderizador principal.electron/main.ts—introRevealControllerse construye conenabled: GUEST_ONBOARDING, por lo que el proceso principal rechazaopen. Ponlo entrue; la vivacidad se decide en el renderizador.
beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() de la incorporación se autoprotegen con isOnboardingEnabled(), por lo que no hacen nada cuando la incorporación está desactivada — no se necesita cambio adicional.
Interruptor de desactivación: localStorage['hermes-intro-reveal-always-v1'] = '0', o inicia con HERMES_SKIP_INTRO=1 / --skip-intro.
Superposición de una pista de audio
Coloca un archivo en apps/desktop/public/intro-voiceover.mp3 (vite copia public/ a dist/ textualmente), luego en sound.ts:
export function startVoiceover(cutAtMs: number): { stop: () => void }
— crea un elemento Audio, establece volume, inícialo, y setTimeout(stop, cutAtMs - 300) para que los últimos 300 ms disminuyan en lugar de cortar. Llámalo junto a startPad() en use-intro-clock.ts y detenlo en la limpieza del efecto.
Usa un elemento <audio>, nunca fetch() + decodeAudioData(). El renderizador empaquetado se ejecuta en file://, donde fetch y decodeAudioData están bloqueados por CORS; los elementos multimedia cargan assets file:// sin problemas.
Las dos pistas son independientes por construcción — el audio de elementos y los cues WebAudio se mezclan a nivel del sistema operativo. Nada se silencia mutuamente. Conecta $hapticsMuted a ambas, y suscríbete en lugar de leerlo una vez, o el interruptor de silencio se desincroniza.
Punto de corte:
const cutMs = Math.round((INTRO_TOTAL_MS + INTRO_EXIT_MS) * INTRO_PACE) // 23 125
Envíalo a una aplicación empaquetada
Las ediciones del código fuente no hacen nada hasta que se vuelva a empaquetar el asar. Dos trampas aquí, ambas vale la pena revisar:
Encuentra la aplicación que el usuario realmente inicia — nunca asumas /Applications.
ps aux | grep '[H]ermes.app/Contents/MacOS/Hermes'
defaults read com.apple.dock persistent-apps | grep -o '/[^"]*Hermes\.app'
Las actualizaciones de Hermes llegan a ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app y el Dock a menudo apunta allí, mientras que /Applications/Hermes.app puede ser una copia obsoleta 0.0.0. Confirma con CFBundleShortVersionString (0.17.x es real, 0.0.0 no lo es).
Actualiza también app.asar.unpacked/dist/. Las compilaciones empaquetadas listan dist/** en asarUnpack, y resolveRendererIndex() prefiere esa copia — reempaquetar solo el asar y el árbol desempaquetado obsoleto lo sombrea.
npm install --workspace apps/desktop --ignore-scripts \
--engine-strict=false --min-release-age=0
cd apps/desktop && node ../../node_modules/vite/bin/vite.js build
node scripts/bundle-electron-main.mjs # las ediciones del proceso principal también necesitan este paso
asar extract "$RES/app.asar" "$WORK"
rm -rf "$WORK/dist" && cp -R apps/desktop/dist "$WORK/dist"
asar pack "$WORK" "$WORK/app.asar"
mv -f "$WORK/app.asar" "$RES/app.asar"
cp -R apps/desktop/dist/. "$RES/app.asar.unpacked/dist/"
codesign --force --deep -s - "$APP" # ad-hoc; Hermes se distribuye firmado ad-hoc
--ignore-scripts importa en redes lentas: la descarga del binario de Electron no es necesaria para construir o reempaquetar, solo para ejecutar dev.
Verificar sin Electron
Renderiza la película en un navegador simple — sin empaquetado, sin descarga de binario:
cd apps/desktop && node ../../node_modules/vite/bin/vite.js --host 127.0.0.1 --port 5174
page.add_init_script("""
window.hermesDesktop = {
guestOnboardingEnabled: true, skipIntro: false,
introReveal: { ready(){}, skip(){},
open(){ return Promise.resolve() }, close(){ return Promise.resolve() },
onSkip(){ return () => {} }, onClosed(){ return () => {} } }
};
""")
page.goto("http://127.0.0.1:5174/?win=intro")
page.evaluate("import('/src/components/intro-reveal/intro-root.tsx').then(m => m.mountIntroReveal())")
La propia llamada de montaje de la ruta no hace nada sin el puente; la importación manual después de simular es lo que realmente pinta. Para verificar la pista de audio, simula window.Audio y registra las instancias — afirma que currentTime avanza con la película y que paused === true, volume === 0 en el corte.
Pruebas de contrato que vale la pena mantener en src/store/intro-reveal.test.ts:
- se reproduce en un inicio normal incluso cuando
hermes-intro-reveal-seen-v1está establecido - se reproduce exactamente una vez —
finishIntroReveal()no debe rearmarla - respeta la clave de exclusión y la bandera de inicio
skipIntro - deja intacta la semántica de primera ejecución de la incorporación
Errores comunes
- Eliminar
playedThisSessionprovoca un bucle de reproducción infinito, no un inicio sin errores. INTRO_TOTAL_MSes tiempo de partitura. OlvidarINTRO_PACEcorta la voz 4.4 s antes.- La bandera
enableddel proceso principal y la propia protección de la ventana de superposición son puertas separadas; falla cualquiera y no se renderiza nada, sin error. - Cuatro capas independientes mantienen la pantalla regresando (cierre normal, Esc + 1.2 s de respaldo, deadman local, vigilante del proceso principal de 34 s). No elimines una mientras cambias la sincronización.
hermes updatereemplaza la aplicación por la compilación oficial. El parche persiste en git; vuelve a ejecutar el instalador después de actualizar.