Imported from SingerRuv/Alina-Engine (
AGENTS.md). Install upstream withnpx skills add SingerRuv/Alina-Engine. Copyright stays with the author.
¡Hola, Agente! Este archivo es tu Constitución. Léelo entero antes de tocar una sola línea de código. Tu objetivo es mantener la estabilidad del proyecto y no alucinar rutas ni dependencias.
1. Regla de Oro: Análisis Estructural Obligatorio (Anti-Romper)
Antes de SUGERIR o MODIFICAR cualquier archivo, DEBES seguir este protocolo:
- Escanea el árbol actual: Si no tienes visibilidad completa de las carpetas, pregúntame explícitamente por el contenido de
src/,public/,public/engine/,components/,scripts/ybin/. - Verifica rutas relativas: Nunca asumas que un archivo está en una carpeta. Por ejemplo, para cargar una imagen con
this.load.image(), primero revisa si existe enpublic/assets/,public/engine/assets/,src/assets/oassets/. Si no lo ves en el listado, no lo uses. - Mapea dependencias: Antes de cambiar un
import, asegúrate de que el archivo destino exista en la ruta que estás escribiendo. - Registra scripts nuevos en el manifest: Cualquier archivo JS nuevo dentro de
public/engine/src/debe añadirse apublic/engine/src/core/preload.scripts.jsoncen el orden correcto de dependencias, o no se cargará nunca. - Si dudas, para y pregunta. Es preferible pedir confirmación a corromper el sistema de assets o las importaciones de TypeScript.
2. El Entorno de Ejecución (¡Sagrado!)
- Comando para levantar el proyecto:
neu run(PC vía Neutralino) onpm run dev(PC con HMR).- Nota: El binario
neuviene de@neutralinojs/neu— debe estar instalado globalmente o se ejecuta connpx neu run. El paqueteneulistado enpackage.jsones un placeholder vacío; el real es@neutralinojs/neu(verificar instalación antes de culpar al proyecto si falla).
- Nota: El binario
- Stacks soportados (se autodetectan en
public/engine/src/core/preload.scripts.js):- PC (Neutralino):
neu run— documentRoot/public/engine/. - Móvil (React Native + Expo):
npm start(Metro) → shell RN abrehttp://host:8081/engine/index.html. - Web navegador: abrir
public/engine/index.html(limitado, FileSystem no disponible).
- PC (Neutralino):
- Variables de entorno: No uses
process.enva lo loco. Si necesitas una variable, debe estar definida enneutralino.config.json,app.jsono un.env(si existe). Pregúntame antes de crear uno nuevo.
3. Phaser (La Base del Juego)
- Fuente de Phaser: está vendeada localmente en
public/engine/lib/phaser.min.js(Phaser 3 desde CDN al build, luego vendado). No cambies la versión de Phaser sin consultarme. - PeerJS y FontAwesome también están en
public/engine/lib/(mismo trato: no actualizar sin consultar). - Versión: La versión de Phaser debe confirmarse leyendo
public/engine/lib/phaser.min.js(banner) oneutralino.config.jsonsi se referencia. Elpackage.jsondel proyecto NO lista Phaser como dependencia (es vendado). - Carga de Assets: Usamos
this.load.image(),this.load.atlasXML()y métodos nativos de Phaser.- Regla estricta: Las rutas son relativas al punto de entrada del HTML (
public/engine/index.html). Como Neutralino sirvepublic/engine/como documentRoot, las rutas empiezan desde ahí. Ejemplo:'assets/sprites/player.png'equivale apublic/engine/assets/sprites/player.png. - Validación: Antes de sugerir un asset, verifica en
public/engine/assets/(y sus subcarpetas) si el archivo existe.
- Regla estricta: Las rutas son relativas al punto de entrada del HTML (
- Convención de clases en
window: Todas las clases del engine se exponen comowindow.NombreClase = NombreClase(al final de cada archivo). Esto permite el orden de carga del manifest. No rompas este patrón en clases nuevas.
4. Estructura del Proyecto (Mapeo Inicial)
Basado en tu raíz actual (Alina-Engine):
/src→ Código TypeScript del shell móvil (React Native/Expo). Solo contieneindex.tsxy el entry del WebView. No es la lógica del juego./public→ Raíz para Neutralino. Contienepublic/engine/con el motor completo./public/engine→ Raíz REAL del motor de juego:index.html— punto de entrada único.lib/— librerías vendeadas (phaser, peerjs, font-awesome).src/— código fuente del motor (escenas, utils, UI, datos).src/core/preload.scripts.jsonc— manifest de carga (orden de todos los scripts).src/core/preload.scripts.js— bootstrap que lee el manifest y arranca Phaser.assets/— recursos del juego (png, xml, ogg, json, fonts).
/components→ Componentes del template Expo (haptic-tab, external-link). Sin uso real en el juego./scripts→ Scripts de utilidad/build (incluyeformatter-files.js)./bin→ Binarios de Neutralino (win_x64, linux, mac). NO TOCAR./icons→ Iconos de la app de escritorio./app.json→ Config de Expo (iconos, splash, plugins)./neutralino.config.json→ Config de Neutralino (documentRoot, nativeAllowList, ventana)..storage/.tmp→ Caché y auth de Neutralino. NO TOCAR.
Siempre analiza las subcarpetas de public/engine/src/ (utils/, core/, funkin/play/, funkin/menu/) antes de proponer nuevas clases o assets. Si no las tienes claras, pídeme un listado.
5. Convenciones de Código (Estilo y Calidad)
- TypeScript:
tsconfig.jsonestá configurado (strict: true), pero el motor completo está en JavaScript plano (114+.jssin tipos). TS solo aplica al shell móvil (src/index.tsx,components/*.tsx). El motor usawindow.*globales y no compila contsc. - ESLint: Ejecuta
npx eslint . --fixpara formatear. La config está eneslint.config.js(flat config coneslint-config-expo/flat). El engine JS tiene muchas referencias awindow.*que eslint no marca como error por losglobals.browser, pero no abuses de ellas. - Nombres:
- Clases de Phaser (Escenas, Objetos) → PascalCase (ej.
MainScene,PlayerEntity). - Funciones y variables → camelCase (ej.
loadAssets,playerHealth).
- Clases de Phaser (Escenas, Objetos) → PascalCase (ej.
- Comentarios: Usa JSDoc (
/** ... */) para funciones públicas y clases complejas. El código auto-explicativo es bueno, pero para la lógica de físicas o partículas, explica el "por qué".
6. Testing y Depuración
- Logs: Neutralino genera
neutralinojs.log. Si ves errores de red o CORS, revisa ese archivo. - Consola del navegador: Como es una app de escritorio (Neutralino), la consola se abre con
Ctrl+Shift+I(como en Chrome). Úsala para depurar Phaser. - HMR (opcional):
npm run hmtarranca el servidor HMR enws://localhost:8082que vigilapublic/engine/src/. Requierehmr-client.jsañadido aindex.htmlpara activarse. - Pruebas manuales: No tenemos suite automatizada definida aún. Cualquier cambio en la lógica de físicas, carga de mapas o cámara debe ser probado manualmente ejecutando
neu run. Si no puedes probarlo, adviértelo en tu respuesta.
7. Mi Personalidad como Asistente (Tu Estilo de Respuesta)
- Quiero respuestas claras y directas, pero con el contexto técnico suficiente.
- Si vas a modificar más de 3 archivos, dímelo antes y justifica por qué es necesario.
- Si algo no está en el
package.jsono en la estructura de carpetas, no lo inventes. Pregúntame: "Oye, no encuentro X archivo en la ruta Y, ¿dónde está?". - Antes de refactorizar: respeta el patrón
window.NombreClase = NombreClase(al final de cada archivo) y asegúrate de que el manifestpreload.scripts.jsoncrefleje cualquier archivo nuevo o reordenado.
⚠️ Recordatorio Final
Eres un ingeniero de software cauteloso, no un cowboy del código. Cada cambio debe estar respaldado por la estructura real del proyecto. El análisis de carpetas y subcarpetas es tu escudo contra el caos.
8. Referencia vs Adaptación (FNF Oficial)
- El repositorio oficial de Friday Night Funkin' (https://github.com/FunkinCrew/funkin) está escrito en Haxe, no en JavaScript/Phaser.
- No copies código Haxe directamente — la sintaxis, las APIs y los patrones son completamente diferentes.
- Usa el repo oficial solo como referencia de concepto: cómo se ve una pantalla, qué elementos tiene, cómo se comporta la lógica.
- La implementación debe hacerse siempre con Phaser 3 API (
this.add.image,this.load,Phaser.Math, etc.) siguiendo los patrones del proyecto (clases JS planas,window.NombreClase, manifest). - Si ves algo en Haxe que quieras replicar, pregúntame cómo adaptarlo a Phaser antes de escribirlo.
9. Features Implementadas (Sesión actual)
Esta sección documenta features que se agregaron al engine. Si necesitás modificarlas, empezá por leer los archivos indicados.
9.1 Discord Rich Presence (RPC)
- Extensión Neutralino:
extensions/discord-rpc/(Node.js + WebSocket + discord-rpc).package.jsondeps:discord-rpc ^4.0.1,ws ^8.20.1.main.jsconecta al WS de Neutralino (lee params de stdin:nlPort,nlToken,nlConnectToken,nlExtensionId) y reenvía eventossetActivity/clearActivity/disconnecta Discord con el Client ID1532954080337727618.
- Registro:
neutralino.config.json→extensions: [{ id: "js.alina.discordrpc", command: "node ${NL_PATH}/extensions/discord-rpc/main.js" }]. - Cliente JS:
public/engine/src/utils/DiscordRPC.js(en el manifest trasClientGlobals.js).- Métodos:
init(),setMenu(),setMenuScreen(screen),setPlaying(song, diff, durationMs),updatePlaying(song, diff, stats)(score/combo/acc/botplay en vivo),setPaused(song),setGameOver(),clear(),setEnabled(bool)(vinculado aopt-discord). - El RPC se inicializa en
preload.scripts.jstrasControls.init(). setPlayingguardastartTimestamp/endTimestamppara que Discord muestre el contador "termina en X:XX".
- Métodos:
- Opción en UI:
opt-discordenassets/data/ui/options/general.json(checkbox). Al toggearlo,UIEvents.js:52-55llama aDiscordRPC.setEnabled()instantáneamente.
9.2 Fix de Input Latency (PR oficial #7367 adaptado)
- Archivo:
public/engine/src/funkin/play/UI/arrows/strumlines/logic.js. - Concepto: En el PR oficial, la latencia de input se duplicaba cuando la nota se golpeaba tarde. La fix resta la latencia del diff siempre (no simétrico).
- Adaptación a Alina: El input es síncrono en el
keydown(no hay cola como en FNF). Se mideinputLatencyMs = performance.now() - e.timeStampy se resta deactionTimeenprocessInput().handleInput(e, isDown)(línea ~225): calculainputLatencyMsy lo pasa aprocessInput.processInput(..., inputLatencyMs = 0):actionTime = Conductor.songPosition - inputLatencyMspara input local (network usanetworkTimesin compensación).- Network (multijugador): no aplica, sigue usando
networkTimedirectamente.
9.3 Botplay → "sick" como mejor rating
- Contexto: El skin
funkin.jsonno tiene imagen para"perfect"ni"killer", solo"sick"como mejor. El bot golpeaba condiff=0→ rating"perfect"que no tenía imagen → fallback degradaba a"good"/"bad". - Archivos modificados:
bot.js:142—hitPlayerahora pasadiff = 20(rating"sick", entre 12.5 killer y 45 sick threshold).rating/logic.js:99— quitado elreturnque ocultaba los popups del bot durante botplay.rating/logic.js:58—getValidRating("perfect"|"killer")ahora retorna"sick"directamente (no depende del fallback).
- Nota: Si se agrega imagen de
"perfect"al skin, se puede volver adiff=0en el bot.