Skip to content

Repository files navigation

LoreForge

Generador web de mapas de fantasía procedurales, deterministas y "vivos": rellenas un cuestionario breve y obtienes un mundo envejecido —civilizaciones que nacen y caen, guerras, una plaga, figuras legendarias— renderizado como un mapa SVG interactivo con estética de libro de fantasía. Inspirado en el modo leyendas de Dwarf Fortress, pero moderno.

Construido con Angular 21 + TypeScript, terreno Voronoi (d3-delaunay) + ruido simplex-noise, y un motor de simulación propio determinista por semilla. La IA es opcional y solo adorna (nombres y narrativa); nunca cambia los hechos del mundo.

Cuestionario de generación de mundo

Mapa interactivo con lore, crónica y figuras notables


Crear un mundo

Arranca la app (ver más abajo) y abre http://localhost:4200. El cuestionario pide:

Campo Qué controla
World name La semilla. Mismo nombre → exactamente el mismo mundo (determinista).
Cells Densidad del terreno Voronoi (~300–600).
Sea level Proporción de océano frente a tierra.
World lifespan Años que simula el motor antes de "congelar" el presente.
Factions Número de civilizaciones iniciales (2–6).
Add AI flavour (opcional) Activa el enriquecimiento por IA con tu propia clave (ver abajo). Apagado por defecto.

Pulsa Generate World. Tras la pantalla de forja aparece el mapa interactivo:

  • Click en cualquier celda → panel lateral con su lore, asentamientos, figuras y eventos.
  • Slider temporal (cabecera) → reproduce la historia año a año. Las bandas de era (founding · expansion · conflict · decay) muestran en qué etapa de la vida del mundo estás; la crónica se reescribe por capítulos según el año visible.
  • Copy link → comparte el mundo exacto vía URL (la semilla y los parámetros van en el enlace; la clave de IA nunca).

IA opcional — modelo BYO-key (bring your own key)

Por defecto la app funciona 100 % sin clave ni red: genera nombres y lore deterministas a partir de la semilla mediante un fallback compartido entre frontend y backend.

Si activas Add AI flavour, introduces en la propia UI:

  • Tone — un estilo libre (runepunk, dark elves, grimdark, high magic…).
  • API base URL — cualquier endpoint compatible con OpenAI (https://api.openai.com/v1, https://api.deepseek.com, etc.).
  • Model — p. ej. gpt-4o-mini, deepseek-chat.
  • API key — tu clave.

El servidor no guarda ninguna clave. Cada petición a POST /api/enrich lleva las credenciales del usuario, se usan para esa única llamada y no se almacenan ni se registran. No hay secretos que configurar en el despliegue. Si la llamada falla o no hay credenciales, se devuelve el fallback determinista automáticamente.

SSRF guard: como la base URL la controla el cliente, el backend exige https y rechaza cualquier host que sea (o resuelva a) una IP privada/reservada. Para un LLM local en desarrollo, arranca el servidor con LLM_ALLOW_PRIVATE=true.


Arrancar la app (desarrollo)

npm install
npm run dev        # backend (puerto 3000) + frontend (puerto 4200) en paralelo

O por separado:

npm run start:server   # solo backend  → http://localhost:3000
npm start              # solo frontend → http://localhost:4200 (proxy /api → :3000)

Build de producción:

npm run build          # bundle Angular en dist/loreforge/browser

Despliegue con Docker (un solo contenedor)

Una sola imagen construye la SPA y arranca el servidor Node, que sirve a la vez el estático y el endpoint /api/enrich en el puerto 3000. Al ser BYO-key, no hay secretos que configurar.

docker compose up -d --build      # levanta http://localhost:3000

Equivale a:

docker build -t loreforge .
docker run -p 3000:3000 loreforge

Variables opcionales: PORT (puerto HTTP), STATIC_DIR (ruta de la SPA construida, ya fijada en el Dockerfile) y LLM_ALLOW_PRIVATE=true (permitir endpoints LLM en red privada).


Test de determinismo

node scripts/test-determinism.mjs       # terreno + simulación
node scripts/test-sim-determinism.mjs   # solo simulación

Generan el mismo mundo dos veces con la misma semilla y verifican que son idénticos.


Arquitectura

server/
  index.ts          # Express: POST /api/enrich (BYO-key) + sirve la SPA en prod + SSRF guard
src/
  engine/           # Motor puro TS (sin Angular, sin Node) — fuente de verdad del mundo
    types.ts        # Tipos: Cell, Faction, Settlement, Figure, WorldEvent, World
    terrain.ts      # Generación Voronoi + simplex-noise
    simulation.ts   # Expansión, guerra, plaga y eras deterministas
    lore-fallback.ts# Fallback determinista + tipos del DTO/LlmResult (frontend + backend)
    index.ts
  app/
    services/
      llm.service.ts             # POST /api/enrich con FlavourConfig opcional + fallback local
      world-generator.service.ts # Orquesta terreno → simulación → enriquecimiento
    components/
      map-view/                  # Render SVG interactivo (celdas, asentamientos, slider)
      info-panel/                # Paneles bento con lore, crónica por eras y leyendas
    app.ts / app.html / app.css  # Raíz: cuestionario + visor
scripts/
  test-determinism.mjs           # Verifica determinismo en Node
Dockerfile / docker-compose.yml  # Despliegue single-container
proxy.conf.json                  # Redirige /api → localhost:3000 en dev
.env.example                     # Opciones del servidor (sin secretos: es BYO-key)

Flujo de enriquecimiento (IA opcional)

Browser  (DTO compacto: seed, factions[], settlements[], figures[], events[] +
  └─ POST /api/enrich              tone/baseUrl/model/apiKey del usuario, si los hay)
        │
        ▼
  Express backend (server/index.ts)
        │   ¿llegan credenciales del usuario en el body?
        ├── sí → assertSafeProviderUrl() (SSRF) → llamada OpenAI-compatible (json_object)
        └── no → buildFallbackResult() determinista
        │   (las credenciales se usan solo para esta llamada; nunca se guardan ni se loguean)
        ▼
  LlmResult { factions[{name,capitalName,lore}], lore[], legends[], chronicle, eraChronicles }
        │
        ▼
  Browser renderiza el mapa con nombres + lore + crónica por eras

Si el fetch falla (backend caído), LlmService ejecuta buildFallbackResult() localmente, así que la app funciona completamente offline y sin backend.


Licencia

MIT © develatter

About

Create a fantasy world from a seed and watch centuries of history unfold through wars, rulers, settlements, and legendary figures.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages