1. Síntoma
Las elecciones de historia clicables estaban en pantalla con document.pointerLockElement activo. El cursor estaba capturado para vuelo. ESC también abría otra UI de saltar el tutorial si el aviso de recaptura era incorrecto.
2. Impacto en el jugador
Una elección a tiempo o crítica (incluido el hail de Varek) no se podía tomar con el ratón. Los jugadores golpeaban ESC y o recapturaban el lock al instante o abrían Saltar. Quien usaba mando no sufría la ruta de clic, así que el bug parecía “aleatorio” según el dispositivo.
3. Reproducción
Entrar a Last Run con teclado/ratón, mantener pointer lock, llegar a un hail con botones 1/2 visibles. Intentar clic. Pulsar ESC. Confirmar si el lock vuelve al instante y si se abre Saltar. lastRunShouldShowReleaseMouseHint es verdadero solo cuando pointerLocked && clickableChoiceVisible && dominantDevice !== "gamepad".
4. Ownership del sistema
La política de pointer lock para historia vs combate live está en pointer-lock-policy.js. Las reglas del aviso de Last Run están en last-run-map-lesson.js / last-run-from-verge.js. Muchos otros sistemas llaman exitPointerLock al abrir estaciones, mapas o historia — el incidente es el handoff que faltaba cuando un overlay clicable abre sin salir del lock.
5. Supuesto erróneo
Si una UI es visible, el ratón está disponible. Si el jugador pulsa ESC, quiere el lock otra vez. Una sola frase de recaptura (“pulsa ESC”) sirve antes y después de que el navegador ya haya soltado el lock.
6. Causa raíz
Pointer lock y DOM clicable son mutuamente excluyentes en Chromium de escritorio. Last Run mostraba elecciones sin salir del lock. Tras soltar el lock con ESC, se seguía mostrando el mismo texto “pulsa ESC”, así que ESC quedaba sobrecargado (recapturar vs Saltar). Mientras el hail seguía abierto, la recaptura volvía a robar el cursor.
7. Arreglo mínimo
Mostrar el aviso de soltar el ratón solo con pointer locked + elecciones clicables visibles + no mando. Mientras ese hail está abierto, ESC no recaptura. Tras ESC, el aviso pasa a clic-para-recapturar y el lock se reintenta solo tras el cooldown del navegador. No cambiar progresión ni recompensas (un toast de Supervivencia posterior se hizo no modal para no soltar el lock).
8. Test de regresión
last-run-from-verge.test.mjs cubre el aviso de ESC y el copy de recaptura. lastRunShouldShowReleaseMouseHint se testa con los tres predicados (lock, elecciones, dispositivo).
9. Validación de release
Log de desarrollo 19 ago 2026, v0.4.148–v0.4.153: aviso ESC, sin recaptura durante el hail, aviso de clic tras ESC. El QA Electron posterior de Last Run (celdas teclado vs mando) es la comprobación estilo release de este corte de input.
10. Lección generalizada
Los modos de captura son excluyentes con overlays clicables. Posee la transición: sal del lock, dile al jugador cómo, y no ates ESC a dos verbos. La UI por dispositivo (ocultar el aviso en mando) es corrección, no pulido.
Extracto simplificado
Simplificado / pseudocódigo para enseñar. No es un volcado de archivos de producción.
El predicado del aviso, simplificado.
// Simplified teaching excerpt — not CONTRABAND production source.
function shouldShowReleaseMouseHint({ pointerLocked, clickableChoiceVisible, dominantDevice }) {
return pointerLocked && clickableChoiceVisible && dominantDevice !== 'gamepad';
}
// While hail open: ESC must not recapture.
// After ESC: browser already dropped lock — ask for a click, not another ESC.Evidencia pública
- Log de desarrollo de CONTRABAND, 19 ago 2026, v0.4.148–v0.4.153 (aviso ESC / recaptura).
- lastRunShouldShowReleaseMouseHint; pointer-lock-policy.js historia vs combate live.
- last-run-from-verge.test.mjs