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étrica | Valor |
|---|---|
| Archivos JavaScript | 53 |
| Líneas de código totales | ~37,500 |
| Resolución del canvas | 960 × 540 px (16:9) |
| FPS objetivo | 60 |
| Idiomas soportados | 3 (Español, Français, English) |
| Claves de traducción | ~1,100+ |
| Efectos de sonido procedurales | 60 métodos |
| Mundos jugables | 12 + mapa del mundo |
| Mini-juegos | 4 (batú, calibración, programación, cables) |
| Directorio | Contenido | Archivos |
|---|---|---|
js/motor/ | Game loop, input, renderizado, sonido, guardado, configuración | 6 |
js/escenas/ | Menú principal, selección de personaje, intro, final cinematográfico | 4 |
js/mundos/ | 13 niveles jugables: cuevas, asentamientos, montaña, coloniales, acuáticos, jurídico, laboratorio, LFSD, lago, cenote, museo | 13 |
js/mecanicas/ | Combate, diálogo, inventario, álbum de fotos, 4 mini-juegos, misiones, reputación, registro | 11 |
js/personajes/ | Jugador (Pepito/Pepita) + 3 compañeros (Magnoboot, Viralata, Cemí) | 4 |
js/idiomas/ | Traducciones ES/FR/EN + gestor de idiomas | 4 |
js/mapas/ | Mapa Leaflet con capas de datos reales (taínos, coloniales, naufragios, museos) | 4 |
js/clima/ | Sistema de clima + huracanes | 2 |
| Tecnología | Uso |
|---|---|
| HTML5 Canvas | Renderizado del juego — todo se dibuja por código, sin sprites externos |
| JavaScript ES Modules | Arquitectura modular sin frameworks ni bundlers |
| Web Audio API | 60 efectos de sonido procedurales + música de fondo con 24 MP3 |
| LeafletJS + Stadia Maps | Mapas interactivos con 76 sitios arqueológicos reales de RD y Haití |
| Kaplay.js | Touch input (joystick analógico y D-pad) |
| CSS3 | Pantalla de carga, controles táctiles responsivos |
velocidadJugador), funciones (estaPresionada), constantes (ANCHO_JUEGO)Archivo: js/motor/juego.js (1,659 líneas) — el orquestador principal del juego.
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)
El juego tiene 15 escenas registradas. Solo una escena está activa a la vez.
| Tipo | Escenas |
|---|---|
| Interfaz | Menú 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ón | Mapa del mundo (mapa de tiles) |
| Secreto | Selector de niveles (Código Konami) |
Cada escena implementa tres métodos:
iniciar(juego) — configuración inicial al entrar en la escenaactualizar(dt, entrada, jugador, companeros) — lógica por frame (movimiento, colisiones, IA)dibujar(renderizador, ancho, alto, textos, jugador, companeros) — renderizado visual
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.
Los overlays se dibujan encima de la escena activa, en este orden (de abajo hacia arriba):
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.
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.
Archivo: js/motor/entrada.js (494 líneas) — capa de abstracción unificada para teclado y táctil.
| Acción | Teclas | Uso |
|---|---|---|
| Mover | WASD / Flechas | Exploración y menús |
| Saltar | Espacio | Solo en cuevas (plataforma) |
| Interactuar | E / Enter | Hablar con NPCs, usar objetos |
| Inventario | I | Abrir/cerrar mochila |
| Especial | F | Detectar metal (Magnoboot) |
| Volver al mapa | M | Salir del nivel actual |
| Mapa real | R | Abrir mapa Leaflet de referencia |
| Registro | L | Game Log (misiones) |
| Foto | T | Tomar foto de entidad cercana |
| Selfie | G | Tomar selfie con entidad |
| Álbum | P | Ver galería de fotos |
| Zoom | + / - | Zoom en el mapa del mundo |
| Código Konami | ↑↑↓↓←→←→ | Selector secreto de niveles |
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.
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.
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.
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.
Archivo: js/motor/renderizado.js (199 líneas) — wrapper de Canvas 2D.
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.
| Método | Descripció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 |
aplicarFiltroCueva(jugadorX, jugadorY, radioLuz) crea un efecto de antorcha usando un
gradiente radial con centro transparente y bordes opacos:
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)
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.
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.
Los navegadores bloquean Web Audio hasta la primera interacción del usuario.
_asegurarContexto() crea el AudioContext solo cuando se necesita por primera vez.
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)
Controla el volumen a lo largo del tiempo: ataque rápido → sustain → decaimiento exponencial hacia silencio.
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).
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
| Método | Técnica | Descripción |
|---|---|---|
salto() | Square 150→400 Hz | Salto del jugador |
aterrizar() | Sine 100→40 Hz | Aterrizaje |
recoger() | Dos notas square (523, 659 Hz) | Recoger objeto |
paso() | Ruido blanco 40ms, lowpass 800 Hz | Paso en el suelo |
goteo() | Sine 800→240 Hz, randomizado | Gota de agua |
dano() | Sawtooth 200→50 Hz | Daño genérico |
muerte() | Tres notas sawtooth descendentes con LFO | "Wah wah waaah" cómico |
| Método | Descripció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) |
| Método | Descripció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) |
| Método | Descripció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 |
| Método | Descripció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) |
| Método | Descripció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 |
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).
| Grupo | Escena(s) | Descripción |
|---|---|---|
menu | menuPrincipal | Música del menú principal |
cuevas | cuevasPomier | Ambiente de cuevas |
taino | Mundos taínos (asentamientos) | Música indígena |
colonial | Mundos coloniales | Época colonial |
acuatico | Mundo Acuático | Ambiente submarino |
aeropuerto | Mundo Jurídico | Aeropuerto de Punta Cana |
museo | Mundo Laboratorio | Interior de museo |
lfsd | LFSD | Clase de robótica |
mapa | mapaPrincipal | Mapa del mundo |
creditos | finalCinematica | Cinemática final y créditos |
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.
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.
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.
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.
Archivo: js/mecanicas/combate.js (932 líneas) — combate por turnos estilo Undertale con ruta pacifista.
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:
Cada enemigo puede tener opcionesPersonalizadas — un array de acciones únicas con rangos de
paciencia/hostilidad aleatorios y contra-respuestas específicas:
| Enemigo | Opciones | Etiqueta |
|---|---|---|
| Constructor Méndez | Redes sociales, Protestas, Denuncia, Vía legal | "Convencido:" |
| Pez León | Atrapar, Pescar, Proteger coral, Alertar buzos | "Controlado:" |
| Rodrigo Torres | Ley 318, Evidencia forense, INTERPOL, UNESCO 1970 | "Evidencia:" |
4 tipos de sprite dibujados proceduralmente: _dibujarSoldado(), _dibujarConstructor(),
_dibujarPezLeon(), _dibujarTraficante(). Cada uno con diseño único y animación.
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.
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.
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".
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.
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.
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).
Archivo: js/mecanicas/dialogo.js (326 líneas) — motor de diálogos con 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).
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).
alTerminar permite encadenar eventos al finalizar un diálogo: iniciar combate,
dar un objeto, descubrir una misión, etc.
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.
Directorio: js/personajes/companeros/ — tres compañeros que siguen al jugador entre escenas.
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.
| Nombre | Tipo | Habilidad | Factor Lerp | Posición Relativa |
|---|---|---|---|---|
| Magnoboot | Robot excavador | Detector de metal (tecla F), excavación (cuesta 10 energía) | 0.05 | Derecha del jugador (+30, +5) |
| Viralata | Perro rastreador | Olfato (radio 50 px), cola que se menea al detectar objetos | 0.06 | Izquierda del jugador (-25, +10) |
| Cemí Murciélago | Espíritu de cueva | 6 niveles de poder desbloqueados con botijas (eco-localización → visión de cemí) | 0.08 | Arriba-izquierda (-20, -25) con flotación sinusoidal |
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).
Archivo: js/mecanicas/inventario.js (742 líneas) — mochila con grilla visual de 20 slots.
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).
| Objeto | Curación | Fuente |
|---|---|---|
| Guanábana | +30 HP | Guarionex (Asentamiento II) |
| Vasija curativa | +35 HP | Anacaona (Asentamiento I) |
| Casabe | +25 HP | Varios |
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.
Archivo: js/mecanicas/album-fotos.js (1,140 líneas) — sistema de captura con renderizado dedicado 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:
| tipoEntidad | Renderizador | Detalle |
|---|---|---|
'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 |
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[].
Archivo: js/mundos/mapa-tiles.js (784 líneas) — generación procedural del terreno de la isla.
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.
AGUA_PROFUNDAISLA_BITMAP| Tipo | Caminable | Decoración |
|---|---|---|
| Agua profunda | No | Curvas de olas animadas |
| Agua superficial | Sí | Espuma |
| Arena | Sí | Textura de granos |
| Pradera | Sí | — |
| Bosque | Sí | Copas de árboles (círculos) |
| Montaña | No | Triángulos con cimas rocosas |
| Río | Sí | Líneas de corriente animadas |
| Camino | Sí | — |
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.
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).
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.
Archivo: js/mundos/mapa-principal.js (809 líneas) — navegación entre 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.
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.
_puedeMoverse() prueba las 4 esquinas del sprite (con margen de 4 px)
contra esCaminable() del mapa de tiles.
Archivos: js/mundos/acuatico/mundo-acuatico.js (2,174 líneas)
+ santuario-manati.js (2,905 líneas).
| Condición | Factor 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) |
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.
Math.sinEl jugador nada a pulmón con snorkel. Barra de O₂ que va de 100 a 0:
3 tiburones patrullan entre waypoints con desfase de fase (0, π/2, π).
Mordida: -8 HP + 2s de lentitud + efecto de sacudida (0.5s) + sonido mordidaTiburon().
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.
Archivos: js/clima/clima.js (442 líneas) + huracan.js (324 líneas).
| Estado | Probabilidad | Intensidad | Efecto en Jugador |
|---|---|---|---|
| Soleado | 45% | 0.1 | Ninguno |
| Nublado | 25% | 0.3 | Ninguno |
| Lluvia | 20% | 0.6 | Velocidad × (1 − 0.15×i) |
| Tormenta | 7% | 0.8 | Velocidad × (1 − 0.25×i) + truenos |
| Huracán | 2% | 1.0 | Empuje de viento 2px×i + velocidad × (1 − 0.35×i) |
| Terremoto | 1% | — | 2% por frame de tropiezo aleatorio |
Hasta 500 partículas recicladas. Tasa de spawn: 3-8 partículas/frame según intensidad. Cada partícula tiene gravedad + empuje de viento.
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).
Lluvia continua (lluviaAmbiente()) con intensidad dinámica durante transiciones.
Truenos aleatorios cada 4-15s durante tormentas.
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.
Archivo: js/mecanicas/batu.js (935 líneas) — física 2D con IA adaptativa.
| Parámetro | Valor |
|---|---|
| Gravedad | 480 px/s² |
| Coeficiente de rebote (suelo) | 0.8 |
| Coeficiente de rebote (paredes) | 0.9 |
| Fricción del aire | 0.998 por frame |
| Radio de la pelota | 8 px |
| Condición de victoria | Primero en 3 puntos |
| Zona | Parte del Cuerpo | Velocidad | Ángulo |
|---|---|---|---|
| < 25% | Cabeza | 290 px/s | -70° (casi vertical) |
| 25-50% | Hombro | 425 px/s | -60° |
| 50-75% | Cadera/yugo | 500 px/s (máximo) | -45° (el más fuerte) |
| > 75% | Rodilla | 280 px/s | -30° (defensivo) |
Cada golpe tiene ±25% de variación de velocidad y ±8° de variación angular para impredecibilidad.
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).
intro → saque → jugando → punto → educativo → saque → ... → finJuego
Archivo: js/mecanicas/calibracion-senal.js (330 líneas) — puzzle de osciloscopio.
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%.
y = centroY + sin(t + fase) × amplitud donde t = (x − 40) / (ancho − 80) × π × 2 × frecuencia
↑↓ para cambiar entre diales, ←→ para ajustar (±2), E para confirmar. Límite: 45 segundos.
Recompensa: magnetómetro calibrado (progreso.magnetometroCalibrado = true), +15 reputación.
Archivo: js/mecanicas/programacion-bloques.js (491 líneas) — navegación por bloques.
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).
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.
Archivo: js/mecanicas/conexion-cables.js (408 líneas) — matching puzzle en placa de circuito.
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.
↑↓ 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.
Archivo: js/mecanicas/reputacion.js (74 líneas) — puntuación 0-100 con 4 niveles.
| Rango | Nivel | Color de Barra |
|---|---|---|
| 0-24 | Desconocido | Rojo |
| 25-49 | Conocido | Amarillo |
| 50-74 | Respetado | Verde |
| 75-100 | Legendario | Dorado |
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).
Archivo: js/mecanicas/misiones-secundarias.js (89 líneas) — máquina de estados por misión.
| Misión | Descripción | Detonador |
|---|---|---|
| Batú | Jugar pelota taína con Higüemota | Hablar con Higüemota en Asentamiento II |
| Rescate del Manatí | Liberar manatí + limpiar arrecife | Hablar con Dra. Sofía en Santuario |
| Good Vibrations | Calibrar el magnetómetro | Hablar con NPC en La Isabela |
| Full Metal Archeologist | Programar robot submarino | Hablar con bióloga en Santuario |
| Weird Science | Reparar equipo de análisis | Hablar 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).
Archivo: js/motor/guardado.js (243 líneas) — persistencia en localStorage.
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.
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.
Directorio: js/mapas/ (4 archivos, ~700 líneas) — mapa geográfico real con sitios arqueológicos.
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.
| Capa | Icono | Cantidad |
|---|---|---|
| Sitios Taínos | 🗿 | 16 (asentamientos, cuevas, petroglifos en RD y Haití) |
| Sitios Coloniales | 🏰 | 8 (La Isabela, Zona Colonial, Cap-Haïtien, etc.) |
| Naufragios | ⚓ | 12 (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) |
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).
Archivo: js/escenas/final-cinematica.js (673 líneas) — 5 finales + créditos cinematográficos.
| Final | Condición | Título |
|---|---|---|
| Completo | 8 nodos + 7 sidequests + más pacificados que violentos | "Leyenda de Quisqueya" |
| Pacifista | 8 nodos + más pacificados, sin violentos | "Guardián del patrimonio" |
| Ecológico | 3+ acciones ecológicas + sin violencia | "Protector del ecosistema marino" |
| Oscuro | Más combates violentos que pacíficos | "El patrimonio merece otro camino" |
| Museo | Fallback por defecto | "Curador de la historia" |
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.
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.
Directorio: js/idiomas/ (4 archivos, ~4,400 líneas).
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:
cambiarIdioma(codigo) — cambia entre 'es', 'fr', 'en't(clave) — acceso con notación de puntos: t('dialogos.cueva.petroSol')| Sección | Contenido |
|---|---|
menu | Títulos, opciones del menú principal |
seleccion | Selección de personaje |
interfaz | HUD (vida, oxígeno, inventario), frases de muerte (10 variantes) |
dialogos | Todos los diálogos por mundo (cueva, aldea, isabela, colonial, acuático, jurídico, laboratorio, LFSD, finales) |
combate | Acciones y mensajes de combate |
batu | 25 claves del mini-juego (reglas, datos, mensajes) |
objetos | 32+ objetos con nombre y descripción |
inventario | UI del inventario |
clima | Nombres de estados climáticos |
guardado | Strings del sistema de guardado |
// 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');
Archivo: js/personajes/pepito.js (258 líneas).
| Propiedad | Valor |
|---|---|
| Dimensiones | 28×32 px (más estrecho que el tile de 32 px para facilitar el paso) |
| Vida máxima | 100 HP |
| Género | 'pepito' (masculino) o 'pepita' (femenino) |
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.
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.
Archivo: js/motor/configuracion.js (95 líneas) — punto central de configuración.
| Constante | Valor | Uso |
|---|---|---|
ANCHO_JUEGO | 960 | Ancho del canvas en píxeles |
ALTO_JUEGO | 540 | Alto del canvas (16:9) |
TAMANO_TILE | 32 | Tamaño estándar de tile |
GRAVEDAD | 0.6 | Aceleración vertical en plataforma |
VELOCIDAD_JUGADOR | 3 | Píxeles por frame |
FPS_OBJETIVO | 60 | Frames por segundo |
ESCALA_MINIMA | 0.5 | Escala mínima del canvas |
ESCALA_MAXIMA | 3.0 | Escala máxima del canvas |
CLAVE_GUARDADO | 'arclycee_guardado' | Key de localStorage |
| Tipo | Ancho × Alto |
|---|---|
| Jugador | 32 × 32 |
| Compañero | 24 × 24 |
| Cemí | 16 × 16 |
| NPC | 32 × 32 |
| Boss | 64 × 64 |
| Boss Grande | 96 × 96 |
| Item | 16 × 16 |
| Petroglifo | 48 × 48 |
| # | Archivo | Líneas | Responsabilidad |
|---|---|---|---|
| 1 | santuario-manati.js | 2,905 | Santuario: oxígeno, tiburones, lanchas, ecología |
| 2 | mundo-acuatico.js | 2,174 | Naufragio: medusas, tortugas, ballenas, combate |
| 3 | sonido-procedural.js | 1,931 | 60 efectos de audio generados por código |
| 4 | zona-colonial.js | 1,923 | Zona Colonial: NPCs, combate, activismo |
| 5 | juego.js | 1,659 | Game loop, escenas, overlays, toasts |
| 6 | es.js | 1,523 | Traducciones en español |
| 7 | fr.js | 1,472 | Traducciones en francés |
| 8 | en.js | 1,408 | Traducciones en inglés |
| 9 | cuevas-pomier.js | 1,340 | Cueva plataforma con linterna |
| 10 | mundo-juridico.js | 1,335 | Aeropuerto: combate legal, INTERPOL |