📊 Visión General del Código

ArcLycée es un RPG 2D escrito en JavaScript vanilla (ES Modules) sin frameworks ni bundlers. Todo el renderizado se hace con HTML5 Canvas, los efectos de sonido se generan por código con Web Audio API, la música de fondo usa archivos MP3 con crossfade, y todos los sprites se dibujan proceduralmente.

MétricaValor
Archivos JavaScript53
Líneas de código totales~37,500
Resolución del canvas960 × 540 px (16:9)
FPS objetivo60
Idiomas soportados3 (Español, Français, English)
Claves de traducción~1,100+
Efectos de sonido procedurales60 métodos
Mundos jugables12 + mapa del mundo
Mini-juegos4 (batú, calibración, programación, cables)

Estructura de Directorios

DirectorioContenidoArchivos
js/motor/Game loop, input, renderizado, sonido, guardado, configuración6
js/escenas/Menú principal, selección de personaje, intro, final cinematográfico4
js/mundos/13 niveles jugables: cuevas, asentamientos, montaña, coloniales, acuáticos, jurídico, laboratorio, LFSD, lago, cenote, museo13
js/mecanicas/Combate, diálogo, inventario, álbum de fotos, 4 mini-juegos, misiones, reputación, registro11
js/personajes/Jugador (Pepito/Pepita) + 3 compañeros (Magnoboot, Viralata, Cemí)4
js/idiomas/Traducciones ES/FR/EN + gestor de idiomas4
js/mapas/Mapa Leaflet con capas de datos reales (taínos, coloniales, naufragios, museos)4
js/clima/Sistema de clima + huracanes2

Tecnologías

TecnologíaUso
HTML5 CanvasRenderizado del juego — todo se dibuja por código, sin sprites externos
JavaScript ES ModulesArquitectura modular sin frameworks ni bundlers
Web Audio API60 efectos de sonido procedurales + música de fondo con 24 MP3
LeafletJS + Stadia MapsMapas interactivos con 76 sitios arqueológicos reales de RD y Haití
Kaplay.jsTouch input (joystick analógico y D-pad)
CSS3Pantalla de carga, controles táctiles responsivos

Principios de Diseño

🔄 Game Loop y Gestión de Escenas

Archivo: js/motor/juego.js (1,659 líneas) — el orquestador principal del juego.

Bucle Principal

El juego corre a ~60 FPS usando requestAnimationFrame. Cada frame calcula el delta time (tiempo entre frames) y ejecuta dos fases: actualizar (lógica) y dibujar (renderizado).

El delta time se limita a 0.1 segundos máximo para evitar "teletransportación" del jugador cuando se vuelve de otra pestaña:

dt = Math.min((tiempoActual - tiempoAnterior) / 1000, 0.1)

Sistema de Escenas

El juego tiene 15 escenas registradas. Solo una escena está activa a la vez.

TipoEscenas
InterfazMenú principal, selección de personaje, intro cinematográfica, final cinematográfico
Mundos jugables (9)Cuevas del Pomier, Asentamiento I, Asentamiento II, La Isabela, Zona Colonial, Mundo Acuático, Aeropuerto, Laboratorio, LFSD
NavegaciónMapa del mundo (mapa de tiles)
SecretoSelector de niveles (Código Konami)

Ciclo de Vida de una Escena

Cada escena implementa tres métodos:

El cambio de escena se realiza con juego.cambiarEscena('nombre'), que llama salir() en la escena anterior, crea al jugador si la nueva escena es jugable, y llama iniciar(juego) en la nueva. Al volver al mapa del mundo se auto-guarda la partida.

Sistema de Overlays

Los overlays se dibujan encima de la escena activa, en este orden (de abajo hacia arriba):

  1. Escena base
  2. Efectos climáticos (lluvia, tormentas)
  3. Combate — encuentro con enemigo
  4. Batú — mini-juego de pelota taína
  5. Mini-juegos LFSD — calibración, programación, cables
  6. Registro de juego — Game Log (tecla L)
  7. Medidor de reputación — barra en el HUD
  8. Álbum de fotos — galería (tecla P)
  9. Indicador de foto/selfie (teclas T/G)
  10. Inventario — mochila (tecla I)
  11. Toasts — notificaciones flotantes (máximo 4 visibles apilados)
  12. Selector secreto — siempre encima de todo

Sistema de Toasts

juego.mostrarToast(texto, duracion) agrega un mensaje flotante. Se apilan verticalmente (máximo 4), con fade-in de 0.3s y fade-out en el último segundo. Se usan para recoger objetos, completar misiones, alertas de peligro, etc.

Sistema de Muerte

Cuando el jugador llega a 0 de vida: sonido cómico "wah wah waaah", mensaje aleatorio de muerte (4s), pausa de 2.5s, luego respawn en el mapa del mundo con 30% de vida restaurada. Un flag _muerteEnCurso previene disparar la muerte múltiples veces.

🎮 Sistema de Input

Archivo: js/motor/entrada.js (494 líneas) — capa de abstracción unificada para teclado y táctil.

Controles de Teclado

AcciónTeclasUso
MoverWASD / FlechasExploración y menús
SaltarEspacioSolo en cuevas (plataforma)
InteractuarE / EnterHablar con NPCs, usar objetos
InventarioIAbrir/cerrar mochila
EspecialFDetectar metal (Magnoboot)
Volver al mapaMSalir del nivel actual
Mapa realRAbrir mapa Leaflet de referencia
RegistroLGame Log (misiones)
FotoTTomar foto de entidad cercana
SelfieGTomar selfie con entidad
ÁlbumPVer galería de fotos
Zoom+ / -Zoom en el mapa del mundo
Código Konami↑↑↓↓←→←→Selector secreto de niveles

API Unificada

Toda consulta de input pasa por entrada.estaPresionada(accion), que combina teclado y táctil. Internamente usa dos Map: teclasPresionadas para teclado y accionesTocadas para touch.

Controles Táctiles

Dos modos intercambiables desde el menú de Opciones (preferencia guardada en localStorage):

5 botones de acción en el lado derecho: A (acción), B (cancelar), X (inventario), Y (especial), SALTO. Usan touchstart/touchend en vez de click para evitar el retraso de 300ms en móviles.

Patrón de Bloqueo de Input

Para evitar que una tecla se "repita" al mantenerla presionada, se usa un flag bloqueoEntrada: se activa al procesar la acción y se desactiva solo cuando la tecla se suelta. Al entrar a una escena donde la tecla de activación podría estar presionada, se inicializa en true.

Prevención de Teclas Pegadas

El evento window.blur limpia todas las teclas presionadas. Esto evita que al cambiar de pestaña con una tecla de movimiento presionada, el jugador siga moviéndose al volver.

🖼️ Renderizado y Efectos Visuales

Archivo: js/motor/renderizado.js (199 líneas) — wrapper de Canvas 2D.

Configuración del Canvas

Resolución fija de 960×540 (mitad de 1080p, fácil de escalar). El suavizado de imagen está deshabilitado (imageSmoothingEnabled = false) para preservar la nitidez del estilo pixel art.

Operaciones Principales

MétodoDescripción
limpiar()Borra el frame anterior con clearRect
dibujarRectangulo()Rectángulo relleno (colisiones, placeholders)
dibujarTexto()Texto con fuente, color y alineación configurables
dibujarBarra()Barra de progreso con porcentaje clamped [0, 1] y borde negro

Efecto de Cueva (Linterna)

aplicarFiltroCueva(jugadorX, jugadorY, radioLuz) crea un efecto de antorcha usando un gradiente radial con centro transparente y bordes opacos:

Efecto de Sacudida (Shake)

Los mundos acuáticos usan un timer _sacudida que al activarse (por colisión con lancha, mordida de tiburón o medusa) aplica una oscilación horizontal al sprite del jugador:

ctx.translate(Math.sin(tiempoTotal * 50) * amplitud * intensidad, 0)
// intensidad = _sacudida / duracionInicial (decae linealmente)

Depth Sorting

En los mundos acuáticos, las entidades (NPCs, tortugas, medusas, jugador) se ordenan por su coordenada Y antes de dibujar, creando un efecto natural de profundidad donde los objetos más cercanos tapan a los más lejanos.

🔊 Sistema de Audio Procedural

Archivo: js/motor/sonido-procedural.js (1,931 líneas) — 60 efectos de sonido generados por código.

Los efectos de sonido se sintetizan en tiempo real combinando osciladores (sine, square, sawtooth, triangle), filtros (lowpass, highpass, bandpass), buffers de ruido blanco y envolventes de ganancia. La música de fondo se reproduce desde archivos MP3 con crossfade automático.

Patrones Fundamentales

Inicialización Perezosa

Los navegadores bloquean Web Audio hasta la primera interacción del usuario. _asegurarContexto() crea el AudioContext solo cuando se necesita por primera vez.

Barrido de Frecuencia

La mayoría de los sonidos usan un barrido de frecuencia (rampa exponencial o lineal) para crear sensación de movimiento:

osc.frequency.setValueAtTime(frecInicial, ahora)
osc.frequency.exponentialRampToValueAtTime(frecFinal, ahora + duracion)

Envolvente de Amplitud (ADSR simplificado)

Controla el volumen a lo largo del tiempo: ataque rápido → sustain → decaimiento exponencial hacia silencio.

Ruido Blanco

Se genera llenando un buffer con valores aleatorios (-1 a 1), luego se filtra para crear diferentes texturas: pasos (lowpass 800 Hz), chispas (highpass 2000 Hz), multitud (bandpass 600 Hz).

LFO (Oscilador de Baja Frecuencia)

Un oscilador a baja frecuencia (4-30 Hz) modula otro parámetro (generalmente la frecuencia principal) para crear vibrato, wobble o sirenas:

lfo.frequency.value = 4     // 4 Hz de wobble
lfoGain.gain.value = 8      // ±8 Hz de profundidad de modulación
lfo → lfoGain → osc.frequency

Catálogo de Sonidos (60 métodos)

Locomoción y Feedback (7)

MétodoTécnicaDescripción
salto()Square 150→400 HzSalto del jugador
aterrizar()Sine 100→40 HzAterrizaje
recoger()Dos notas square (523, 659 Hz)Recoger objeto
paso()Ruido blanco 40ms, lowpass 800 HzPaso en el suelo
goteo()Sine 800→240 Hz, randomizadoGota de agua
dano()Sawtooth 200→50 HzDaño genérico
muerte()Tres notas sawtooth descendentes con LFO"Wah wah waaah" cómico

Combate (17)

MétodoDescripción
combateAtacar()Sawtooth 400→100 Hz agresivo
combateHablar()Dos sines simultáneos (300, 350 Hz), pacífico
combateNegociar()Dos notas square alternas con pausa
combateRedesSociales()Dos sines "ding-ding" (notificación de teléfono)
combateProtestas()Tres pulsos square (ritmo de coro de protesta)
combateDenuncia()Ruido + sine 150→60 Hz (sello oficial)
combateViaLegal()Triangle 180→90 Hz (mazo judicial)
combateAtrapar()Ruido bandpass 1200 Hz (red de pesca)
combatePescar()Sine 200→800 Hz + ruido lowpass (arpón + splash)
combateProtegerCoral()Acorde de tres notas mayor cálido
combateAlertarBuzos()Square 700 Hz con LFO 8 Hz (silbato submarino)
combateLey318()Triangle 200→100 Hz (golpe judicial)
combateEvidenciaForense()Dos beeps sine (cámara/escáner)
combateInterpol()Square 600→800→600 Hz (sirena policial)
combateUnesco()Acorde solemne mayor (autoridad diplomática)
combateVictoria()Arpegio ascendente triunfante
combatePacificado()Acorde mayor sostenido 0.5s (paz lograda)

Batú (5)

MétodoDescripción
batuGolpe()Triangle 120→60 Hz (impacto pelota-piedra)
batuRebote()Sine 300→150 Hz (rebote de goma)
batuPunto()Arpegio ascendente de tres notas
batuSaque()Sine 600→800 Hz (silbato de saque)
batuMultitud()Ruido blanco bandpass 600 Hz (rugido del público)

Ambiente Acuático (4)

MétodoDescripción
cantoBallenaCerca()Tres notas sine largas con LFO individual, lowpass 600 Hz (canto de ballena jorobada)
manatiLlamada()Sine 80→120→90→110 Hz wobble (llamada ondulante del manatí)
lanchaImpacto()3 capas: impacto triangle 200→40 Hz + splash de ruido + grito Wilhelm sintético (sawtooth con LFO 30 Hz)
mordidaTiburon()3 capas: crujido de mandíbula (ruido highpass 3000 Hz) + impacto profundo + gruñido con LFO 12 Hz

Mini-juegos LFSD (15)

MétodoDescripción
calibracionTono(freq)Sine a frecuencia dada, 0.2s
calibracionExito()Arpegio mayor ascendente (4 notas)
calibracionFallo()Dos pulsos sawtooth descendentes
calibracionAjuste()Ruido highpass 2000 Hz, 20ms (click de dial)
bloqueColocar()Square 800→400 Hz + ruido (bloque encajado)
bloqueQuitar()Sine 300→600 Hz ascendente (bloque retirado)
robotMoverse()Sawtooth 120→180→120 Hz modulado (servo motor)
robotExito()Arpegio square de 4 notas triunfante
robotFallo()Dos beeps square descendentes
cableConectar()Sine 1200→600 Hz + ruido de contacto
cableError()Sawtooth + ruido highpass 3000 Hz (chispa eléctrica)
circuitoCompleto()Sine + triangle exponenciales (power-up del circuito)
robotFLLIniciar()Sawtooth continuo con zumbido de motor (arranque del robot FLL de orugas)
robotFLLDetener()Detiene el motor con fade-out suave (parada del robot FLL)
robotFLLClick()Click mecánico corto (sin uso actualmente)

Ambiente y UI (4)

MétodoDescripción
dialogo()Square "bleep" rápido 380±40 Hz (carácter de texto estilo Undertale)
fotoCaptura()Ruido 0.08s + square 1200→300 Hz (obturador + click mecánico)
selfieCaptura()Ruido 0.06s + sine 400→1200 Hz ascendente (obturador + flash)
lluviaAmbiente()Loop continuo: ruido filtrado + gotas periódicas cada 150-350ms

Música de Fondo (js/motor/musica.js)

La clase SistemaMusica gestiona la música de fondo del juego usando archivos MP3. A diferencia de los efectos de sonido (procedurales), la música usa 24 archivos de audio reales organizados en 12 grupos temáticos (2 pistas por grupo).

Grupos Musicales (12 grupos, 24 MP3)

GrupoEscena(s)Descripción
menumenuPrincipalMúsica del menú principal
cuevascuevasPomierAmbiente de cuevas
tainoMundos taínos (asentamientos)Música indígena
colonialMundos colonialesÉpoca colonial
acuaticoMundo AcuáticoAmbiente submarino
aeropuertoMundo JurídicoAeropuerto de Punta Cana
museoMundo LaboratorioInterior de museo
lfsdLFSDClase de robótica
mapamapaPrincipalMapa del mundo
creditosfinalCinematicaCinemática final y créditos

Crossfade A-B

Cada grupo tiene 2 pistas (A y B). Cuando una pista termina, el sistema hace un crossfade de 2 segundos hacia la otra pista, creando un bucle continuo sin silencios (A → B → A → B...). Ambas pistas usan HTMLAudioElement y el volumen se interpola linealmente durante la transición.

Override de Combate y Batú

Cuando se inicia un combate o una partida de batú, la música de fondo se pausa temporalmente. Al terminar el overlay, la música del escenario se restaura automáticamente.

Control de Volumen

El volumen de la música (0-100%) se configura desde el menú de Opciones. La preferencia se guarda en localStorage con la clave arclycee_volumen_musica y se restaura al iniciar el juego.

Autoplay del Navegador

Los navegadores bloquean la reproducción automática de audio hasta la primera interacción del usuario. Si el intento inicial de reproducción falla, el sistema reintenta automáticamente cuando se detecta la primera pulsación de tecla en el menú principal.

⚔️ Sistema de Combate

Archivo: js/mecanicas/combate.js (932 líneas) — combate por turnos estilo Undertale con ruta pacifista.

Concepto

El combate en ArcLycée no es de daño mutuo tradicional. El objetivo es convencer al enemigo a través de acciones diplomáticas. Hay dos medidores emocionales:

Flujo de Turnos

  1. Turno del jugador: elige entre opciones (←→ para navegar, E para confirmar)
  2. Feedback: mensaje dorado con el resultado de la acción (1.8-2.5s)
  3. Turno del enemigo: el enemigo responde según la última acción del jugador
  4. Repetir hasta que paciencia = 100 (victoria pacífica) o vida del enemigo = 0 (victoria por fuerza)

Opciones Personalizadas por Enemigo

Cada enemigo puede tener opcionesPersonalizadas — un array de acciones únicas con rangos de paciencia/hostilidad aleatorios y contra-respuestas específicas:

EnemigoOpcionesEtiqueta
Constructor MéndezRedes sociales, Protestas, Denuncia, Vía legal"Convencido:"
Pez LeónAtrapar, Pescar, Proteger coral, Alertar buzos"Controlado:"
Rodrigo TorresLey 318, Evidencia forense, INTERPOL, UNESCO 1970"Evidencia:"

Sprites de Enemigos

4 tipos de sprite dibujados proceduralmente: _dibujarSoldado(), _dibujarConstructor(), _dibujarPezLeon(), _dibujarTraficante(). Cada uno con diseño único y animación.

Sub-menú de Compañeros al Atacar

Cuando el jugador elige atacar y tiene compañeros activos, aparece un sub-menú que pregunta "¿Con quién atacas?" con las opciones: Solo / Magnoboot +3 / Viralata +2 / Cemí +4. Se navega con flechas (↑↓), E para confirmar y Q para cancelar y volver al menú principal de combate.

Panel de Ayuda (tecla H)

Un overlay togglable con la tecla H que explica los objetivos del combate, las diferencias entre la ruta pacifista y la agresiva, el significado de los medidores (paciencia, hostilidad, vida) y las recompensas de reputación según el tipo de victoria.

Indicador de Estado

Texto dinámico que muestra quién va ganando en el combate. Cambia según los valores de paciencia y hostilidad: "Casi convencido", "Muy hostil", "Progresando", "Situación tensa".

[E] Continuar

Tanto los mensajes de acción del jugador como los contra-ataques del enemigo se muestran durante 2.5 segundos. Al terminar, aparece un botón pulsante [E] Continuar que el jugador debe presionar para avanzar al siguiente turno.

Barra de Vida Oculta

Para los enemigos que tienen opcionesPersonalizadas, la barra de vida del enemigo se oculta. Estos enemigos se vencen por convencimiento (paciencia = 100), no por daño de vida.

Integración con Reputación

Victoria pacífica = +15 reputación. Victoria por fuerza = +5. Además, la reputación actual del jugador da una ventaja inicial: pacienciaInicial = min(reputacion / 2, 25).

💬 Sistema de Diálogo

Archivo: js/mecanicas/dialogo.js (326 líneas) — motor de diálogos con efecto máquina de escribir.

Efecto Máquina de Escribir

Los caracteres aparecen uno por uno. Un contador _caracteresVisibles se incrementa cada frame, y el texto mostrado es texto.substring(0, Math.floor(caracteres)). Cada carácter dispara un "bleep" de diálogo (sonido procedural).

Opciones de Respuesta

Las líneas de diálogo pueden incluir opciones: [{texto, valor}]. Cuando hay opciones, el avance se bloquea hasta que el jugador selecciona una (↑↓ para navegar, E para confirmar).

Callback de Encadenamiento

alTerminar permite encadenar eventos al finalizar un diálogo: iniciar combate, dar un objeto, descubrir una misión, etc.

Diálogo Rotativo

NPCs mentores (Roberto Cassá, Lcda. Carmen Vidal) usan un contador que incrementa tras cada conversación. El índice se calcula como contador % array.length para ciclar infinitamente por los temas.

🤝 Sistema de Compañeros

Directorio: js/personajes/companeros/ — tres compañeros que siguen al jugador entre escenas.

Patrón Común

Todos comparten: propiedad tipo (string), flag activo, métodos activar()/desactivar(), y movimiento por interpolación lineal (lerp) que crea un seguimiento suave en vez de teletransporte.

Los Tres Compañeros

NombreTipoHabilidadFactor LerpPosición Relativa
MagnobootRobot excavadorDetector de metal (tecla F), excavación (cuesta 10 energía)0.05Derecha del jugador (+30, +5)
ViralataPerro rastreadorOlfato (radio 50 px), cola que se menea al detectar objetos0.06Izquierda del jugador (-25, +10)
Cemí MurciélagoEspíritu de cueva6 niveles de poder desbloqueados con botijas (eco-localización → visión de cemí)0.08Arriba-izquierda (-20, -25) con flotación sinusoidal

Persistencia

Los compañeros persisten entre escenas vía juego.companeros[]. En el guardado se serializan como {tipo, activo} y se reconstruyen con _crearCompaneroBasico(tipo, activo).

🎒 Sistema de Inventario

Archivo: js/mecanicas/inventario.js (742 líneas) — mochila con grilla visual de 20 slots.

Diseño

Grilla de 5 columnas × 4 filas. Cada slot mide 52×52 px con margen de 4 px. El panel se centra en el canvas. Navegación con flechas, uso de objeto con E (si esUsable).

Objetos Curativos

ObjetoCuraciónFuente
Guanábana+30 HPGuarionex (Asentamiento II)
Vasija curativa+35 HPAnacaona (Asentamiento I)
Casabe+25 HPVarios

Iconos Procedurales

Más de 24 iconos únicos dibujados con Canvas 2D: linterna con rayos de luz, cemí dorado con ojos, brújula con agujas roja/blanca, arcabuz colonial, moneda con corona, etc. _dibujarIconoObjeto(ctx, id, x, y, tam) despacha al icono correcto por ID.

📸 Álbum de Fotos

Archivo: js/mecanicas/album-fotos.js (1,140 líneas) — sistema de captura con renderizado dedicado por tipo de entidad.

Tipos de Captura

Renderizado por Tipo de Entidad

El campo tipoEntidad determina qué renderizador usar. Las escenas exponen un array fotografiables[] para entidades adicionales más allá de NPCs:

tipoEntidadRenderizadorDetalle
'npc'_renderizarNPCCentrado()Dibuja el NPC en canvas auxiliar 200×200, detecta bounding box por píxeles, recorta y centra
'tortuga'_renderizarTortuga()4 sprites dedicados: carey (oliva con franjas ámbar), tinglar (azul-negro con 7 crestas), caguama (marrón-rojo con marca de corazón), verde (oliva con manchas)
'coral'_renderizarCoral()4 sprites: cerebro (hemisferio con surcos meándricos), cuerno (5 ramas bifurcadas), abanico (abanico púrpura-rosa con venas radiales), mesa (plataforma elíptica sobre tronco)
'petroglifo'_renderizarPetroglifo()4 tipos: sol, murciélago, cara, espiral
'objeto'_renderizarObjeto()Coleccionable genérico con resplandor

Rango de Detección

La detección de objetivo fotográfico usa distancia euclidiana con un radio de 80 px (RANGO_FOTO). Se busca primero en NPCs, luego en objetos, luego en fotografiables[].

🗺️ Mapa de Tiles (Hispaniola)

Archivo: js/mundos/mapa-tiles.js (784 líneas) — generación procedural del terreno de la isla.

El Bitmap

La costa de La Hispaniola se define con ISLA_BITMAP: un array de 68 strings de 128 caracteres cada uno. Cada carácter es '1' (tierra) o '0' (agua). Trazado manualmente desde una imagen de referencia de la NASA, escalado 2× desde un bitmap original de 64×34.

Pipeline de Generación de Terreno

  1. Inicializar todos los tiles como AGUA_PROFUNDA
  2. Marcar tiles de tierra desde ISLA_BITMAP
  3. Agregar agua superficial adyacente a la costa (borde de 1 tile)
  4. Pintar playas (tierra adyacente al agua)
  5. Aplicar cordilleras usando distancia punto-a-segmento (8 cadenas montañosas)
  6. Agregar lagos interiores (Enriquillo, Saumâtre) por colisión de elipse
  7. Generar bosques con hash pseudo-aleatorio (40% de probabilidad por tile)
  8. Dibujar 5 ríos con algoritmo de Bresenham (Yaque del Norte/Sur, Yuna, Ozama, Artibonite)

8 Tipos de Terreno

TipoCaminableDecoración
Agua profundaNoCurvas de olas animadas
Agua superficialEspuma
ArenaTextura de granos
Pradera
BosqueCopas de árboles (círculos)
MontañaNoTriángulos con cimas rocosas
RíoLíneas de corriente animadas
Camino

Algoritmo de Bresenham para Ríos

Los ríos se trazan conectando waypoints con el algoritmo de línea de Bresenham. Solo se pintan tiles que ya son tierra (se saltan tiles de agua), creando ríos que fluyen naturalmente a lo largo del terreno.

Cordilleras por Distancia

8 cadenas montañosas definidas como secuencias de waypoints. Para cada tile de tierra, se calcula la distancia mínima a cada segmento de cordillera. Si la distancia es menor a 3.6 tiles, se marca como montaña (no caminable).

Renderizado con Culling

dibujarTilesVisibles() solo renderiza tiles dentro del viewport de la cámara, calculando columna/fila de inicio y fin. Esto es crucial para rendimiento dado que el mapa total es de 128×68 = 8,704 tiles.

🧭 Mapa del Mundo

Archivo: js/mundos/mapa-principal.js (809 líneas) — navegación entre nodos de nivel.

Sistema de Cámara

Nodos de Nivel

13 nodos ubicados en coordenadas de tile verificadas como caminables. Cada nodo tiene: ID, posición, tipo (cueva, aldea, ciudad, naufragio, etc.), escena destino, y nodo previo en la cadena de progresión.

Visualización: círculo (18-22 px) + borde + icono emoji + nombre. Los colores indican estado: gris (bloqueado), oro (activo), verde (completado). Un indicador [E] dorado pulsa cuando el jugador está cerca.

Mini-Mapa

Esquina superior derecha (120×75 px). Muestra tiles simplificados (paso de 2 para rendimiento), nodos coloreados por estado, posición del jugador y viewport de la cámara.

Colisión

_puedeMoverse() prueba las 4 esquinas del sprite (con margen de 4 px) contra esCaminable() del mapa de tiles.

🌊 Mecánicas Acuáticas

Archivos: js/mundos/acuatico/mundo-acuatico.js (2,174 líneas) + santuario-manati.js (2,905 líneas).

Movimiento Submarino

CondiciónFactor de Velocidad
Normal (bajo el agua)× 0.7
Con efecto de medusa× 0.4
Con efecto de tiburón× 0.4 (2s de duración)

Medusas Pasivas

4 medusas con movimiento sinusoidal entre dos waypoints: t = (sin(fase + dt*0.8) + 1) / 2 → lerp entre puntoA y puntoB. Contacto (radio 15-20 px) = 5 HP de daño + 2.5s de lentitud + 2s de invulnerabilidad.

Vida Marina

Sistema de Oxígeno (Santuario del Manatí)

El jugador nada a pulmón con snorkel. Barra de O₂ que va de 100 a 0:

Tiburones

3 tiburones patrullan entre waypoints con desfase de fase (0, π/2, π). Mordida: -8 HP + 2s de lentitud + efecto de sacudida (0.5s) + sonido mordidaTiburon().

Lanchas Rápidas

Spawn cada 5-15s en la zona de superficie. Colisión: -10 HP + sacudida (0.6s) + grito Wilhelm sintético (lanchaImpacto()). La zona de hélices causa daño periódico (-2 HP con cooldown). El toast de advertencia se muestra una sola vez por entrada a la zona.

Acciones Ecológicas

⛈️ Sistema de Clima

Archivos: js/clima/clima.js (442 líneas) + huracan.js (324 líneas).

6 Estados Climáticos

EstadoProbabilidadIntensidadEfecto en Jugador
Soleado45%0.1Ninguno
Nublado25%0.3Ninguno
Lluvia20%0.6Velocidad × (1 − 0.15×i)
Tormenta7%0.8Velocidad × (1 − 0.25×i) + truenos
Huracán2%1.0Empuje de viento 2px×i + velocidad × (1 − 0.35×i)
Terremoto1%2% por frame de tropiezo aleatorio

Partículas

Hasta 500 partículas recicladas. Tasa de spawn: 3-8 partículas/frame según intensidad. Cada partícula tiene gravedad + empuje de viento.

Huracanes (Avanzado)

Categorías 1-5 con velocidad de viento = categoría × 2 px/frame. Tres fases: inicio (0-25%, intensidad sube) → pico (25-75%, intensidad máxima) → final (75-100%, intensidad baja). Debris físico hasta 500 objetos con rebote en terreno. Dirección: típicamente del este (realista para el Caribe).

Sonido Ambiental

Lluvia continua (lluviaAmbiente()) con intensidad dinámica durante transiciones. Truenos aleatorios cada 4-15s durante tormentas.

🎲 Mini-Juegos

Mundo LFSD — Clase de Robótica

Archivo: js/mundos/lfsd/mundo-lfsd.js — nivel interior de la clase de robótica del Liceo Francés de Santo Domingo.

El Prof. Nicolas Droulers (pelo blanco y barba blanca) guía al jugador. El aula contiene mesas de trabajo con pantallas de programación Scratch, una pizarra, una impresora 3D con etiqueta traducida según el idioma activo, y una mesa de FIRST LEGO League con caminos negros de seguimiento de línea donde un robot LEGO animado con orugas de cadena recorre las pistas con sonido de motor (robotFLLIniciar/robotFLLDetener). 9 NPCs estudiantes (3 quest-givers con camisetas de color) y 4 estaciones de trabajo.


Batú — Juego de Pelota Taíno

Archivo: js/mecanicas/batu.js (935 líneas) — física 2D con IA adaptativa.

Física

ParámetroValor
Gravedad480 px/s²
Coeficiente de rebote (suelo)0.8
Coeficiente de rebote (paredes)0.9
Fricción del aire0.998 por frame
Radio de la pelota8 px
Condición de victoriaPrimero en 3 puntos

Tipos de Golpe (por altura de contacto)

ZonaParte del CuerpoVelocidadÁngulo
< 25%Cabeza290 px/s-70° (casi vertical)
25-50%Hombro425 px/s-60°
50-75%Cadera/yugo500 px/s (máximo)-45° (el más fuerte)
> 75%Rodilla280 px/s-30° (defensivo)

Cada golpe tiene ±25% de variación de velocidad y ±8° de variación angular para impredecibilidad.

IA del Oponente

Datos Educativos

Entre cada punto se muestra un dato histórico sobre el batú: material de la pelota (látex de cupey), petroglifos en bateys, el yugo ceremonial, festivales de areíto, resolución pacífica de conflictos, y sitios arqueológicos reales (Chacuey, La Aleta).

Fases

intro → saque → jugando → punto → educativo → saque → ... → finJuego


Calibración de Señal ("Good Vibrations")

Archivo: js/mecanicas/calibracion-senal.js (330 líneas) — puzzle de osciloscopio.

Mecánica

3 diales (frecuencia, amplitud, fase) que controlan una onda sinusoidal mostrada en un osciloscopio (verde sobre negro). El jugador debe igualar la onda objetivo dentro de una tolerancia del ±15%.

Fórmula de la Onda

y = centroY + sin(t + fase) × amplitud
donde t = (x − 40) / (ancho − 80) × π × 2 × frecuencia

Controles

↑↓ para cambiar entre diales, ←→ para ajustar (±2), E para confirmar. Límite: 45 segundos.

Recompensa: magnetómetro calibrado (progreso.magnetometroCalibrado = true), +15 reputación.


Programación de Robot ("Full Metal Archeologist")

Archivo: js/mecanicas/programacion-bloques.js (491 líneas) — navegación por bloques.

Mecánica

Ordenar 5-6 bloques de instrucción (avanzar, girar izquierda/derecha, escanear, sumergir, ascender) para guiar un robot submarino a través de una cuadrícula de 6×4. Robot empieza en (0,1) mirando a la derecha, objetivo en (5,2).

Ejecución

Máximo 12 bloques. Intervalo de ejecución: 0.6s entre pasos. Fallo: robot sale de la cuadrícula u obstáculo (si no está sumergido). Fase de programación: 60s. Ejecución: automática sin límite.

Recompensa: capa de sitios arqueológicos en mapa Leaflet (progreso.robotProgramado = true), +15 reputación.


Conexión de Cables ("Weird Science")

Archivo: js/mecanicas/conexion-cables.js (408 líneas) — matching puzzle en placa de circuito.

Mecánica

Conectar 6 entradas (izquierda) con 6 salidas (derecha) por color y símbolo. Salidas en orden aleatorio. Las conexiones se dibujan como curvas de Bézier cúbicas. Conexión incorrecta = animación de chispa.

Controles

↑↓ para seleccionar entrada/salida, E para conectar. Límite: 30 segundos.

Recompensa: +15 reputación (bonus +5 si se completa en <15s), artículo de periódico en inventario.

Reputación y Misiones Secundarias

Sistema de Reputación

Archivo: js/mecanicas/reputacion.js (74 líneas) — puntuación 0-100 con 4 niveles.

RangoNivelColor de Barra
0-24DesconocidoRojo
25-49ConocidoAmarillo
50-74RespetadoVerde
75-100LegendarioDorado

La reputación afecta el combate (ventaja inicial de paciencia) y se muestra en el epílogo final. Se gana con victorias pacíficas (+15), victorias por fuerza (+5), mini-juegos (+15 cada uno) y acciones ecológicas (+10).

5 Misiones Secundarias

Archivo: js/mecanicas/misiones-secundarias.js (89 líneas) — máquina de estados por misión.

MisiónDescripciónDetonador
BatúJugar pelota taína con HigüemotaHablar con Higüemota en Asentamiento II
Rescate del ManatíLiberar manatí + limpiar arrecifeHablar con Dra. Sofía en Santuario
Good VibrationsCalibrar el magnetómetroHablar con NPC en La Isabela
Full Metal ArcheologistProgramar robot submarinoHablar con bióloga en Santuario
Weird ScienceReparar equipo de análisisHablar con mentor en Laboratorio

Cada misión progresa: no_descubierta → descubierta → en_progreso → completada. El Registro de Juego (tecla L) muestra el estado de todas las misiones en dos pestañas: Historia Principal (13 nodos con ✅/🔓/🔒) y Secundarias (con checkmarks).

💾 Sistema de Guardado

Archivo: js/motor/guardado.js (243 líneas) — persistencia en localStorage.

Auto-guardado

La partida se guarda automáticamente al volver al mapa del mundo. "Continuar Juego" en el menú restaura todo el estado y lleva al mapa.

Datos Serializados

Reconstrucción de Compañeros

Los compañeros se guardan como {tipo: 'magnoboot', activo: true} y se reconstruyen como instancias reales vía _crearCompaneroBasico(tipo, activo) usando los imports de Magnoboot, Viralata y CemiMurcielago.

🌐 Mapa de Referencia (LeafletJS)

Directorio: js/mapas/ (4 archivos, ~700 líneas) — mapa geográfico real con sitios arqueológicos.

Configuración

Centro: 19.0°N, -70.5°W (República Dominicana). Zoom por defecto: 8. Se abre con tecla R desde cualquier escena jugable (no solo el mapa del mundo). Tiles de Stadia Maps (6 capas estéticas: Watercolor, Terrain, Toner, Dark, Smooth, OSM Bright) con fallback a CARTO Voyager.

Capas de Datos (76 sitios reales)

CapaIconoCantidad
Sitios Taínos🗿16 (asentamientos, cuevas, petroglifos en RD y Haití)
Sitios Coloniales🏰8 (La Isabela, Zona Colonial, Cap-Haïtien, etc.)
Naufragios12 (Santa María 1492, San Miguel 1551, Concepción 1641, etc.)
Museos🏛30 (RD + Haití)
Sitios Inexplorados🔍8 (solo visible tras completar mini-juego del robot)

Interacción

Marcadores con popup (nombre + descripción) al hacer click. Click-to-travel: window._viajarANodo(id) para volar animadamente al nodo y cambiar de escena. Estado visual: completado (borde verde + brillo), bloqueado (escala de grises + opacidad 0.4), activo (borde blanco).

🎬 Múltiples Finales

Archivo: js/escenas/final-cinematica.js (673 líneas) — 5 finales + créditos cinematográficos.

Tipos de Final

FinalCondiciónTítulo
Completo8 nodos + 7 sidequests + más pacificados que violentos"Leyenda de Quisqueya"
Pacifista8 nodos + más pacificados, sin violentos"Guardián del patrimonio"
Ecológico3+ acciones ecológicas + sin violencia"Protector del ecosistema marino"
OscuroMás combates violentos que pacíficos"El patrimonio merece otro camino"
MuseoFallback por defecto"Curador de la historia"

Secuencia

5 pasos de texto con auto-avance (5-6s cada uno), skip con E. Fondos temáticos por final: gradientes dorados (completo/pacifista), azul marino con burbujas (ecológico), rojo con fracturas (oscuro), cálido con foco de luz (museo). El nivel de reputación se muestra en el epílogo.

Créditos

Scroll vertical automático a 40 px/s. Lista los 8 creadores (les fous du robot), el Liceo Francés de Santo Domingo y el año 2026. E para acelerar/saltar.

🌍 Internacionalización

Directorio: js/idiomas/ (4 archivos, ~4,400 líneas).

Estructura

3 archivos de traducción (es.js, en.js, fr.js) con ~1,100+ claves cada uno, organizados como objetos JavaScript anidados. Un gestor idiomas.js (73 líneas) provee:

Secciones Principales

SecciónContenido
menuTítulos, opciones del menú principal
seleccionSelección de personaje
interfazHUD (vida, oxígeno, inventario), frases de muerte (10 variantes)
dialogosTodos los diálogos por mundo (cueva, aldea, isabela, colonial, acuático, jurídico, laboratorio, LFSD, finales)
combateAcciones y mensajes de combate
batu25 claves del mini-juego (reglas, datos, mensajes)
objetos32+ objetos con nombre y descripción
inventarioUI del inventario
climaNombres de estados climáticos
guardadoStrings del sistema de guardado

Acceso en el Código

// En escenas y mecánicas:
const textos = juego.idiomas.traducciones[juego.idiomas.idiomaActual];
const nombre = textos.menu.titulo;

// O usando el helper con dot-notation:
const texto = juego.idiomas.t('dialogos.cueva.petroSol');

🧑 Jugador (Pepito/Pepita)

Archivo: js/personajes/pepito.js (258 líneas).

Dos Modos de Movimiento

Propiedades Principales

PropiedadValor
Dimensiones28×32 px (más estrecho que el tile de 32 px para facilitar el paso)
Vida máxima100 HP
Género'pepito' (masculino) o 'pepita' (femenino)

Animación de Marcha

Las piernas se animan con Math.sin(cuadroAnimacion * 5) * 3 — un movimiento sinusoidal que simula pasos. Los brazos con Math.sin(cuadroAnimacion * 2) * 1. La animación solo avanza cuando esAnimando = true.

Selección de Personaje

js/escenas/seleccion-personaje.js (291 líneas): el sprite del juego se muestra escalado ×2.5 con animación de marcha en vivo. Pepito es azul (#4488ff) con pelo puntiagudo; Pepita es morada (#aa44ff) con trenzas.

⚙️ Configuración y Constantes

Archivo: js/motor/configuracion.js (95 líneas) — punto central de configuración.

ConstanteValorUso
ANCHO_JUEGO960Ancho del canvas en píxeles
ALTO_JUEGO540Alto del canvas (16:9)
TAMANO_TILE32Tamaño estándar de tile
GRAVEDAD0.6Aceleración vertical en plataforma
VELOCIDAD_JUGADOR3Píxeles por frame
FPS_OBJETIVO60Frames por segundo
ESCALA_MINIMA0.5Escala mínima del canvas
ESCALA_MAXIMA3.0Escala máxima del canvas
CLAVE_GUARDADO'arclycee_guardado'Key de localStorage

Dimensiones de Sprites

TipoAncho × Alto
Jugador32 × 32
Compañero24 × 24
Cemí16 × 16
NPC32 × 32
Boss64 × 64
Boss Grande96 × 96
Item16 × 16
Petroglifo48 × 48

📁 Archivos Principales por Tamaño

#ArchivoLíneasResponsabilidad
1santuario-manati.js2,905Santuario: oxígeno, tiburones, lanchas, ecología
2mundo-acuatico.js2,174Naufragio: medusas, tortugas, ballenas, combate
3sonido-procedural.js1,93160 efectos de audio generados por código
4zona-colonial.js1,923Zona Colonial: NPCs, combate, activismo
5juego.js1,659Game loop, escenas, overlays, toasts
6es.js1,523Traducciones en español
7fr.js1,472Traducciones en francés
8en.js1,408Traducciones en inglés
9cuevas-pomier.js1,340Cueva plataforma con linterna
10mundo-juridico.js1,335Aeropuerto: combate legal, INTERPOL