ArcLycée is a 2D RPG written in vanilla JavaScript (ES Modules) without frameworks or bundlers. All rendering is done with HTML5 Canvas, sound effects are generated by code with Web Audio API, background music uses MP3 files with crossfade, and all sprites are drawn procedurally.
| Metric | Value |
|---|---|
| JavaScript files | 53 |
| Total lines of code | ~37,500 |
| Canvas resolution | 960 × 540 px (16:9) |
| Target FPS | 60 |
| Supported languages | 3 (Español, Français, English) |
| Translation keys | ~1,100+ |
| Procedural sound effects | 60 methods |
| Playable worlds | 12 + world map |
| Mini-games | 4 (batú, calibration, programming, wires) |
| Directory | Contents | Files |
|---|---|---|
js/motor/ | Game loop, input, rendering, sound, save, configuration | 6 |
js/escenas/ | Main menu, character selection, intro, final cutscene | 4 |
js/mundos/ | 12 playable levels: caves, settlements, mountain, colonial, aquatic, legal, laboratory, LFSD, lake | 12 |
js/mecanicas/ | Combat, dialogue, inventory, photo album, 4 mini-games, quests, reputation, log | 11 |
js/personajes/ | Player (Pepito/Pepita) + 3 companions (Magnoboot, Viralata, Cemí) | 4 |
js/idiomas/ | ES/FR/EN translations + language manager | 4 |
js/mapas/ | Leaflet map with real data layers (Taíno, colonial, shipwrecks, museums) | 4 |
js/clima/ | Weather system + hurricanes | 2 |
| Technology | Usage |
|---|---|
| HTML5 Canvas | Game rendering — everything drawn by code, no external sprites |
| JavaScript ES Modules | Modular architecture without frameworks or bundlers |
| Web Audio API | 60 procedural sound effects + background music with 24 MP3s |
| LeafletJS + Stadia Maps | Interactive maps with 76 real archaeological sites from DR and Haiti |
| Kaplay.js | Touch input (analog joystick and D-pad) |
| CSS3 | Loading screen, responsive touch controls |
velocidadJugador), functions (estaPresionada), constants (ANCHO_JUEGO)File: js/motor/juego.js (1,659 lines) — the main game orchestrator.
The game runs at ~60 FPS using requestAnimationFrame.
Each frame computes delta time and runs two phases: update (logic) and draw (rendering).
Delta time is capped at 0.1 seconds to prevent player "teleportation" when returning from another tab:
dt = Math.min((currentTime - previousTime) / 1000, 0.1)
The game has 15 registered scenes. Only one scene is active at a time.
| Type | Scenes |
|---|---|
| Interface | Main menu, character selection, intro cutscene, final cutscene |
| Playable worlds (9) | Pomier Caves, Settlement I, Settlement II, La Isabela, Colonial Zone, Aquatic World, Airport, Laboratory, LFSD |
| Navigation | World map (tile map) |
| Secret | Level selector (Konami Code) |
Each scene implements three methods:
iniciar(juego) — initial setup when entering the sceneactualizar(dt, entrada, jugador, companeros) — per-frame logic (movement, collisions, AI)dibujar(renderizador, ancho, alto, textos, jugador, companeros) — visual rendering
Scene changes use juego.cambiarEscena('name'), which calls salir() on the previous scene,
creates the player if the new scene is playable, and calls iniciar(juego) on the new one.
Returning to the world map triggers auto-save.
Overlays are drawn on top of the active scene, in this order (bottom to top):
juego.mostrarToast(text, duration) adds a floating message. They stack vertically
(max 4), with 0.3s fade-in and fade-out in the last second. Used for item pickups,
quest completion, danger alerts, etc.
When the player reaches 0 health: comic "wah wah waaah" sound, random death message (4s),
2.5s pause, then respawn at the world map with 30% health restored.
A _muerteEnCurso flag prevents triggering death multiple times.
File: js/motor/entrada.js (494 lines) — unified abstraction layer for keyboard and touch.
| Action | Keys | Usage |
|---|---|---|
| Move | WASD / Arrows | Exploration and menus |
| Jump | Space | Caves only (platformer) |
| Interact | E / Enter | Talk to NPCs, use objects |
| Inventory | I | Open/close backpack |
| Special | F | Metal detect (Magnoboot) |
| Return to map | M | Exit current level |
| Real map | R | Open Leaflet reference map |
| Log | L | Game Log (quests) |
| Photo | T | Take photo of nearby entity |
| Selfie | G | Take selfie with entity |
| Album | P | View photo gallery |
| Zoom | + / - | Zoom on world map |
| Konami Code | ↑↑↓↓←→←→ | Secret level selector |
All input queries go through entrada.estaPresionada(action), which combines keyboard and touch.
Internally uses two Map objects: teclasPresionadas for keyboard and accionesTocadas for touch.
Two modes switchable from Options menu (preference saved in localStorage):
5 action buttons on the right: A (action), B (cancel), X (inventory), Y (special), JUMP.
Use touchstart/touchend instead of click to avoid the 300ms mobile delay.
To prevent a key from "repeating" when held, a bloqueoEntrada flag is used:
activated on action processing and deactivated only when the key is released.
Initialized to true when entering scenes where the activation key might be pressed.
The window.blur event clears all pressed keys. This prevents the player from
continuing to move when returning from another tab with a movement key pressed.
File: js/motor/renderizado.js (199 lines) — Canvas 2D wrapper.
Fixed resolution of 960×540 (half of 1080p, easy to upscale). Image smoothing is disabled
(imageSmoothingEnabled = false) to preserve pixel art crispness.
| Method | Description |
|---|---|
limpiar() | Clears previous frame with clearRect |
dibujarRectangulo() | Filled rectangle (collisions, placeholders) |
dibujarTexto() | Text with configurable font, color and alignment |
dibujarBarra() | Progress bar with clamped percentage [0, 1] and black border |
aplicarFiltroCueva(playerX, playerY, lightRadius) creates a torch effect using a
radial gradient with transparent center and opaque edges:
Aquatic worlds use a _sacudida timer that, when activated (by speedboat collision,
shark bite or jellyfish), applies a horizontal oscillation to the player sprite:
ctx.translate(Math.sin(totalTime * 50) * amplitude * intensity, 0) // intensity = _sacudida / initialDuration (decays linearly)
In aquatic worlds, entities (NPCs, turtles, jellyfish, player) are sorted by their Y coordinate before drawing, creating a natural depth effect where closer objects overlap further ones.
File: js/motor/sonido-procedural.js (1,931 lines) — 60 sound effects generated by code.
Sound effects are synthesized in real time by combining oscillators (sine, square, sawtooth, triangle), filters (lowpass, highpass, bandpass), white noise buffers and gain envelopes. Background music is played from MP3 files with automatic crossfade.
Browsers block Web Audio until the first user interaction.
_asegurarContexto() creates the AudioContext only when first needed.
Most sounds use a frequency sweep (exponential or linear ramp) to create a sense of motion:
osc.frequency.setValueAtTime(startFreq, now) osc.frequency.exponentialRampToValueAtTime(endFreq, now + duration)
Controls volume over time: fast attack → sustain → exponential decay to silence.
Generated by filling a buffer with random values (-1 to 1), then filtered to create different textures: footsteps (lowpass 800 Hz), sparks (highpass 2000 Hz), crowd (bandpass 600 Hz).
A low-frequency oscillator (4-30 Hz) modulates another parameter (usually the main frequency) to create vibrato, wobble or sirens:
lfo.frequency.value = 4 // 4 Hz wobble lfoGain.gain.value = 8 // ±8 Hz modulation depth lfo → lfoGain → osc.frequency
| Method | Technique | Description |
|---|---|---|
salto() | Square 150→400 Hz | Player jump |
aterrizar() | Sine 100→40 Hz | Landing |
recoger() | Two square notes (523, 659 Hz) | Item pickup |
paso() | White noise 40ms, lowpass 800 Hz | Footstep |
goteo() | Sine 800→240 Hz, randomized | Water droplet |
dano() | Sawtooth 200→50 Hz | Generic damage |
muerte() | Three descending sawtooth notes with LFO | Comic "wah wah waaah" |
| Method | Description |
|---|---|
combateAtacar() | Aggressive sawtooth 400→100 Hz |
combateHablar() | Two simultaneous sines (300, 350 Hz), peaceful |
combateNegociar() | Two alternating square notes with pause |
combateRedesSociales() | Two sine "ding-ding" (phone notification) |
combateProtestas() | Three square pulses (protest chant rhythm) |
combateDenuncia() | Noise + sine 150→60 Hz (official stamp) |
combateViaLegal() | Triangle 180→90 Hz (judicial gavel) |
combateAtrapar() | Noise bandpass 1200 Hz (fishing net) |
combatePescar() | Sine 200→800 Hz + noise lowpass (harpoon + splash) |
combateProtegerCoral() | Warm three-note major chord |
combateAlertarBuzos() | Square 700 Hz with 8 Hz LFO (underwater whistle) |
combateLey318() | Triangle 200→100 Hz (judicial strike) |
combateEvidenciaForense() | Two sine beeps (camera/scanner) |
combateInterpol() | Square 600→800→600 Hz (police siren chirp) |
combateUnesco() | Solemn major chord (diplomatic authority) |
combateVictoria() | Ascending triumphant arpeggio |
combatePacificado() | Sustained major chord 0.5s (peace achieved) |
| Method | Description |
|---|---|
batuGolpe() | Triangle 120→60 Hz (ball-on-stone impact) |
batuRebote() | Sine 300→150 Hz (rubber bounce) |
batuPunto() | Three-note ascending arpeggio (scoring) |
batuSaque() | Sine 600→800 Hz (serve whistle) |
batuMultitud() | White noise bandpass 600 Hz (crowd roar) |
| Method | Description |
|---|---|
cantoBallenaCerca() | Three long sine notes with individual LFO, lowpass 600 Hz (humpback whale vocalization) |
manatiLlamada() | Sine 80→120→90→110 Hz wobble (manatee undulant call) |
lanchaImpacto() | 3 layers: triangle impact 200→40 Hz + noise splash + synthetic Wilhelm scream (sawtooth with 30 Hz LFO) |
mordidaTiburon() | 3 layers: jaw crunch (highpass noise 3000 Hz) + deep impact + growl with 12 Hz LFO |
| Method | Description |
|---|---|
calibracionTono(freq) | Sine at given frequency, 0.2s |
calibracionExito() | Ascending major arpeggio (4 notes) |
calibracionFallo() | Two descending sawtooth pulses |
calibracionAjuste() | Highpass noise 2000 Hz, 20ms (dial click) |
bloqueColocar() | Square 800→400 Hz + noise (block snapped) |
bloqueQuitar() | Sine 300→600 Hz ascending (block removed) |
robotMoverse() | Modulated sawtooth 120→180→120 Hz (servo motor) |
robotExito() | Triumphant 4-note square arpeggio |
robotFallo() | Two descending square beeps |
cableConectar() | Sine 1200→600 Hz + contact noise |
cableError() | Sawtooth + highpass noise 3000 Hz (electrical spark) |
circuitoCompleto() | Exponential sine + triangle (circuit power-up) |
robotFLLIniciar() | Continuous sawtooth motor whir (FLL chain-track robot start) |
robotFLLDetener() | Stops the motor with smooth fade-out (FLL robot stop) |
robotFLLClick() | Short mechanical click (currently unused) |
| Method | Description |
|---|---|
dialogo() | Quick square "bleep" 380±40 Hz (Undertale-style text character) |
fotoCaptura() | Noise 0.08s + square 1200→300 Hz (shutter + mechanical click) |
selfieCaptura() | Noise 0.06s + sine 400→1200 Hz ascending (shutter + flash) |
lluviaAmbiente() | Continuous loop: filtered noise + periodic drops every 150-350ms |
js/motor/musica.js)
The SistemaMusica class manages the game's background music using MP3 files.
Unlike the sound effects (procedural), the music uses 24 real audio files
organized into 12 thematic groups (2 tracks per group).
| Group | Scene(s) | Description |
|---|---|---|
menu | menuPrincipal | Main menu music |
cuevas | cuevasPomier | Cave ambience |
taino | Taíno worlds (settlements) | Indigenous music |
colonial | Colonial worlds | Colonial era |
acuatico | Aquatic World | Underwater ambience |
aeropuerto | Legal World | Punta Cana Airport |
museo | Laboratory World | Museum interior |
lfsd | LFSD | Robotics classroom |
mapa | mapaPrincipal | World map |
creditos | finalCinematica | Final cutscene and credits |
Each group has 2 tracks (A and B). When one track ends, the system crossfades over 2 seconds
to the other track, creating a continuous loop with no silence (A → B → A → B...).
Both tracks use HTMLAudioElement and volume is linearly interpolated during the transition.
When combat or a batú match starts, the background music is temporarily paused. When the overlay ends, the scene's music is automatically restored.
Music volume (0-100%) is configured from the Options menu.
The preference is saved to localStorage under the key arclycee_volumen_musica
and restored when the game starts.
Browsers block automatic audio playback until the first user interaction. If the initial playback attempt fails, the system automatically retries when the first keypress is detected in the main menu.
File: js/mecanicas/combate.js (932 lines) — Undertale-style turn-based combat with pacifist route.
Combat in ArcLycée is not traditional mutual damage. The goal is to convince the enemy through diplomatic actions. Two emotional meters:
Each enemy can have opcionesPersonalizadas — an array of unique actions with random
patience/hostility ranges and specific counter-responses:
| Enemy | Options | Label |
|---|---|---|
| Constructor Méndez | Social media, Protests, Report, Legal route | "Convinced:" |
| Lionfish | Capture, Fish, Protect coral, Alert divers | "Controlled:" |
| Rodrigo Torres | Law 318, Forensic evidence, INTERPOL, UNESCO 1970 | "Evidence:" |
4 sprite types drawn procedurally: _dibujarSoldado(), _dibujarConstructor(),
_dibujarPezLeon(), _dibujarTraficante(). Each with unique design and animation.
When the player chooses to attack and has active companions, a sub-menu appears asking "Who do you attack with?" showing: Solo / Magnoboot +3 / Viralata +2 / Cemí +4. Navigate with arrows (up/down), E to confirm, Q to cancel and return to the main combat menu.
A toggleable overlay activated with the H key that explains combat objectives, the differences between the pacifist and aggressive paths, the meaning of the meters (patience, hostility, health), and the reputation rewards depending on the type of victory.
Dynamic text showing who is winning the combat. Changes based on patience and hostility values: "Almost convinced", "Very hostile", "Making progress", "Tense situation".
Both player action messages and enemy counter-attack messages are displayed for 2.5 seconds. Afterwards, a pulsing [E] Continue button appears that the player must press to advance to the next turn.
For enemies that have opcionesPersonalizadas, the enemy HP bar is hidden.
These enemies are defeated by conviction (patience = 100), not by HP damage.
Pacifist victory = +15 reputation. Force victory = +5. Additionally, the player's current reputation
gives an initial advantage: initialPatience = min(reputation / 2, 25).
File: js/mecanicas/dialogo.js (326 lines) — dialogue engine with typewriter effect.
Characters appear one by one. A _caracteresVisibles counter increments each frame,
and the displayed text is text.substring(0, Math.floor(characters)). Each character triggers
a dialogue "bleep" (procedural sound).
Dialogue lines can include opciones: [{texto, valor}]. When options are present,
advancement is blocked until the player selects one (↑↓ to navigate, E to confirm).
alTerminar allows chaining events after a dialogue ends: start combat,
give an item, discover a quest, etc.
Mentor NPCs (Roberto Cassá, Lcda. Carmen Vidal) use a counter that increments after each conversation.
The index is calculated as counter % array.length to cycle infinitely through topics.
Directory: js/personajes/companeros/ — three companions that follow the player across scenes.
All share: tipo property (string), activo flag,
activar()/desactivar() methods, and linear interpolation (lerp) movement
for smooth following instead of teleportation.
| Name | Type | Ability | Lerp Factor | Relative Position |
|---|---|---|---|---|
| Magnoboot | Excavator robot | Metal detector (F key), excavation (costs 10 energy) | 0.05 | Right of player (+30, +5) |
| Viralata | Tracker dog | Smell (50 px radius), tail wags when objects detected | 0.06 | Left of player (-25, +10) |
| Cemí Murciélago | Cave spirit | 6 power levels unlocked with botijas (echo-location → cemí vision) | 0.08 | Above-left (-20, -25) with sinusoidal floating |
Companions persist across scenes via juego.companeros[]. In saves they are serialized as
{tipo, activo} and reconstructed with _crearCompaneroBasico(tipo, activo).
File: js/mecanicas/inventario.js (742 lines) — visual backpack with 20-slot grid.
5 columns × 4 rows grid. Each slot is 52×52 px with 4 px margin. The panel is centered on the canvas.
Navigation with arrows, use item with E (if esUsable).
| Item | Healing | Source |
|---|---|---|
| Guanábana | +30 HP | Guarionex (Settlement II) |
| Healing vessel | +35 HP | Anacaona (Settlement I) |
| Casabe | +25 HP | Various |
Over 24 unique icons drawn with Canvas 2D: torch with light rays, golden cemí with eyes,
compass with red/white needles, colonial arquebus, coin with crown, etc.
_dibujarIconoObjeto(ctx, id, x, y, size) dispatches to the correct icon by ID.
File: js/mecanicas/album-fotos.js (1,140 lines) — capture system with dedicated per-entity-type rendering.
The tipoEntidad field determines which renderer to use. Scenes expose a
fotografiables[] array for additional entities beyond NPCs:
| tipoEntidad | Renderer | Detail |
|---|---|---|
'npc' | _renderizarNPCCentrado() | Draws NPC on auxiliary 200×200 canvas, detects bounding box by pixels, crops and centers |
'tortuga' | _renderizarTortuga() | 4 dedicated sprites: hawksbill (olive with amber stripes), leatherback (blue-black with 7 ridges), loggerhead (brown-red with heart mark), green (olive with spots) |
'coral' | _renderizarCoral() | 4 sprites: brain (hemisphere with meandering grooves), staghorn (5 bifurcated branches), fan (purple-pink with radial veins), table (elliptical platform on trunk) |
'petroglifo' | _renderizarPetroglifo() | 4 types: sun, bat, face, spiral |
'objeto' | _renderizarObjeto() | Generic collectible with glow |
Photo target detection uses Euclidean distance with an 80 px radius (RANGO_FOTO).
Searches NPCs first, then objects, then fotografiables[].
File: js/mundos/mapa-tiles.js (784 lines) — procedural terrain generation for the island.
Hispaniola's coastline is defined with ISLA_BITMAP: an array of 68 strings of 128 characters each.
Each character is '1' (land) or '0' (water). Manually traced from a NASA reference image,
scaled 2× from a 64×34 original bitmap.
DEEP_WATERISLA_BITMAP| Type | Walkable | Decoration |
|---|---|---|
| Deep water | No | Animated wave curves |
| Shallow water | Yes | Foam |
| Sand | Yes | Grain texture |
| Prairie | Yes | — |
| Forest | Yes | Tree crowns (circles) |
| Mountain | No | Triangles with rocky peaks |
| River | Yes | Animated current lines |
| Path | Yes | — |
Rivers are traced connecting waypoints with Bresenham's line algorithm. Only tiles that are already land are painted (water tiles are skipped), creating rivers that flow naturally along the terrain.
8 mountain ranges defined as waypoint sequences. For each land tile, the minimum distance to each range segment is calculated. If below 3.6 tiles, it becomes a mountain (non-walkable).
dibujarTilesVisibles() only renders tiles within the camera viewport,
calculating start/end column and row. This is crucial for performance given the map is
128×68 = 8,704 tiles total.
File: js/mundos/mapa-principal.js (809 lines) — navigation between level nodes.
13 nodes placed at verified walkable tile coordinates. Each has: ID, position, type (cave, village, city, shipwreck, etc.), destination scene, and previous node in the progression chain.
Visualization: circle (18-22 px) + border + emoji icon + name. Colors indicate state:
gray (locked), gold (active), green (completed). A pulsing golden [E] indicator appears when the player is nearby.
Top-right corner (120×75 px). Shows simplified tiles (step 2 for performance), nodes colored by state, player position and camera viewport.
_puedeMoverse() tests the sprite's 4 corners (with 4 px margin)
against the tile map's esCaminable().
Files: js/mundos/acuatico/mundo-acuatico.js (2,174 lines)
+ santuario-manati.js (2,905 lines).
| Condition | Speed Factor |
|---|---|
| Normal (underwater) | × 0.7 |
| With jellyfish effect | × 0.4 |
| With shark effect | × 0.4 (2s duration) |
4 jellyfish with sinusoidal movement between two waypoints:
t = (sin(phase + dt*0.8) + 1) / 2 → lerp between pointA and pointB.
Contact (radius 15-20 px) = 5 HP damage + 2.5s slowness + 2s invulnerability.
Math.sin swayThe player swims on lung power with a snorkel. O₂ bar from 100 to 0:
3 sharks patrol between waypoints with phase offset (0, π/2, π).
Bite: -8 HP + 2s slowness + shake effect (0.5s) + mordidaTiburon() sound.
Spawn every 5-15s in the surface zone. Collision: -10 HP + shake (0.6s) + synthetic Wilhelm scream
(lanchaImpacto()). The propeller zone causes periodic damage (-2 HP with cooldown).
Warning toast shown once per zone entry.
Files: js/clima/clima.js (442 lines) + huracan.js (324 lines).
| State | Probability | Intensity | Player Effect |
|---|---|---|---|
| Sunny | 45% | 0.1 | None |
| Cloudy | 25% | 0.3 | None |
| Rain | 20% | 0.6 | Speed × (1 − 0.15×i) |
| Storm | 7% | 0.8 | Speed × (1 − 0.25×i) + thunder |
| Hurricane | 2% | 1.0 | Wind push 2px×i + speed × (1 − 0.35×i) |
| Earthquake | 1% | — | 2% per frame stumble chance |
Up to 500 recycled particles. Spawn rate: 3-8 particles/frame based on intensity. Each particle has gravity + wind push.
Categories 1-5 with wind speed = category × 2 px/frame. Three phases: onset (0-25%, intensity ramps up) → peak (25-75%, max intensity) → end (75-100%, intensity drops). Debris physics with up to 500 objects bouncing off terrain. Direction: typically from the east (realistic for the Caribbean).
Continuous rain (lluviaAmbiente()) with dynamic intensity during transitions.
Random thunder every 4-15s during storms.
File: js/mundos/lfsd/mundo-lfsd.js — interior level of the Lycée Français de Santo Domingo robotics classroom.
Prof. Nicolas Droulers (white hair and white beard) guides the player.
The classroom contains work tables with Scratch programming screens, a whiteboard,
a 3D printer with a translated label matching the active language,
and a FIRST LEGO League table with black line-following paths
where an animated LEGO robot with chain tracks follows the paths with motor sound (robotFLLIniciar/robotFLLDetener).
9 student NPCs (3 quest-givers with colored T-shirts) and 4 workstations.
File: js/mecanicas/batu.js (935 lines) — 2D physics with adaptive AI.
| Parameter | Value |
|---|---|
| Gravity | 480 px/s² |
| Bounce coefficient (ground) | 0.8 |
| Bounce coefficient (walls) | 0.9 |
| Air friction | 0.998 per frame |
| Ball radius | 8 px |
| Win condition | First to 3 points |
| Zone | Body Part | Speed | Angle |
|---|---|---|---|
| < 25% | Head | 290 px/s | -70° (nearly vertical) |
| 25-50% | Shoulder | 425 px/s | -60° |
| 50-75% | Hip/yugo | 500 px/s (max) | -45° (strongest) |
| > 75% | Knee | 280 px/s | -30° (defensive) |
Each hit has ±25% speed variation and ±8° angle variation for unpredictability.
Between each point, a historical fact about batú is displayed: ball material (cupey tree latex), batey petroglyphs, the ceremonial yugo, areíto festivals, peaceful conflict resolution, and real archaeological sites (Chacuey, La Aleta).
intro → serve → playing → point → educational → serve → ... → gameEnd
File: js/mecanicas/calibracion-senal.js (330 lines) — oscilloscope puzzle.
3 dials (frequency, amplitude, phase) controlling a sine wave displayed on an oscilloscope (green on black). The player must match the target wave within ±15% tolerance.
y = centerY + sin(t + phase) × amplitude where t = (x − 40) / (width − 80) × π × 2 × frequency
↑↓ to switch dials, ←→ to adjust (±2), E to confirm. Time limit: 45 seconds.
Reward: calibrated magnetometer (progreso.magnetometroCalibrado = true), +15 reputation.
File: js/mecanicas/programacion-bloques.js (491 lines) — block-based navigation.
Arrange 5-6 instruction blocks (advance, turn left/right, scan, dive, ascend) to guide a submarine robot through a 6×4 grid. Robot starts at (0,1) facing right, target at (5,2).
Max 12 blocks. Execution interval: 0.6s between steps. Failure: robot exits grid or hits obstacle (if not submerged). Programming phase: 60s. Execution: automatic, no time limit.
Reward: archaeological sites layer on Leaflet map (progreso.robotProgramado = true), +15 reputation.
File: js/mecanicas/conexion-cables.js (408 lines) — matching puzzle on a circuit board.
Connect 6 inputs (left) to 6 outputs (right) by color and symbol. Outputs in random order. Connections drawn as cubic Bézier curves. Wrong connection = spark animation.
↑↓ to select input/output, E to connect. Time limit: 30 seconds.
Reward: +15 reputation (bonus +5 if completed in <15s), newspaper article in inventory.
File: js/mecanicas/reputacion.js (74 lines) — score from 0-100 with 4 levels.
| Range | Level | Bar Color |
|---|---|---|
| 0-24 | Unknown | Red |
| 25-49 | Known | Yellow |
| 50-74 | Respected | Green |
| 75-100 | Legendary | Gold |
Reputation affects combat (initial patience bonus) and is shown in the final epilogue. Gained through pacifist victories (+15), force victories (+5), mini-games (+15 each) and ecological actions (+10).
File: js/mecanicas/misiones-secundarias.js (89 lines) — state machine per quest.
| Quest | Description | Trigger |
|---|---|---|
| Batú | Play Taíno ball game with Higüemota | Talk to Higüemota in Settlement II |
| Manatee Rescue | Free manatee + clean reef | Talk to Dra. Sofía in Sanctuary |
| Good Vibrations | Calibrate the magnetometer | Talk to NPC in La Isabela |
| Full Metal Archeologist | Program submarine robot | Talk to biologist in Sanctuary |
| Weird Science | Repair analysis equipment | Talk to mentor in Laboratory |
Each quest progresses: undiscovered → discovered → in_progress → completed.
The Game Log (L key) shows all quest states in two tabs:
Main Story (13 nodes with ✅/🔓/🔒) and Side Quests (with checkmarks).
File: js/motor/guardado.js (243 lines) — localStorage persistence.
The game auto-saves when returning to the world map. "Continue Game" in the main menu restores all state and goes to the map.
Companions are saved as {tipo: 'magnoboot', activo: true} and reconstructed
as real instances via _crearCompaneroBasico(tipo, activo) using the Magnoboot,
Viralata and CemiMurcielago imports.
Directory: js/mapas/ (4 files, ~700 lines) — real-world geographic map with archaeological sites.
Center: 19.0°N, -70.5°W (Dominican Republic). Default zoom: 8. Opens with R key from any playable scene (not just the world map). Stadia Maps tiles (6 aesthetic layers: Watercolor, Terrain, Toner, Dark, Smooth, OSM Bright) with CARTO Voyager fallback.
| Layer | Icon | Count |
|---|---|---|
| Taíno sites | 🗿 | 16 (settlements, caves, petroglyphs in DR and Haiti) |
| Colonial sites | 🏰 | 8 (La Isabela, Colonial Zone, Cap-Haïtien, etc.) |
| Shipwrecks | ⚓ | 12 (Santa María 1492, San Miguel 1551, Concepción 1641, etc.) |
| Museums | 🏛 | 30 (DR + Haiti) |
| Unexplored sites | 🔍 | 8 (only visible after completing robot mini-game) |
Markers with popup (name + description) on click.
Click-to-travel: window._viajarANodo(id) for animated fly and scene change.
Visual state: completed (green border + brightness), locked (grayscale + opacity 0.4), active (white border).
File: js/escenas/final-cinematica.js (673 lines) — 5 endings + cinematic credits.
| Ending | Condition | Title |
|---|---|---|
| Complete | 8 nodes + 7 sidequests + more pacified than violent | "Legend of Quisqueya" |
| Pacifist | 8 nodes + more pacified, no violent | "Heritage Guardian" |
| Ecological | 3+ ecological actions + no violence | "Marine Ecosystem Protector" |
| Dark | More violent combats than pacified | "Heritage deserves another path" |
| Museum | Default fallback | "Curator of History" |
5 text steps with auto-advance (5-6s each), skip with E. Themed backgrounds per ending: golden gradients (complete/pacifist), marine blue with bubbles (ecological), red with fractures (dark), warm with light spot (museum). Reputation level shown in epilogue.
Automatic vertical scroll at 40 px/s. Lists the 8 creators (les fous du robot), the Liceo Francés de Santo Domingo and the year 2026. E to speed up/skip.
Directory: js/idiomas/ (4 files, ~4,400 lines).
3 translation files (es.js, en.js, fr.js) with ~1,100+ keys each,
organized as nested JavaScript objects. A manager idiomas.js (73 lines) provides:
cambiarIdioma(code) — switches between 'es', 'fr', 'en't(key) — dot-notation access: t('dialogos.cueva.petroSol')| Section | Content |
|---|---|
menu | Titles, main menu options |
seleccion | Character selection |
interfaz | HUD (health, oxygen, inventory), death phrases (10 variants) |
dialogos | All dialogues by world (cave, village, isabela, colonial, aquatic, legal, laboratory, LFSD, endings) |
combate | Combat actions and messages |
batu | 25 mini-game keys (rules, facts, messages) |
objetos | 32+ items with name and description |
inventario | Inventory UI |
clima | Weather state names |
guardado | Save system strings |
// In scenes and mechanics:
const texts = juego.idiomas.traducciones[juego.idiomas.idiomaActual];
const name = texts.menu.titulo;
// Or using the dot-notation helper:
const text = juego.idiomas.t('dialogos.cueva.petroSol');
File: js/personajes/pepito.js (258 lines).
| Property | Value |
|---|---|
| Dimensions | 28×32 px (narrower than 32 px tile for easier passage) |
| Max health | 100 HP |
| Gender | 'pepito' (male) or 'pepita' (female) |
Legs animate with Math.sin(cuadroAnimacion * 5) * 3 — a sinusoidal movement
simulating steps. Arms with Math.sin(cuadroAnimacion * 2) * 1.
Animation only advances when esAnimando = true.
js/escenas/seleccion-personaje.js (291 lines): the game sprite is shown scaled ×2.5
with live walking animation. Pepito is blue (#4488ff) with spiky hair; Pepita is purple (#aa44ff) with braids.
File: js/motor/configuracion.js (95 lines) — central configuration point.
| Constant | Value | Usage |
|---|---|---|
ANCHO_JUEGO | 960 | Canvas width in pixels |
ALTO_JUEGO | 540 | Canvas height (16:9) |
TAMANO_TILE | 32 | Standard tile size |
GRAVEDAD | 0.6 | Vertical acceleration in platformer |
VELOCIDAD_JUGADOR | 3 | Pixels per frame |
FPS_OBJETIVO | 60 | Frames per second |
ESCALA_MINIMA | 0.5 | Minimum canvas scale |
ESCALA_MAXIMA | 3.0 | Maximum canvas scale |
CLAVE_GUARDADO | 'arclycee_guardado' | localStorage key |
| Type | Width × Height |
|---|---|
| Player | 32 × 32 |
| Companion | 24 × 24 |
| Cemí | 16 × 16 |
| NPC | 32 × 32 |
| Boss | 64 × 64 |
| Large Boss | 96 × 96 |
| Item | 16 × 16 |
| Petroglyph | 48 × 48 |
| # | File | Lines | Responsibility |
|---|---|---|---|
| 1 | santuario-manati.js | 2,905 | Sanctuary: oxygen, sharks, speedboats, ecology |
| 2 | mundo-acuatico.js | 2,174 | Shipwreck: jellyfish, turtles, whales, combat |
| 3 | sonido-procedural.js | 1,931 | 60 code-generated audio effects |
| 4 | zona-colonial.js | 1,923 | Colonial Zone: NPCs, combat, activism |
| 5 | juego.js | 1,659 | Game loop, scenes, overlays, toasts |
| 6 | es.js | 1,523 | Spanish translations |
| 7 | fr.js | 1,472 | French translations |
| 8 | en.js | 1,408 | English translations |
| 9 | cuevas-pomier.js | 1,340 | Cave platformer with spotlight |
| 10 | mundo-juridico.js | 1,335 | Airport: legal combat, INTERPOL |