Lumiland: come ho costruito un mondo di orsetti di luce che vive da solo nel browser | Antonio Pedoto

Come è fatto dentro Lumiland: god-game 3D in Godot 4 e WebGL2, AI locale senza LLM, mondo infinito a chunk e un'ecologia auto-bilanciata. Storia tecnica e codic

Campo Dettaglio --- --- Nome Lumiland Genere God-game / life-sim 3D isometrico (l'eredità degli RTS anni '90–2000, con tecnologie moderne) Engine Godot 4.3 — linguaggio GDScript Piattaforma Web (WebAssembly + WebGL2); gira anche su mobile URL https://antoniopedoto.it/lumiland/ Protagonisti i Lumi — orsetti di luce, riconoscibili dal loro "ciuffo di luce" Cuore tecnico Simulazione headless separata dal rendering · stato serializzabile in JSON · AI cognitiva locale e deterministica (niente LLM) · mondo infinito a chunk · ecologia auto-bilanciata con curva a S Hook — un mondo che vive anche quando non lo guardi

Chiudi la scheda del browser. Vai a dormire. La mattina dopo riapri Lumiland e trovi un biglietto che non avevi scritto tu: "Mentre eri via, a Valdoro sono nati sette cuccioli. Una pattuglia è partita verso nord e ha fondato Boscoluce. Il vecchio Miele, il primo che avevi battezzato, non c'è più: i suoi figli lo hanno pianto per due giorni."

Non è una cutscene. Non è un testo precompilato che si attiva al login. È successo davvero — o meglio, è successo nel senso in cui succede dentro una simulazione: il mondo ha continuato a girare, ha calcolato quante ore reali erano passate, ha fatto invecchiare i Lumi, accoppiare le coppie, migrare le colonie, morire i vecchi. E poi te lo ha raccontato.

Questa è la promessa di Lumiland, ed è anche la sua sfida tecnica più grande. Un god-game in cui sei un dio che influenza ma non comanda deve avere creature con una vita propria così robusta da non aver bisogno di te. Devono mangiare, innamorarsi, litigare, imparare, costruire e morire anche quando non c'è nessuno a guardare. E tutto questo deve girare dentro un browser, in WebAssembly, su un telefono, senza un server che pensi al posto loro.

Come si fa? La risposta sta in un mucchio di decisioni controintuitive: separare nettamente la simulazione dalla grafica, rinunciare ai modelli linguistici di moda in favore di una mente cognitiva minuscola e deterministica, generare un pianeta infinito 64 metri alla volta, e — il pezzo più divertente — combattere per mesi contro un'ecologia che oscillava tra l'estinzione di massa e l'esplosione demografica. Mettiamoci dentro. Cos'è Lumiland, e perché è interessante

Lumiland è un god-game in 3D isometrico. Se sei cresciuto con i gestionali/RTS di fine anni '90 — quel filone in cui guardavi dall'alto un mondo brulicante e potevi spingerlo gentilmente in una direzione — riconoscerai subito il linguaggio: telecamera obliqua, popolino che fa le sue cose, e tu nel ruolo di una presenza superiore. La differenza è che sotto non c'è più un motore dell'epoca, ma Godot 4 che gira in WebGL2.

I protagonisti sono i Lumi: orsetti di luce con un "ciuffo di luce" emissivo sulla testa, il loro tratto distintivo. Ognuno è un individuo: ha un genoma, un temperamento, un umore, una memoria, dei legami con gli altri. Nascono, crescono, si accoppiano, hanno cucciolate, fondano villaggi, inventano cose, invecchiano e muoiono. Tu sei il loro dio.

E qui sta la frase che ha guidato ogni scelta di design: il dio influenza, non comanda. Puoi selezionare un gruppo di Lumi e spostarli — anche col dito, su touch — ma non è un ordine assoluto: è una spinta. Loro hanno libero arbitrio. Se sposti un Lumo affamato lontano dal cibo, magari ti ascolta per un momento e poi torna a cercare da mangiare, perché la fame conta più della tua volontà. Questo cambia tutto. In un RTS classico l'unità è un'estensione del tuo mouse; qui è un'entità autonoma che, al massimo, ti dà retta.

Perché è interessante dal punto di vista tecnico? Perché "creature autonome che vivono per conto loro" è esattamente il tipo di feature che, fatta male, ti distrugge le performance, ti riempie il codice di stato accoppiato e ti rende impossibile testare qualunque cosa. Lumiland è, in larga parte, la storia di come non farsi distruggere da questa ambizione. E comincia da una separazione netta. L'architettura — la simulazione non sa che esiste la grafica

La prima decisione, quella che ha reso possibili tutte le altre, è banale a dirsi e disciplinante a farsi: la simulazione è completamente separata dal rendering.

Nel progetto ci sono cartelle nette. sim/ contiene il mondo, gli orsi, la genetica, la società, la persistenza: è il modello, e non sa nulla di telecamere, mesh o shader. ai/ ha il Direttore (la regìa degli eventi) e la Mente (la cognizione dei singoli). comm/ gestisce dialoghi e voci. Poi ci sono render/, core/, ui/ e tests/. La regola d'oro è una sola: nel renderer non c'è logica di gioco. Il renderer legge lo stato della simulazione e lo disegna. Punto. Non decide chi si accoppia, non sposta nessuno, non sa nemmeno cosa significhi "fame": sa solo che il Lumo numero 42 è in posizione (x, z), ha un certo umore, e va disegnato con un certo ciuffo che brilla.

Sembra una pignoleria architetturale. È invece la cosa che tiene in piedi tutto il resto, per tre motivi concreti.

Primo: lo stato è serializzabile in JSON. Ogni oggetto della simulazione sa convertirsi in un dizionario (todict) e ricostruirsi da un dizionario (fromdict). Il mondo intero — orsi, genomi, colonie, calendario, era evolutiva — è dati puri. Niente nodi della scena, niente riferimenti alla GPU. Questo significa che salvare una partita è serializzare un dizionario; caricarla è il contrario. E significa che il famoso "il mondo continua mentre sei via" non richiede di accendere il motore 3D: si lavora sui dati.

Secondo: si può testare davvero. Quando la logica non dipende dal rendering, puoi farla girare headless — senza finestra, senza GPU. Lumiland ha una batteria di test in GDScript che esercitano la genetica, la simulazione, la navigazione, la persistenza, le menti, l'evoluzione e le colonie. Sono test che fanno andare avanti il mondo per anni-gioco e verificano invarianti: la popolazione non si estingue? I salvataggi tornano identici dopo un round-trip JSON? La navmesh copre l'area attesa? Tutto questo gira in pochi secondi in CI, senza schermo.

Terzo: il rendering si verifica comunque, ma per conto suo. Per controllare che la grafica non sia rotta, niente GPU vera in CI: si fanno screenshot software con Xvfb + Mesa llvmpipe (un display X virtuale e un rasterizzatore via CPU). Bruttini e lenti, ma sufficienti a beccare una regressione visiva — una mesh sparita, un materiale nero — senza dipendere da hardware grafico.

La morale è che la separazione non è purismo: è ciò che rende la simulazione portabile. La stessa logica che disegni su un desktop con scheda video puoi farla girare in un test headless, in un catch-up offline, o dentro un browser su un telefono. È sempre lo stesso modello di dati. Tieni a mente questa idea — "il mondo è dati, la grafica è una lettura" — perché torna ovunque. Far girare un 3D vivo nel browser — i limiti e i trucchi

Ora la parte scomoda. Far girare Godot 4 nel browser è possibile, ma il browser non è un PC da gaming: è una sandbox WebAssembly che parla WebGL2. E WebGL2, in Godot, vuol dire un renderer specifico con regole tutte sue.

Lumiland usa due renderer a seconda di dove gira. Su desktop, dove c'è una GPU vera, il project.godot seleziona Forward+, il pipeline ricco con illuminazione PBR completa. Su web e mobile passa a glcompatibility**, l'equivalente GLES3/WebGL2: il target più portabile che esista, quello che gira davvero ovunque. Stessa simulazione, stesso gioco, due rese diverse.

Il problema è che glcompatibility toglie cose a cui ti eri affezionato. La più dolorosa: niente glow / bloom post-process. Su Forward+ il "ciuffo di luce" dei Lumi sarebbe un materiale emissivo che il post-processing fa sbocciare in un alone morbido. Su compatibility quel passaggio non esiste. E un orsetto di luce* senza luce è solo un orsetto.

Il trucco è vecchio quanto la grafica real-time: se non puoi avere il bloom vero, fingilo con un billboard additivo. Attorno al ciuffo c'è una QuadMesh con materiale in blending additivo, sempre rivolta verso la camera (bearview.gd la chiama esplicitamente fake bloom, ovunque*). Sommando il suo colore a ciò che sta dietro, dà l'illusione di un bagliore che si irradia — su qualunque GPU, anche la più scrausa. È un alone disegnato a mano che imita un effetto di post-processing che non possiamo permetterci. Funziona perché l'occhio non vuole la fisica della luce: vuole vedere qualcosa che brilla.

Il resto del look è scelto per essere bello senza costare troppo: tonemap ACES, ombre direzionali morbide, e un ciclo giorno-notte che pilota in un colpo solo il colore e l'angolo del sole e il cielo. Niente di esoterico, tutto sostenibile in tempo reale dentro una scheda del browser.

C'è un'altra limatura nascosta. Su glcompatibility le GPUParticles non sono supportate**. Quindi pioggia e neve — che vuoi a particelle, mille fiocchi che scendono — non possono girare sulla GPU. Soluzione: CPUParticles3D. Le particelle le calcola la CPU. È un compromesso esplicito (meno fiocchi, calcolo sul processore), ma è l'unico modo di avere meteo a particelle su WebGL2. E siccome il meteo è coerente con la grafica — la coltre nuvolosa smorza il sole, il cielo vira al grigio — anche poche particelle, sopra un cielo che si è incupito, vendono benissimo l'idea della pioggia.

Infine il deploy, dove il diavolo sta nei dettagli. Si esporta in HTML5/WASM. Gli asset (il .pck del progetto, il .wasm del motore, il loader JS) si pre-comprimono in gzip — il .wasm di Godot non è leggero, e mandarlo crudo significa secondi di attesa sul primo caricamento. Lato nginx servono due accortezze poco intuitive: MIME application/wasm sul file .wasm, altrimenti il browser non lo istanzia in streaming. Cache-Control: no-cache. Questo sorprende sempre. Godot esporta con nomi di file fissi (index.wasm, index.pck…): non c'è hash nel nome. Se lasci la cache aggressiva, dopo un aggiornamento gli utenti restano incollati alla versione vecchia perché il browser riusa il file in cache con lo stesso nome. Con no-cache il browser ricontrolla sempre, e l'aggiornamento arriva.

Tre righe di configurazione che, sbagliate, ti fanno credere di aver pubblicato una build nuova mentre il mondo intero vede ancora quella di ieri. Il mondo infinito — streaming a chunk, fog senza confini, navmesh che insegue la camera

Un god-game vuole spazio. Vuole che tu sposti la telecamera oltre la collina e trovi altro mondo, non un muro invisibile o un cartello "fine della mappa". Lumiland prende questo desiderio alla lettera: il mondo è infinito. E un mondo infinito non puoi tenerlo tutto in memoria — devi generarlo a pezzi, attorno a dove stai guardando, e buttare via ciò che non serve.

Il terreno si genera a chunk di 64 metri di lato, ricavati da un rumore (FastNoiseLite). Attorno alla telecamera si tiene una griglia 7×7 di chunk (raggio 3). A ogni frame si controlla in quale chunk è finito il fuoco della camera: se è lo stesso di prima, non si fa nulla; se è cambiato, si generano i chunk nuovi entrati nel raggio e si scaricano quelli usciti. Tutto qui — ma è il "tutto qui" che rende il mondo illimitato a costo costante:

La chiave è la guardia in cima: il lavoro pesante (costruire le mesh dei chunk) avviene solo quando la camera attraversa un confine, non a ogni frame. Per 63 metri di scorrimento non succede niente; al 64°, una manciata di chunk nascono e altrettanti muoiono. E poiché ogni chunk è identificato da una coppia di interi (Vector2i), non c'è un'origine privilegiata né un limite: puoi viaggiare per sempre in qualunque direzione e il sistema continua a indicizzare con coordinate intere che non finiscono mai.

La stessa filosofia governa la fog of war, ed è la mia parte preferita. Niente mappa di dimensione prefissata: la nebbia è un dizionario di celle indicizzate per coordinate intere. Esplori una zona, quella cella si dirada. Non c'è un bordo perché non c'è un array di dimensione fissa: c'è solo l'insieme delle celle che hai scoperto, che cresce indefinitamente man mano che cammini. La minimappa si auto-adatta di conseguenza: invece di rappresentare un rettangolo fisso, calcola i confini di ciò che hai esplorato e si ridimensiona, crescendo insieme al tuo regno. All'inizio è un francobollo intorno alla colonia madre; dopo qualche anno-gioco di esplorazione è un atlante.

L'acqua è risolta con un trucco elegante e a costo zero: invece di tassellare oceani infiniti, è un grande piano che segue la telecamera. Ovunque guardi, l'acqua è già lì sotto, perché si muove con te. Non la generi mai e non la scarichi mai: è una sola superficie che ti accompagna.

E poi c'è la navigazione, che è stata la grana più sottile. I Lumi devono muoversi su un terreno che non esiste finché non lo generi. La soluzione "ortodossa" sarebbe il bake della NavigationMesh con Recast a runtime, mano a mano che i chunk appaiono. Il problema: il bake a runtime di Recast non è affidabile su tutte le piattaforme — e "tutte le piattaforme" include i browser e i telefoni, che sono proprio dove Lumiland deve girare. Affidare la navigazione a un componente che a volte, su certi device, semplicemente non produce una mesh, significa avere Lumi che restano paralizzati su alcuni dispositivi e non su altri. Inaccettabile.

Quindi: niente Recast. La navmesh è costruita proceduralmente a mano — una griglia di poligoni navigabili generata via codice, deterministica e identica ovunque, che si ri-centra seguendo la telecamera esattamente come fa il terreno. Rinuncio alla precisione di un bake che adatterebbe i poligoni alla forma esatta del terreno; in cambio ottengo una navigazione che funziona uguale su desktop, su browser e su mobile, senza sorprese specifiche del device. In un progetto che vive nel browser, la prevedibilità vale più della raffinatezza. Dare una mente ai Lumi — AI cognitiva locale, e perché non un LLM

Arriviamo al cuore. Cosa rende un Lumo "vivo"? La tentazione, nel 2026, è ovvia: collega ogni creatura a un modello linguistico, fagli generare pensieri e battute, e avrai personaggi che parlano come persone. Lumiland fa la scelta opposta, e l'ha fatta in modo consapevole. Niente LLM lato client.

Le ragioni sono concrete, non ideologiche. Primo, la latenza: un mondo con decine di Lumi che chiacchierano in tempo reale non può fermarsi ad aspettare una risposta di rete per ogni battuta. Secondo, i costi: ogni conversazione sarebbe una chiamata a pagamento, moltiplicata per ogni coppia che si incontra, per ogni giocatore, per sempre. Terzo, e dirimente per un gioco che gira nel browser: le chiavi esposte. Qualunque segreto tu metta in un client WASM è, di fatto, pubblico — chiunque apra gli strumenti di sviluppo può leggerlo. Mettere una chiave API in Lumiland significherebbe regalarla. E far girare un modello linguistico dentro WASM, senza chiamate esterne, è impraticabile per peso e prestazioni.

Quindi ogni Lumo ha una mente cognitiva locale e deterministica. Piccola, leggibile, senza rete. Quattro componenti, ciascuno con un ruolo preciso.

Il temperamento è la linea di base emotiva, e viene dal genoma. Un Lumo socievole, coraggioso e sveglio parte tendenzialmente più sereno; uno molto goloso è un filo più lunatico. È una costante della personalità — chi sei "di natura".

L'umore (mood) è una valenza tra −1 e +1: come ti senti adesso. Non è statico, reagisce agli eventi attraverso un modello di appraisal — la stessa idea della psicologia delle emozioni: un evento viene "valutato" e sposta lo stato emotivo. Mangiare lo alza; la pioggia presa senza riparo lo abbassa; la nascita di un figlio è gioia; la morte di un caro è lutto, e colpisce parenti e amici; imparare qualcosa fa bene; un litigio fa male. Cruciale: l'umore decade verso il temperamento. Le emozioni passano. Dopo una giornata storta torni lentamente verso il tuo "tu" di base.

Hai notato il secondo effetto di feel? Oltre a muovere l'umore, lascia una traccia in memoria. È il terzo componente: ogni Lumo ricorda i suoi ultimi eventi (qui ne tiene cinque). Questa memoria non serve a riempire un diario: serve a far parlare i Lumi di cose appena successe. Chi ha appena avuto un cucciolo ha in testa "nascita"; chi ha perso qualcuno ha in testa "lutto".

Il quarto componente sono i legami — un'opinione verso gli altri, che si rafforza o si incrina a ogni interazione e viene "potata" tenendo solo i rapporti più intensi. L'opinione di A verso B non è solo il legame personale: ci si sommano la parentela (un genitore, un figlio, il compagno pesano) e l'appartenenza alla stessa colonia. Da legami che si accumulano nel tempo emergono amicizie del cuore e rivalità, senza che nessuno le abbia scritte a mano.

Il punto da non perdere è questo: niente di tutto ciò è casuale e niente è scriptato. È un sistema dinamico minuscolo — quattro variabili che si influenzano — che produce comportamento credibile in modo deterministico. Lo stesso seed produce la stessa storia, il che è esattamente ciò che ti serve per testare un mondo e per farlo ripartire identico da un salvataggio. Un LLM ti darebbe varietà a costo di imprevedibilità, latenza e dipendenza dalla rete. Questa mente ti dà coerenza, velocità e zero costi — e gira tutta dentro il browser. Cosa ci facciamo? La facciamo parlare. Conversazioni emergenti — dal topic alle voci procedurali

Quando due Lumi si incontrano, succede qualcosa. Ma cosa si dicono non è estratto a caso da una lista: la mente sceglie un topic in base a stato, umore e opinione reciproca. I topic disponibili coprono lo spettro di una vita sociale: saluto, amore, gioco, insegnamento, battibecco, pettegolezzo, meteo, riflessione filosofica, nascita, lutto.

La scelta è una cascata di priorità, leggibile come un albero decisionale:

Leggilo come una scena. Se uno ha appena insegnato qualcosa all'altro, parlano di quello. Se sono compagni, parlano d'amore. Se si detestano (opinione sotto soglia), bisticciano. Se uno ha in memoria una nascita recente, ne parlano con gioia; se ha in memoria un lutto, lo elaborano insieme. Se piove, si lamentano del tempo. Altrimenti, due conoscenti si salutano. Le menti sveglie e serene, in più, ogni tanto si lasciano andare a una riflessione filosofica sul mondo. Il dialogo emerge dall'incrocio di chi sono, come stanno e cosa pensano l'uno dell'altro — context-aware nel senso letterale: dipende dal contesto.

Scelto il topic, il testo lo producono template italiani parametrici. Non frasi fisse: scheletri che si riempiono coi dati reali del momento — i nomi dei due Lumi, la parola d'umore di chi parla ("raggiante", "sereno", "pensieroso", "inquieto", "giù di morale"), e il termine di relazione corretto ("il suo compagno", "un figlio", "un amico del cuore", "un rivale"). La stessa coppia, in giorni diversi e umori diversi, dice cose diverse. È combinatoria, non generazione neurale, ma per un orsetto di luce è più che sufficiente a sembrare vivo.

E le voci? Qui Lumiland fa una cosa che adoro per economia e carattere: non registra audio, lo sintetizza. Ogni voce è una melodia procedurale — onde sinusoidali su una scala pentatonica, generate in un AudioStreamWAV al volo. Pentatonica perché qualunque nota tu peschi suona consonante: impossibile stonare. Il risultato è un linguaggio gorgheggiante, da creature di luce, mai sgradevole. Sopra le voci dei singoli cresce un coro/pad ambientale che si gonfia con la popolazione: più Lumi ci sono, più il mondo "canta". Una colonia piena ha un suo respiro sonoro che un villaggio di quattro non ha — e non hai dovuto registrare nemmeno un campione.

Visivamente, quando due Lumi conversano compare un'icona-fumetto cliccabile sopra di loro. Toccala e leggi la trascrizione di quello che si stanno dicendo. È il ponte tra la simulazione muta e te: la chiacchiera esiste come dato (il topic scelto, i parametri, l'audio), e il fumetto la rende leggibile su richiesta, senza intasare lo schermo di testo. Vuoi origliare? Tocchi. Non vuoi? Vedi solo orsetti che si parlano, e va benissimo così. Genetica ed evoluzione — genoma, ere, costruzioni che crescono

Sotto la personalità c'è la biologia. Ogni Lumo ha un genoma: un insieme di tratti — curiosità, socievolezza, coraggio, intelligenza, gola, taglia, colore e altri. Non sono etichette estetiche: sono leve che muovono il comportamento. La socievolezza alza la probabilità di accoppiarsi e di legare; il coraggio cambia come ci si comporta; l'intelligenza apre la porta alle conversazioni filosofiche e influenza l'apprendimento; perfino la gola sposta il temperamento verso il lunatico. I tratti incidono su comportamento, dialoghi, longevità e fertilità.

La riproduzione è una funzione, breed(), che mescola i genomi dei genitori per produrre quello del figlio — con mutazioni e ricombinazione. E ogni tanto, raramente, nasce una "leggenda": un Lumo con una combinazione di tratti fuori scala. Sono gli individui che danno una spina dorsale narrativa a una partita: il fondatore eccezionale, il capostipite che la colonia ricorderà. Niente di tutto questo è hard-coded come "personaggio": è il genoma che, ogni tanto, tira fuori un asso.

Sopra la biologia dei singoli c'è l'evoluzione della società, scandita per ere: Risveglio → Prime Capanne → Villaggio → Cultura → Civiltà della Luce. Non sono livelli che scegli: sono soglie calcolate dal progresso accumulato e annunciate come traguardi quando le superi. Vedere comparire "La tua gente è entrata nell'era del Villaggio" dopo qualche anno-gioco di crescita organica dà esattamente quella soddisfazione da god-game di guardare una civiltà alzarsi da sola.

E le ere non sono solo etichette: cambiano cosa i Lumi possono fare. Le costruzioni non si fermano mai. Le case e i campi salgono di livello nel tempo; ogni nuova era sblocca edifici nuovi — un deposito, un totem di luce che dona "Fede". I Lumi liberi, senza che tu glielo dica, costruiscono, migliorano, coltivano. Torni dopo qualche giornata reale e il villaggio di capanne che avevi lasciato è diventato qualcos'altro: tetti migliori, campi nuovi, un totem che prima non c'era. Il progresso è visibile col passare degli anni, ed è progresso loro. Tu, al massimo, hai dato una spinta nella direzione giusta — perché, ancora una volta, influenzi, non comandi.

C'è un cliffhanger nascosto in tutto questo, però. Una civiltà che cresce ha bisogno di una popolazione che cresca. E far crescere una popolazione di individui autonomi, in modo che non muoia né esploda, si è rivelato il problema più ostico dell'intero progetto. È ora di parlarne. L'ecologia che non doveva morire — estinzione contro esplosione

Ti racconto il bug più interessante che ho incontrato, perché è il genere di bug che ti insegna come funziona davvero un sistema vivo.

Versione iniziale: quattro fondatori, RNG randomizzato a ogni partita, e i Lumi che si riproducono incontrandosi. Sembra ragionevole. Avvii dieci partite e ottieni dieci storie completamente diverse — il che, all'inizio, sembra perfino una feature. Poi guardi meglio e ti accorgi che la varianza non è "sapore": è fuori controllo. Alcune partite finiscono in estinzione; altre in esplosione a centinaia di Lumi. E i due fallimenti hanno cause opposte, il che è la cosa più insidiosa: ogni volta che spingevo per risolverne uno, peggioravo l'altro.

L'estinzione arrivava in due modi, entrambi istruttivi. Il primo: i quattro fondatori avevano età simili, quindi una coorte di coetanei moriva tutta insieme — un'intera generazione che si spegne nello stesso arco di tempo, e se i figli non erano ancora abbastanza, fine. Il secondo, ancora più beffardo: un fondatore che muore di fame inseguendo cibo lontano. Con appena quattro individui, perderne uno per una pessima decisione di pathfinding poteva azzerare la riproduzione — magari era l'unico maschio, o l'unica femmina. Quattro è un numero crudele: non perdona nessun errore.

L'esplosione era il problema speculare: alza un po' troppo la fertilità per evitare l'estinzione, e all'improvviso la popolazione non ha nessun freno. Cresce esponenzialmente finché il browser non comincia a singhiozzare. Due bug di bilanciamento, opposti, che si rincontravano in un equilibrio impossibile da centrare con una singola manopola.

La soluzione non è stata una correzione: è stato un insieme di forze combinate che, messe insieme, producono una curva a S — crescita lenta all'inizio, rapida in mezzo, che si appiattisce verso un tetto. La curva a S è la forma con cui crescono le popolazioni reali, e ottenerla qui ha richiesto cinque ingredienti.

Primo, le cucciolate. Ogni accoppiamento non genera un figlio, ma una nidiata: dai 2 ai 5 cuccioli dalla stessa coppia. Questo dà robustezza fin dai pochi fondatori — un singolo accoppiamento riuscito ripopola davvero, invece di aggiungere un'unità alla volta che la sfortuna può cancellare.

Secondo, la longevità individuale variabile. Qui c'è una piccola lezione professionale: la varianza nella durata della vita era documentata nei commenti del codice ma non era mai stata implementata davvero. I Lumi morivano con tempi troppo sincronizzati — ecco perché le coorti si spegnevano insieme. Dare a ciascuno una longevità diversa spalma le morti nel tempo: niente più generazioni che collassano in blocco. (Promemoria per tutti: un commento che descrive un comportamento non è quel comportamento. Fidati del codice, non delle buone intenzioni scritte sopra.)

Terzo, le colonie coese. Invece di lasciare i Lumi disperdersi su un mondo infinito — dove due potenziali partner potrebbero non incontrarsi mai — li si fa gravitare attorno al loro insediamento. Stando vicini, si incontrano di continuo, e quindi si riproducono di continuo. La coesione sociale è, di fatto, un meccanismo demografico: l'amore funziona solo se le persone sono nello stesso posto.

Quarto e quinto, i due freni e le due reti di sicurezza. Una capacità portante morbida: avvicinandosi al tetto, le nascite rallentano da sole. E una spinta anti-estinzione: quando si è pochissimi, la fertilità schizza in alto perché la specie deve ripartire. E se proprio manca uno dei due sessi — la condizione da cui non si torna — c'è l'ultima rete: l'immigrazione di un Lumo errante, non imparentato, che si unisce alla colonia. Il codice che governa questi due estremi è sorprendentemente piccolo:

Guarda fertility: sotto la soglia di salvataggio restituisce un moltiplicatore alto e secco — ripartite, costi quel che costi. Sopra, restituisce 1 − pop/tetto con un minimo di 0.05: più ci si avvicina alla capacità portante, più la fertilità si avvicina a zero, ma senza mai annullarsi del tutto. È letteralmente la pendenza che dà la forma a S: spinta forte in basso, freno crescente in alto. La litter_size rincara: nidiate generose quando si è in pochi, un solo cucciolo quando si è al tetto. Le due funzioni insieme producono un'ecologia che si auto-corregge da entrambi gli estremi.

Il risultato, verificato su simulazioni di cinque anni-gioco: niente estinzioni, niente esplosioni, niente lag. La popolazione respira — sale, si stabilizza, oscilla un po' come fa un'ecologia viva — ma non scappa né muore. E qui arriva la nota di onestà tecnica, perché è importante: la popolazione oscilla davvero, non si inchioda su un numero. Non è un bug, è la natura del sistema; ma è anche il motivo per cui esiste un tetto morbido attorno al centinaio di Lumi vivi (la SOFTCAP qui vale 95). Quel limite non è arbitrario: oltre il centinaio abbondante di creature contemporaneamente vive e simulate*, la fluidità nel browser comincia a soffrire. Ho scelto un'ecologia che canta entro un confine sostenibile, invece di un mondo illimitato che ingolfa il telefono. È un compromesso, ed è giusto chiamarlo col suo nome. Colonie indipendenti — migrazione, nomi, identità dei villaggi

Una popolazione coesa risolve la demografia, ma ne crea un'altra di esigenza: se tutti restano stretti attorno a un solo insediamento, dopo un po' è affollato e il mondo infinito resta vuoto. Lumiland risolve facendo gemmare le colonie.

Ogni insediamento è una colonia con un nome proprio. La prima, la colonia madre, si chiama Prima Luce. Poi vengono Valdoro, Lumevia, Stellaria, Auroria, Fonteviva, Boscoluce… un repertorio di nomi luminosi che danno a ogni villaggio una targa, non un numero. Quando una colonia diventa troppo affollata, parte una pattuglia verso un nuovo sito — e qui c'è il dettaglio che fa la differenza tra una migrazione e una condanna: la pattuglia ha una coppia maschio + femmina garantita. Una colonia nuova fondata da soli individui dello stesso sesso sarebbe nata morta. Garantendo entrambi i sessi nel gruppo migrante, ogni nuovo villaggio nasce demograficamente vitale. È la lezione dei quattro fondatori applicata ogni volta che si espande: non lasciare mai che la fondazione dipenda dalla fortuna.

I nati in una colonia ne ereditano l'appartenenza, e l'appartenenza pesa sulle relazioni — i Lumi della stessa colonia partono con un po' di simpatia reciproca in più, perché l'opinione include il "siamo dello stesso popolo". Così le colonie non sono solo puntini sulla mappa: sono identità sociali. Hanno un nome, una storia, dei membri che si riconoscono come concittadini.

E tu le vedi, queste identità, in tre punti dell'interfaccia. Sulla minimappa, dove ogni colonia ha la sua etichetta. Nella scheda del singolo Lumo, che riporta "Colonia di X" come fosse la sua provenienza anagrafica. E nel momento epico: alla nascita di un nuovo insediamento compare un popup di fondazione — "Nasce un popolo" — che ti annuncia il battesimo. Guardare Prima Luce diventare cinque, sei, sette villaggi sparsi su un mondo che si dirada dalla loro frontiera è, alla fine, il god-game che volevo: una civiltà che si espande da sola, e tu che assisti alla sua geografia che cresce. Tempo, meteo e UX — calendario, velocità, popup, autosave, progressi offline

Un mondo che vive nel tempo ha bisogno che il tempo sia leggibile. Lumiland tiene un calendario vero — giorni, mesi (con i loro nomi) e anni — così che gli eventi abbiano una data e una storia abbia una cronologia. "È nato nell'autunno del terzo anno" significa qualcosa, perché c'è un autunno e c'è un terzo anno.

Sopra il calendario scorre il ciclo giorno-notte, e sopra ancora il meteo, coerente con la grafica — che è la cosa che lo rende convincente. Non è un overlay di pioggia appiccicato a un cielo immutato: quando arriva il maltempo, la coltre nuvolosa smorza il sole e vira il cielo al grigio; con la neve il cielo va verso un bianco freddo. Solo a quel punto cadono le particelle (le CPUParticles3D di cui dicevamo). L'illuminazione e l'atmosfera cambiano insieme, e l'occhio ci crede perché tutto vira nella stessa direzione contemporaneamente. Il meteo non è un effetto: è uno stato del mondo che tocca luce, cielo e particelle in un colpo solo.

E poiché un mondo vivo a velocità reale può essere lento da guardare, c'è il controllo della velocità del tempo: pausa, 1×, 2×, 4×, 8×. Vuoi seguire una conversazione momento per momento? 1×. Vuoi vedere una colonia crescere e gemmare nell'arco di una sessione? 8× e guardi gli anni scorrere. È il telecomando che un god-game deve avere: il tempo è tuo.

Poi c'è il capitolo che, sul web, separa un prototipo da un prodotto: la persistenza. C'è una registrazione/profilo locale per ogni giocatore. E c'è un autosave robusto, ed è robusto per una ragione molto pratica e molto web: su un browser chiudere la scheda non sempre dà al gioco il tempo di salvare. Non puoi fidarti del classico "salva all'uscita", perché l'uscita può essere brutale. Quindi si salva periodicamente e alla perdita del focus — appena cambi scheda o riduci la finestra, il mondo si mette al sicuro. Difesa in profondità contro il modo in cui la gente usa davvero un browser: a scatti, con venti schede aperte, chiudendo senza pensarci.

Ed eccoci tornati all'inizio — al biglietto del mattino dopo. Quel "il mondo continua mentre sei via" è il catch-up offline. Quando rientri, il gioco calcola quanto tempo reale è passato dall'ultimo salvataggio e fa avanzare la simulazione di conseguenza. E può farlo senza accendere la grafica proprio grazie alla scelta del Capitolo 3: la simulazione è dati, gira headless. Mentre eri via non c'era nessuna GPU a disegnare nulla — c'era solo il modello che macinava giorni-gioco. Quel recupero alimenta un diario/cronaca persistente che ti racconta cosa è accaduto: nascite, morti, migrazioni, fondazioni. Il biglietto del mattino non è una finzione narrativa appiccicata sopra: è la trascrizione fedele di una simulazione che ha davvero girato senza di te.

Il resto dell'esperienza segue la stessa filosofia di tatto. I messaggi del gioco sono popup organizzati a schede, così non ti seppelliscono; i pannelli informativi sono mobili e ancorabili, te li sistemi come preferisci. E il gesto del dio — selezionare gruppi di Lumi e spostarli, anche con le dita su touch — c'è, funziona, ma resta un suggerimento. Influenzi più di quanto comandi. È la stessa frase del primo capitolo, ma adesso sai quanto lavoro ci vuole sotto per renderla vera: serve che le creature abbiano una vita abbastanza solida da poterti, gentilmente, dire di no. Lezioni apprese, limiti onesti e cosa c'è dopo

Costruire Lumiland mi ha lasciato alcune convinzioni che porterò nel prossimo progetto.

La separazione paga, sempre. "La simulazione non sa che esiste la grafica" sembra disciplina fine a sé stessa finché non ti accorgi che è ciò che rende possibili i test headless, il catch-up offline e la portabilità su browser e mobile. Una regola architetturale severa al momento giusto compra libertà per mesi.

A volte la scelta tecnologicamente "umile" è quella giusta. Niente LLM lato client; niente bake della navmesh a runtime; finto bloom con un billboard additivo invece del post-processing; voci sintetizzate al posto di file audio. Ognuna è una rinuncia consapevole a qualcosa di più potente o più raffinato in cambio di prevedibilità, costo zero e funzionamento su qualunque dispositivo. In un gioco che deve girare nel browser di un telefono qualunque, gira ovunque batte è impressionante su una macchina.

I sistemi viventi vanno bilanciati con più forze deboli, non con una manopola forte. L'ecologia non si è stabilizzata quando ho trovato il numero magico: si è stabilizzata quando cinque meccanismi modesti — cucciolate, longevità variabile, coesione, tetto morbido, rete anti-estinzione — hanno cominciato a spingersi a vicenda verso una curva a S. E il bug della longevità "documentata ma mai implementata" è il promemoria più onesto del mestiere: leggi il codice, non i commenti.

E i limiti, detti senza girarci intorno. Non c'è alcun LLM: le conversazioni sono ricche e context-aware, ma restano combinatorie — un orsetto di luce non ti sorprenderà mai con un pensiero davvero nuovo, e va bene così per quello che è. La popolazione oscilla come un'ecologia viva: non aspettarti un numero fermo, aspettati un respiro. E c'è un soffitto pratico: attorno ai 100–120 Lumi vivi simulati contemporaneamente, la fluidità nel browser comincia a calare, ed è il motivo per cui la capacità portante è quella che è. Il mondo è infinito; la quantità di vita contemporanea dentro di esso, no.

E dopo? Le fondamenta sono pensate per crescere proprio nei punti dove ho dovuto fermarmi. Le ere finiscono oggi a "Civiltà della Luce", ma la scala di evoluzione è aperta. Le costruzioni continuano a salire di livello: c'è spazio per nuovi edifici e nuovi ruoli sociali man mano che le ere si allungano. Le menti dei Lumi sono quattro variabili che si influenzano — e quel modello si può arricchire restando deterministico e locale, senza tradire la scelta anti-LLM che tiene tutto leggero. E poiché lo stato è solo dati serializzabili in JSON, ogni futuro — più colonie, più storia, più mondo — non deve combattere contro l'architettura: ci scivola dentro.

Per ora, però, mi basta una cosa. Chiudo la scheda. So che domani mattina, da qualche parte tra Prima Luce e Boscoluce, sarà nato qualcuno, qualcun altro se ne sarà andato, e un piccolo popolo di luce avrà continuato a vivere — senza di me, e proprio per questo davvero vivo.

---

Seguimi sui Social

Resta aggiornato sui miei progetti tech e gamedev: LinkedIn — Articoli professionali e dietro le quinte Facebook — Community e aggiornamenti

Prova Lumiland nel browser: antoniopedoto.it/lumiland

Antonio Pedoto - IT System Administrator & Full Stack Developer

25+ anni di esperienza in ambienti enterprise e high-security. Articoli tecnici, progetti e approfondimenti su System Administration, Full Stack Development, Cybersecurity e DevOps.

Ultimi articoli

Scopri gli ultimi articoli tecnici su Linux, DevOps, Cybersecurity, sviluppo web e molto altro.

Vedi tutti gli articoli

Progetti

Esplora i progetti realizzati nel campo dell'IT e dello sviluppo software.

Vedi tutti i progetti

Chi sono

Sono Antonio Pedoto e possiedo oltre 25 anni di esperienza professionale maturata in ambienti enterprise e ad alta sicurezza. Mi occupo principalmente di System Administration, Full Stack Development, Cybersecurity e DevOps.

Scopri di più

Contatti

Email: antonio.pedoto@gmail.com

Sede: Ferrara, Emilia-Romagna, Italia

LinkedIn: linkedin.com/in/antoniopedoto

GitHub: github.com/antoniopedoto