← Retour

Strates, Changelog

Historique des versions

Historique des itérations du proto. Les anciens protos 1 à 5 ont été fusionnés dans public/strates/editor/.

Emplacements de port predetermines sur cotes oceaniques (Lot B) 2026-06-09

Refonte de la mecanique de placement du port. Le port ne peut plus etre pose n importe ou.

Detection ocean vs lac (flood-fill)

  • Nouveau module editor/modules/port-slots.js.
  • Flood-fill BFS depuis toutes les cellules d eau situees sur les bords de la grille (x=0, x=GRID-1, z=0, z=GRID-1).
  • Toutes les cellules d eau atteintes = ocean. Les non-atteintes = lacs interieurs (ignores).
  • Candidats port = cellules de terre basse adjacentes a au moins une cellule ocean.
  • Selection de 2-3 slots bien repartis (distance min 20 cases, preference sable > herbe > foret).
  • Stockage dans state.portSlots et state.portSlotMarkers.

Marqueurs pontons fantomes

  • Pour chaque slot libre et revele par le fog : mesh translucide (ponton bois opacity 0.55 + bouee bleue opacity 0.45).
  • buildPortSlotMarkers() appele au boot (apres buildTerrain()) et apres chaque chargement de save.
  • refreshPortSlotMarkersVisibility() appele chaque seconde dans la boucle lente pour afficher les marqueurs au fur et a mesure que le fog se revele.
  • Le marqueur disparait quand un port est construit sur le slot (markSlotOccupied()).

Validation placement port

  • Le clic place-port verifie que la cellule ciblee est sur ou pres d un slot libre (tolerance 3 cases).

Sinon : toast "Le port doit etre construit sur un emplacement cotier (ponton)."

  • Verification qu au moins un colon a skillLevel('building') >= 10.

Sinon : toast "Necessite un batisseur niveau 10."

  • Si valide : addPort() normal puis markSlotOccupied().

Persistence

  • serializeSnapshot inclut portSlots (positions + etat occupe/libre).
  • applySnapshot restaure les slots depuis la save. Si absent (vieille save) : recompute et marque les ports charges comme occupes (retrocompat transparente).
  • clearEverything nettoie portSlots et portSlotMarkers.
Iles organiques par biome (Lot B, phase 4+5 maritime) 2026-06-08

Generation procedurale d iles aux contours irréguliers selon le biome de destination. Remplace le terrain carré vide généré lors du premier voyage vers une ile coloniale.

gamedata.js - chargement biomes.json

  • biomes.json chargé au boot dans loadGameData() aux côtés des autres JSON.
  • Variable BIOMES_DATA exportée.
  • Accesseur getBiome(biomeId) : retourne la config d un biome (palette, terrain, vegetation, resources) ou null si introuvable.

island-gen.js (nouveau module)

  • makeIslandHeightmap(biomeCfg, seed) : calcule une heightmap d ile organique.
  • Masque d ile par distance normalisée au centre + bruit angulaire fbm proportionnel à coastRoughness : crée des péninsules (bruit positif) et des baies (bruit négatif).
  • Zones de terre : relief fbm haute fréquence modulé par mountainLevel et par la distance au centre (intérieur plus montagneux, bords bas = plages).
  • Zones hors masque : hauteur 0 = océan.
  • buildIslandTerrain(biomeId, seed) : construit l InstancedMesh du terrain ile en utilisant la palette du biome (state.currentPalette). Appelle computeFertileCells et repaintCellSurface comme buildTerrain.
  • restoreIslandPalette(biomeId) : restaure state.currentPalette depuis la config biome. Appelée au chargement d un snapshot d ile coloniale.

terrain.js - palette courante

  • colorForLayer(biome, y, top) lit maintenant state.currentPalette || COL : si une palette biome est active (iles coloniales), les voxels prennent les couleurs du biome ; sinon les couleurs COL (home inchangé).
  • buildTerrain() (home uniquement) remet state.currentPalette = null avant la génération pour garantir le rendu tempéré inchangé.

state.js

  • state.currentPalette : null : palette courante du terrain (null = COL par défaut).

worldgen.js - peuplement par biome

  • populateIsland(biomeId, seed) : peuple une ile coloniale selon biomes.json.
  • Arbres : treeDensity * 120 en biome forest (clusters), treeDensity * 30 en herbe.
  • Rochers : 60 en biome rock/snow.
  • Filons : oreRichness * 20 filons choisis parmi oreTypes du biome.
  • Buissons : proportionnels à treeDensity.
  • Cerfs : rares, proportionnels à treeDensity.
  • Pas de colons ni de bâtiments : l ile est vierge.

persistence.js

  • serializeSnapshot stocke terrain.islandBiomeId (biome de l ile active au moment de la sauvegarde).
  • applySnapshot restaure la palette via restoreIslandPalette avant rebuildTerrainFromState, pour que les saves d iles coloniales retrouvent leurs couleurs biome au rechargement.
  • clearEverything remet state.currentPalette = null.

map-manager.js

  • _generateEmptyIsland(biomeId, seed) réduit à deux appels : buildIslandTerrain + populateIsland. La logique de génération est entièrement déléguée aux modules spécialisés.
  • switchToMap passe le biome et le seed depuis targetEntry lors de la première visite.

Résultat

  • Ile tropicale : vert intense, jungle dense (treeDensity 2.2), plages larges (beachWidth 6), contours irréguliers, filons cuivre et or.
  • Ile glaciaire : blanc/gris-bleu, quasi aucun arbre (treeDensity 0.3), beaucoup de filons rares (oreRichness 1.8 : fer, argent, améthyste).
  • Ile volcanique : sombre, contours très irréguliers (coastRoughness 0.5), relief prononcé (mountainLevel 0.9), minerais abondants.
  • Home (biome tempere) : rendu inchangé, buildTerrain + populateDefaultScene comme avant.
Infrastructure multi-iles (Lot B, phase 2 maritime) 2026-06-08

Chantier technique posant les fondations du mode multi-cartes style Anno : une ile chargee a la fois, les autres dormantes sous forme de snapshots. Pas d UI dans ce sprint.

state.js - registre des iles

  • state.currentIslandId (string, defaut 'home') : identifiant de l ile active.
  • state.islands (objet id -> { id, name, biome, seed, snapshot, discovered }) : registre de toutes les iles connues. L ile de depart 'home' (Ile principale, biome tempere) est pre-initialisee.

module map-manager.js (nouveau)

  • getCurrentIslandId() / getIsland(id) / listIslands() : accesseurs du registre.
  • registerIsland(id, { name, biome, seed }) : cree une entree vierge (snapshot null) si absente.
  • switchToMap(targetId) : coeur du systeme. Sauvegarde l ile courante (serializeSnapshot), charge la cible (applySnapshot si snapshot present, sinon genere un terrain vierge avec arbres/rochers/buissons/cerfs sans colons via _generateEmptyIsland), met a jour state.currentIslandId, recentre la camera, emet strates:islandChanged.
  • Annulation des modes actifs (movingBuilding, toolState) avant le switch pour eviter un etat corrompu.
  • window.switchToMap et window.registerIsland exposes pour test depuis la console navigateur.

persistence.js - save/load multi-iles

  • serializeSnapshot exporte maintenant currentIslandId et islands (avec snapshots dormants des autres iles, sans le snapshot de l ile active qui est le contenu serialise courant).
  • applySnapshot restaure state.islands et state.currentIslandId en fin de restauration.
  • Retrocompat : une save sans islands charge normalement (ile unique 'home').
  • clearEverything remet le registre a l etat initial (ile 'home' seule, snapshot null).
  • serializeSnapshot, applySnapshot, clearEverything sont desormais exportees (etaient privees) pour consommation par map-manager.js.

main.js

  • Import de switchToMap et registerIsland depuis map-manager.js (declenche l exposition sur window).
Mode nuit enrichi (torches colons, fenêtres age 3, tour de flamme) 2026-06-08

Session centrée sur l'ambiance nocturne. La nuit reste sombre par design en âge 1 et 2, mais les colons et les bâtiments contribuent désormais à l'éclairage.

Torches colons

  • Tous les colons non-explorateurs portent une torche discrète la nuit dès qu'un foyer est construit : PointLight 0xff8844, intensité 0.9, portée 4, vacillement animé.
  • Les explorateurs conservent leur torche plus puissante inchangée (intensité 1.5, portée 6).
  • Condition : state.isNight && state.foyers.length > 0 && profession !== 'explorateur'.

Fenêtres éclairées (age 3)

  • Manoir et Longhouse ont maintenant des fenêtres procedurales (MeshStandardMaterial emissif 0xffa040).
  • Les fenêtres s'allument la nuit via setNightLights(isNight) et s'éteignent le jour (emissiveIntensity 0).
  • Toggle câblé dans daynight.js au moment du basculement jour/nuit.

Tour de flamme (nouveau bâtiment age 3)

  • Bâtiment tour-flamme 2x2 : colonne de pierre + brasero en fer + flamme visible la nuit uniquement.
  • PointLight 0xff7020, intensité 5, portée 12 (éclaire une large zone du village).
  • Coût : 12 pierre, 5 bois. Pas de tech requise, disponible dès l'age 3.
  • Entièrement câblé : ghost preview 2x2, placement, save/load, démolition.
  • La flamme et la lumière sont pilotées par setNightLights() au même titre que les fenêtres.
Session récapitulatif (Âge 3 Médiéval + polish L1-L6 + UI/UX) 2026-06-07

Session dense : 77 commits sur la journée. Couvre la finalisation du contenu Âge 3 Médiéval, six lots de polish, une refonte actionbar et plusieurs fixes infra.

Âge 3 Médiéval (vague 1, data + engine + UI)

  • 7 techs débloquées : axe-iron, arrow-iron, windmill, bakery, market, tavern, library, plus gouvernance, navigation, urbanisme et les 4 techs nocturnes.
  • 5 bâtiments de production : moulin, boulangerie, marché, taverne, bibliothèque (visuels GLB ou voxel procedural, footprint 4x4).
  • Château fort : merveille de passage vers l'Âge 4 Renaissance, 3 stages de construction avec coûts croissants (bois, pierre, fer, bronze).
  • 3 métiers : meunier (milling), boulanger (baking), marchand (trading) avec gainSkillXp par cycle.
  • Chaîne de production : wheat -> flour (moulin, 6s) -> bread (boulangerie, 8s). Le pain rassasie 60 pts (saturation 3 x20).
  • Mode test ?mode=medieval-test : 12 colons, bâtiments médiévaux posés, techs Âge 3 débloquées, stocks pleins.
  • Bibliothèque : bonus recherche x1.5 passif (une seule suffit).
  • Rebrand Âge 3 : renommé "Médiéval" (était "Fer").

L1 - Visual Refresh (GLB médiéval)

  • Manifest GLB médiéval : castle, windmill, market, big-house avec alternance a/b.
  • Scale x2 sur tous les GLB médiévaux (castle, windmill, market, big-house).
  • Ghost preview placement : mesh vert/rouge selon validité de la cellule cible, module placement-preview.js.

L2 - Techs Nocturnes

  • 4 techs nocturnes (astrologie, alchimie, herboristerie-nocturne, meditation) avec cost.night dans techtree.json.
  • Effet astrologie : +20% rendement des champs cultivés avec fermier actif.
  • Effet alchimie : recette forgeron 5 fer -> 1 argent (silver), priorité bronze si les deux sont possibles.
  • Effet méditation : +0.5 pts de recherche par minute, passif global, sans bâtiment requis.
  • Infirmerie (herboristerie) : bâtiment 2x2, heal +1 HP/5s sur les colons dans un rayon Manhattan 6.
  • Badge lune sur les techs nocturnes dans le tech tree.
  • Ressource silver ajoutée dans resources.json.

L3 - Storage Hut

  • Bâtiment storage-hut 4x4, débloqué par tech granary.
  • Panel de choix de ressource assignable (une par hutte, non cumulable sur la même ressource).
  • Capacité de base 100, +400 par hutte assignée à la ressource (lu depuis buildings.json#provides.resourceCapBoost).
  • enforceResourceCaps() plafonne toutes les state.resources chaque seconde.

L4 - Port Maritime

  • Bâtiment port-lvl1 (4x4) upgradable en port-lvl2 via tech navigation.
  • Ressource fish (poisson, saturation 2) ajoutée dans resources.json.
  • GLB port-lvl1/port-lvl2, persist avec level et upgradingUntil.
  • Panel port avec modes idle, en cours d'upgrade, lvl2 construit.

L5 - Mairie (Town Hall)

  • Big-house upgradable en town-hall via tech gouvernance (coût wood:50, stone:40, iron:5).
  • +5 pop max par town-hall construite (lu depuis buildings.json#provides.populationCapBonus).
  • GLB Tower House, fallback voxel tour + dome.
  • Panel town-hall avec modes big-house idle, upgrading, town-hall terminée.

L6 - Champ Légumes

  • Bâtiment vegetable-farm 2x2, stages Farm Dirt (semis) -> Crops (mature).
  • Ressource vegetables (saturation 4, 80 pts de faim).
  • Fermier : états VEGETABLE_PLANTING (3s) et VEGETABLE_HARVESTING (3s, +3 légumes). Maturation via growDuration JSON.
  • Panel vegetable-farm avec barre de maturation.

UI / UX

  • Bandeau actionbar plein largeur 2 niveaux (CSS Grid, HTML refonte).
  • Sous-onglets catégories dans l'onglet Construire : Habitat, Production, Stockage, Civil, Maritime, Voirie, Merveilles.
  • Bouton DÉPLACER bâtiment (tech urbanisme) : 19 types déplaçables, coût 5 bois, ghost preview vert/rouge.
  • Fix clignotement bouton château (lock/visible au lieu de hide/show).
  • Fix stabilisation largeur boutons actionbar (anti-saccades).
  • Fix centrage bouton NAVIGUER dans le bandeau horizontal.
  • Refonte rédactionnelle Guide du Bâtisseur : style narratif, sections Bronze et Médiéval ajoutées.
  • Fix em dash retiré du Guide du Bâtisseur.

Infra

  • Compression localStorage saves : gzip via CompressionStream (gain estimé ~70%), saveGame rendu async.
  • Refonte footprints : bâtiments Âge 3 en 4x4, bâtiments Âges 1-2 en 2x2 minimum.
  • Système prédispositions : 47 fiches lore par prénom dans skill-caps.json, cap max par skill, étoiles avec plafond dans charsheet.
  • Tech urbanisme ajoutée (Âge 3), débloque l'outil DÉPLACER.
  • Champ category complété sur tous les bâtiments (7 catégories).

Fixes

  • fix(engine): import manquant techUnlocked dans main.js : corrige le freeze du tick astrologie et méditation.
  • feat(data): ressource silver manquante dans resources.json (était référencée par pick-iron sans entrée).
  • Fix CSS em dash dans le Guide du Bâtisseur (règle CLAUDE.md).
Lot B, engine deplacement batiment (move-building) 2026-06-07
  • feat(engine): state.movingBuilding pour le mode deplacement batiment : nouveau champ state.movingBuilding = null dans state.js. Quand actif, contient { entry, type, originX, originZ, def }.
  • feat(engine): findBuildingTypeAndArray startMoveBuilding confirmMoveBuilding cancelMoveBuilding : dans placements.js, table MOVABLE_TYPES listant 19 types de batiments deplacables avec leur footprint (w, h) et offset de positionnement (cx, cz). findBuildingTypeAndArray(entry) retrouve la definition d un batiment dans ses tableaux d etat. startMoveBuilding(entry, type) memorise la position d origine, rend le mesh semi-transparent (opacity 0.45), set state.movingBuilding. cancelMoveBuilding() remet le mesh a sa position d origine, restaure l opacite 1.0, reset state.movingBuilding. confirmMoveBuilding(targetX, targetZ) verifie le footprint cible (cellules libres en excluant les cellules d origine, fog revele, terrain hors eau), deduit 5 wood, met a jour entry.x/z, repositionne le group, restaure l opacite. Retourne false si placement invalide ou bois insuffisant. Non deplacables : cairn, ziggurat, castle, tuiles sol.
  • feat(engine): showMoveGhost hideMoveGhost pour preview deplacement batiment : dans placement-preview.js, ghost BoxGeometry dont les dimensions (w, d, h) sont lues depuis le def du batiment en main. Vert si placement valide (cellules libres en excluant l origine, fog revele, 5 bois disponibles), rouge sinon. Mesh cree a la demande et reutilise tant que le footprint ne change pas. hideMoveGhost() masque le mesh sans le detruire.
  • feat(engine): interaction move-building clic batiment ghost pointermove Escape save : dans interaction.js, import de startMoveBuilding/confirmMoveBuilding/cancelMoveBuilding/findBuildingTypeAndArray (placements) et showMoveGhost/hideMoveGhost (placement-preview). setTool annule tout deplacement actif sauf si on passe a move-building. refreshToolButtons gate le bouton move-building sur la tech urbanisme. Handler Escape : annule le deplacement actif en premier (avant tout autre mode). Handler pointermove : quand state.movingBuilding != null, appelle showMoveGhost. Handler pointerup : si state.movingBuilding != null -> confirmMoveBuilding ; si tool move-building -> startMoveBuilding sur le batiment clique (ou toast si non deplacable). resetAllToNav et exitAllModesAndEnter annulent le deplacement actif. Bouton save et event beforeunload annulent aussi le deplacement pour eviter un etat corrompu en save.
Lot B, engine Sprint L6 Legumes (vegetable-farm) 2026-06-07
  • feat(data): vegetable-farm batiment 2x2 + saturation 4 sur vegetables : ajout du batiment vegetable-farm dans buildings.json (age 2, footprint 2x2, cost wood:5 stone:3, buildTime 20s, requires tech agriculture + fermier, growDuration 10s, harvestYield 3). Mise a jour de la ressource vegetables dans resources.json : ajout de saturation: 4, suppression du flag placeholder.
  • feat(engine): crops GLB manifest + CROPS_GLB_SCALE : ajout de 'crops': 'Crops.glb' dans le MANIFEST de glb-cache.js et constante CROPS_GLB_SCALE = 0.5 coherente avec FARM_GLB_SCALE.
  • feat(engine): state.vegetableFarms + vegetableFarmNextId + resources.vegetables : nouveau tableau state.vegetableFarms, compteur state.vegetableFarmNextId dans state.js. state.resources.vegetables initialise a 0.
  • feat(engine): addVegetableFarm 2x2 GLB+fallback, swapVegetableFarmMesh, addVegetableFarmFromSave, isCellOccupied etendu : dans placements.js, addVegetableFarm(gx, gz) verifie biome grass/forest, footprint 2x2 libre, place le mesh GLB farm-dirt (stage semis initial). swapVegetableFarmMesh(entry, stage) retire le mesh courant et cree un nouveau mesh GLB : farm-dirt pour semis, crops pour mature. addVegetableFarmFromSave(data) restaure depuis la sauvegarde. Helpers findVegetableFarmById, releaseFromVegetableFarms exportes. isCellOccupied etendu footprint 2x2. clearAllPlacements couvre vegetableFarms.
  • feat(engine): cycle fermier vegetable-farm planting/harvesting, VEGETABLE_PLANTING et VEGETABLE_HARVESTING : dans colonist.js, le fermier (isFarmerActive) cible en IDLE les potagers matures (recolte) ou semis sans plantedAt (plantation). Les etats VEGETABLE_PLANTING (3s, pose plantedAt) et VEGETABLE_HARVESTING (3s, +3 legumes, swap mesh semis, reset plantedAt) sont ajoutes. Nettoyage de assignedVegetableFarmId sur changement de metier et dans dispose(). _hasNoTaskForProfession etendu pour inclure les potagers. vegetables ajoute dans tryEatMeatFromStocks (satiete 80pts via saturation 4).
  • feat(engine): tick maturation vegetable-farm semis->mature apres growDuration JSON : dans main.js, boucle lente (1s) : pour chaque potager en stage semis avec plantedAt non null, si Date.now() - plantedAt >= growDuration * 1000 (lu depuis buildings.json via getBuildingById), appel swapVegetableFarmMesh(vf, 'mature').
  • feat(engine): persistence vegetableFarms save/load + assignedVegetableFarmId colon : serializeSnapshot serialise vegetableFarms[] (x, z, id, stage, plantedAt, assignedColonistIds) et vegetableFarmNextId. clearEverything remet les deux a zero. applySnapshot restaure via addVegetableFarmFromSave. assignedVegetableFarmId sauvegardes et restaure sur chaque colon.
  • feat(engine): interaction place-vegetable-farm dispatch, findBuildingAtCell 2x2, refreshToolButtons agriculture gate : dans interaction.js, import de addVegetableFarm, ajout dans BUILDING_PLACEMENT_TOOLS, dispatch dans les switch pointerdown et pointermove, setLocked lie a tech agriculture. findBuildingAtCell etendu footprint 2x2 pour type vegetable-farm.
  • feat(engine): bouton place-vegetable-farm dans actionbar age 3 icone carotte : bouton icone carotte label "Legumes" dans #ab-build de index.html, verrouille par defaut, deverrouille par tech agriculture.
Lot B, engine Mairie (upgrade big-house vers town-hall) 2026-06-07
  • feat(engine): town-hall GLB manifest Tower House + TOWN_HALL_GLB_SCALE : ajout de 'town-hall': 'Tower House.glb' dans le MANIFEST de glb-cache.js et constante TOWN_HALL_GLB_SCALE = 2.5.
  • feat(engine): state.townHalls + townHallNextId : nouveau tableau state.townHalls et compteur d id monotone dans state.js.
  • feat(engine): upgradeBigHouseToTownHall, finishUpgradeTownHall, addTownHallFromSave, isCellOccupied etendu : dans placements.js, upgradeBigHouseToTownHall(entry) verifie tech gouvernance et upgradeCost depuis JSON via getBuildingById, deduit les ressources, set entry.upgradingUntil = Date.now() + buildTime * 1000. finishUpgradeTownHall(entry) retire le mesh big-house, cree un mesh GLB Tower House (fallback voxel tour+dome), retire l entree de state.bigHouses, push dans state.townHalls. addTownHallFromSave(data) restaure un town-hall depuis la sauvegarde. isCellOccupied etendu pour le footprint 4x4 des town-halls.
  • feat(engine): persistence town-halls save/load, bigHouse upgradingUntil preserve : serializeSnapshot sérialise townHalls[] (x, z, id) et townHallNextId. Sérialise aussi upgradingUntil sur les big-houses en cours d upgrade. clearEverything remet state.townHalls = []. applySnapshot restaure via addTownHallFromSave et preserves le upgradingUntil des big-houses pour reprendre l upgrade au reload.
  • feat(engine): tick boucle lente finishUpgradeTownHall sur bigHouse upgradingUntil : dans main.js, la boucle lente (1s) itere sur state.bigHouses et appelle finishUpgradeTownHall(b) des que Date.now() >= b.upgradingUntil.
  • feat(engine): computePopCap +5 par town-hall via provides.populationCapBonus JSON : computePopCap() dans colonist.js lit getBuildingById('town-hall').provides.populationCapBonus et ajoute ce bonus par town-hall construite au cap de population.
  • feat(engine): findBuildingAtCell town-hall 4x4, dispatch strates:buildingClicked type town-hall : findBuildingAtCell dans interaction.js couvre le footprint 4x4 des town-halls. Le clic gauche sur une town-hall emet strates:buildingClicked { type: 'town-hall', building, entry } via le dispatch generique.
Lot B, storage-hut engine 2026-06-07
  • feat(engine): storage-hut GLB manifest + state.storageHuts : ajout de 'storage-hut': 'Storage Hut.glb' dans le MANIFEST de glb-cache.js, constante STORAGE_HUT_GLB_SCALE = 2.5. Ajout de storageHuts: [] dans state.js.
  • feat(engine): addStorageHut 4x4 GLB+fallback voxel, isCellOccupied etendu : addStorageHut(gx, gz) dans placements.js, footprint 4x4, mesh GLB avec fallback voxel (corps bois + toit vert + porte). Entry : { x, z, group, id, assignedResource: null }. _markUnderConstruction sur les 16 cases, buildTime lu depuis buildings.json. Fonctions exportees : addStorageHutFromSave, removeStorageHut, getUnassignedStorageHuts, getAssignedResources, getStorageHutForResource, assignResourceToHut (retourne false si ressource deja couverte, emet strates:storageHutAssigned). isCellOccupied etendu pour le footprint 4x4 storage-hut.
  • feat(engine): getResourceCap +400 par storage-hut assignee, enforceResourceCaps : stocks.js gagne getResourceCap(resourceId) (100 par defaut, +400 si une hutte construite est assignee a cette ressource, valeur lue depuis buildings.json#storage-hut.provides.resourceCapBoost). enforceResourceCaps() plafonne toutes les state.resources a leur cap respectif. Appelee chaque seconde dans la boucle lente de main.js avec enforceFoodStorageCap.
  • feat(engine): persistence save/load storageHuts (position + assignedResource) : serializeSnapshot sérialise storageHuts[] (x, z, id, assignedResource). applySnapshot restaure via addStorageHutFromSave et marque les huttes construites. clearEverything remet state.storageHuts = [].
  • feat(engine): interaction dispatch place-storage-hut, refreshToolButtons, findBuildingAtCell : import de addStorageHut, ajout dans BUILDING_PLACEMENT_TOOLS, dispatch dans les deux switch pointerdown et pointermove, setLocked lie a granary, footprint 4x4 correct dans findBuildingAtCell.
  • feat(engine): bouton place-storage-hut dans actionbar age 3 : bouton icone 📦 label "Stockage" dans l actionbar age 3 de index.html, verrouille par defaut, deverrouille par la tech granary.
Lot B, engine techs nocturnes Age 3 2026-06-07
  • feat(engine): effet astrologie +20% rendement champs cultives : dans main.js, le taux de production de ble (cultiveWheatRate) est multiplie par 1.2 si la tech astrologie est debloquee. Aucun hardcode : le multiplicateur est conditionnel et s applique uniquement sur les champs en stage cultive avec un fermier actif.
  • feat(engine): effet alchimie recette iron_to_silver a la forge : pickForge() dans colonist.js accepte desormais deux cas de declenchement : stocks copper+tin suffisants (bronze, comportement existant) OU tech alchimie debloquee ET iron >= 5 (argent). Dans le cycle WORKING, la priorite est bronze si les deux sont possibles, sinon argent (5 iron -> 1 silver, gainSkillXp forging +2). _hasNoTaskForProfession() mis a jour en consequence.
  • feat(engine): effet meditation +0.5 recherche par minute passif global : dans main.js, apres le bloc de tick de recherche, si la tech meditation est debloquee et qu une tech est active, on ajoute RESEARCH_TICK * 0.5 / 60 a state.activeResearch.progress a chaque tick (3s). Effet purement passif, aucun batiment requis.
  • feat(engine): infirmerie 2x2 healing +1 HP/5s rayon 6 + bouton actionbar : addInfirmary(gx, gz) dans placements.js (batiment 2x2, visuel voxel croix rouge sur toit). state.infirmaries ajoute dans state.js. Tick healing dans main.js : toutes les 5s, les colons dans un rayon Manhattan 6 autour de chaque infirmerie construite gagnent +1 HP (plafond 100). Bouton place-infirmary ajoute dans index.html (icone hopital). interaction.js : import addInfirmary, setLocked lie a herboristerie-nocturne, dispatch dans les deux switch pointerdown/pointermove.
Lot B, engine Age 3 Medieval vague 1 2026-06-05
  • feat(engine): skills milling/baking/trading dans colonist.js et productivity.js : trois nouvelles skills ajoutees dans SKILL_TO_JOB, JOB_TO_SKILL (colonist.js) et SKILL_FOR_JOB, MIN_COLONISTS_FOR_JOB (productivity.js). Initialisation a 0 dans le constructeur Colonist (ces skills ne s initient pas au hasard, elles progressent uniquement par cycle de production). constants.js gagne trois nouvelles constantes de duree de cycle : MILL_CRAFT_DURATION = 6s, BAKE_CRAFT_DURATION = 8s, TRADE_CRAFT_DURATION = 10s.
  • feat(engine): cycles production meunier/boulanger/marchand dans colonist.js : les trois metiers age 3 sont cables avec le meme pattern que le forgeron. Chaque metier a un pick*() (cherche le workplace le plus proche sous condition de matieres disponibles), un declenchement dans le bloc IDLE (entre forgeron et LEISURE), une bascule MOVING->WORKING en arrivant au workplace, et un cycle WORKING qui consomme/produit au terme de la duree. Meunier : consume 1 wheat (ou grain en fallback) -> produit 1 flour, cycle 6s, gainSkillXp milling. Boulanger : consume 1 flour -> produit 1 bread, cycle 8s, gainSkillXp baking. Marchand : si surplus > 20 sur wheat/bread/raw-meat, consume 1 unite et donne +1 state.researchPoints, cycle 10s, gainSkillXp trading. isIdle() couvre les 3 nouveaux metiers (gate workplace construit + matieres disponibles).
  • feat(engine): add placements windmill/bakery/market/tavern/library avec visuels voxel : 5 nouvelles fonctions exportees depuis placements.js. Chaque fonction suit strictement le pattern addForge/addGrenier : check eau/occupation, construction voxel Three.js, _markUnderConstruction, push dans le tableau d etat, revealAround. Visuels distincts : windmill (cylindre pierre + ailes croisees), bakery (corps pierre + arch + foyer emberMat emissif + cheminee + PointLight orange), market (plateforme + poteaux + auvent tissu jaune), tavern (corps plaster + colombages + PointLight ambre + enseigne), library (colonnades + fenetres glass transparent + dome). isCellOccupied() etendue pour couvrir les 5 bâtiments 1x1.
  • feat(engine): bibliotheque bonus recherche x1.5 passif : computeLibraryResearchBonus(state) exportee depuis productivity.js. Retourne 1.5 si au moins 1 library est construite (non en chantier), 1.0 sinon. Pas de cumul : 2+ libraries = meme bonus. Cablage dans main.js : const libraryBonus = computeLibraryResearchBonus(state) applique en multiplicateur sur n = Math.round(prod * 10 * libraryBonus) dans la boucle tick recherche.
  • feat(engine): bread rassasie 60 pts via saturation x20 : hungerReductionFor() etendue avec un troisieme fallback qui lit getResourceById(resourceId).saturation * 20 quand la ressource n est pas dans satisfied_by de needs.json. Le pain (saturation: 3) rassasie donc 60 points, soit 3 baies. tryEatMeatFromStocks() inclut desormais bread dans la priorite de consommation (cooked-meat > bread > raw-meat).
  • feat(engine): init arrays age 3 dans state : state.windmills/bakeries/markets/taverns/libraries/castles initialises a [] dans state.js. state.resources gagne flour: 0 et bread: 0 pour eviter les undefined sur les accedes numeriques.
  • Note integration : les handlers de placement (cases place-windmill etc. dans interaction.js) sont a cablage par l agent Lot C (hors scope Lot B). Les fonctions addWindmill/addBakery/addMarket/addTavern/addLibrary sont exportees et prates a l import.
Lot C, panel quetes - support quetes Age 2 2026-06-05
  • feat(ui): quetes age 2 - catalogue JSON, filtrage par age, badge visuel Bronze : le module quests.js consomme desormais QUEST_DEFS_BASE (charge depuis editor/data/quests.json via gamedata.js) au lieu du catalogue hardcode legacy. La fonction buildCatalog() prefere le JSON v1 si disponible, sinon tombe en fallback legacy. resolveCheckKey() traduit les checkKey en fonctions live : berries, colonistsCount, housesPlaced, minesCompleted, totalBerriesHarvested, buildingBuilt-*, jobHired-*, resourceProduced-*, techUnlocked-*, buildingStageBuilt-*. Le filtrage dans getAvailableQuests() filtre sur q.age <= state.currentAge : les 10 quetes Age 2 (q2-*) sont invisibles en Age 1. Un badge "Age Bronze" est injecte dans les cartes des onglets "A prendre" et "En cours" pour les quetes age >= 2. Listener strates:ageChange ajouté en fin de module pour rafraichir la liste quand l'age change.
Lot B, polish technique fin Age 2 (3 dettes résolues) 2026-06-05
  • feat(engine): spawner ore-tin (etain) en mode bronze-test, generaliser _spawnTestOres : la substitution temporaire ore-iron utilisée depuis le changelog du 2026-05-07 est supprimée. La fonction _spawnTestIronOres (spécifique ore-iron) est remplacée par _spawnTestOres(oreType, ...) générique. En mode ?mode=bronze-test, 3 à 4 filons ore-tin (couronne [8, 30]) et 2 à 3 filons ore-iron (couronne [15, 40]) sont désormais spawés. ore-tin était déjà présent dans ORE_TYPES, ORE_TO_STOCK et ORE_TECH dans constants.js depuis le lot A du 2026-05-06 : seul le spawn en mode test manquait.
  • fix(engine): ajouter buildTime 8s sur route-pavee (pose non instantanee) : le bâtiment route-pavee dans editor/data/buildings.json n'avait pas de buildTime, violant la règle "tout bâtiment a buildTime". Ajout de buildTime: 8. Le câblage du speed_multiplier: 1.2 (colons 1.2x plus vite sur une cellule paved-road) était déjà en place dans colonist.js via state.cellSurface (depuis une session antérieure non documentée). Le buildTime était la seule dette réelle.
  • feat(engine): grenier augmente la capacite de stockage alimentaire de 200 par unite construite : editor/modules/stocks.js gagne deux nouvelles fonctions exportées. computeFoodStorageCapacity() lit getBuildingById('grenier').provides.storage (200 par défaut) et multiplie par le nombre de greniers construits (!isUnderConstruction), ajoute une capacité de base FOOD_STORAGE_BASE = 100. enforceFoodStorageCap() plafonne les 5 ressources alimentaires (berries, grain, wheat, raw-meat, cooked-meat) au cap calculé. Appelée depuis la boucle lente de main.js (1s de cadence, avec checkCairnOverlay). Sans grenier : cap 100 sur chaque ressource alimentaire. Avec 1 grenier construit : cap 300. Avec 2 : cap 500.
v1.0.0 (2026-05-14)

**Personnages**

  • Affinités par nom : François chercheur, Alexie mineuse, Frédéric chercheur (actif contextuel), et autres
  • François spawn avec niveau 10 en recherche ; le tutoriel mentionne son affinité
  • Couples prédéfinis : Julien/Victoire (âge 3), Frédéric/Chloé ; liens sociaux persistants
  • Duo Thomas/Omar : bonus productivité x1.15 quand tous deux vivants simultanément
  • Fête du foyer : boost x1.25 pendant 5 min, cooldown 10 min, disponible âge 2+
  • Maison de Julien : bâtiment 1x2, âge 3, footprint 1x2 inédit
  • Vision étendue de Valentin explorateur (+10% rayon)
  • Labels prénoms agrandis (scale 3.8) et badge affinité dans la vue Métiers
  • Couleurs métiers harmonisées avec les branches du tech tree

**Progression et tech tree**

  • 7 âges : Pierre, Bronze, Fer, Médiéval, Industriel, Moderne, Espace (Atomique supprimé, Médiéval inséré)
  • Drag et drop pour réordonner la file de recherche

**Champs et agriculture**

  • 3 stages de champ : Sauvage, En culture, Cultivé
  • Panneau latéral dédié par stage (production, worker requis)
  • Suppression de la ressource grain obsolète (remplacée par blé)

**Moteur**

  • 5 slots de sauvegarde indépendants
  • Fix minage ore-iron : les filons ne déclenchent plus le gate "Mineur niv.5+" de la roche de montagne
  • Bronze-test : arbres (clusters forêt + épars herbe), buissons et cerfs générés sur la map

**Visuels et UI**

  • Meshes voxel Grenier (silo) et Forge remplacent les placeholders
  • Fix sky shader : injection uOpacity dans le fragment shader (mode nuit fonctionnel)
  • Fermeture du panneau bâtiment au clic en dehors de la fenêtre
  • Fog of war ne se révèle plus au placement d'un bâtiment
  • Touche Escape revient en mode Naviguer si un outil est actif
Lot B, liens sociaux et popCap pour la reproduction 2026-05-12
  • feat(engine): social level and popCap gate reproduction : ajout du champ socialLevel (0 a 100, persiste) sur chaque colon, alimente a chaque transition jour vers nuit par tickSocial() dans colonist.js. Bonus de presence au foyer (SOCIAL_PER_NIGHT_CAMPFIRE), bonus partenaire cohabitant (SOCIAL_PER_NIGHT_PARTNER), penalites faim critique, sans-abri et isolement. Nouvelles constantes dans constants.js : SOCIAL_LEVEL_MAX, SOCIAL_PER_NIGHT_CAMPFIRE, SOCIAL_PER_NIGHT_PARTNER, SOCIAL_HUNGER_PENALTY, SOCIAL_HOMELESS_PENALTY, SOCIAL_ISOLATED_PENALTY, SOCIAL_REPRODUCE_MIN, POP_CAP_DEFAULT. Etat global state.popCap = 10 (par defaut). Fonction computePopCap() exportee depuis colonist.js : min(capacite cumulee des habitations construites, state.popCap). tickReproduction() enrichi : 1. les deux partenaires doivent avoir socialLevel >= SOCIAL_REPRODUCE_MIN, 2. au moins 10 nuits de cohabitation effective (compteur runtime _cohabitDays), 3. state.colonists.length < computePopCap(). Tant que ces conditions ne sont pas reunies, reproductionTimer ne progresse pas mais _cohabitDays continue de s incrementer. La naissance passe desormais par triggerReproductionModal() (import dynamique de ui/reproduction-modal.js, fallback direct si modale absente) : generation des stats enfant (genre, nom unique, 7 skills heritees des parents avec bruit, skill dominante), puis spawnChildFromStats() qui remplace l ancien _spawnChildAt. Persistance : socialLevel sauvegarde dans persistence.js. UI : ligne "Liens sociaux" ajoutee a la fiche personnage (charsheet-ui.js), affichee en niveau / 10 avec barre rose. HUD : nouvelle ligne "Population : X / Y" calculee via computePopCap() (id c-popcap ajoute aux compat IDs caches). Nouveau JSON data/colonist-templates.json (descriptions par skill dominante, lecture seule, utilise plus tard par la modale Lot C). Fichiers touches : modules/colonist.js, modules/constants.js, modules/state.js, modules/persistence.js, modules/charsheet-ui.js, modules/hud.js, index.html, nouveau data/colonist-templates.json.
Lot D, Ziggurat construction par etapes et spawn filons fer 2026-05-12
  • feat(engine): ziggurat 3-stage construction + ore-iron spawn : implementation moteur du Ziggurat (monument age 2 vers 3) en complement du UI Lot C. Cote state.js, ajout de state.ziggurat = { placed, stage, stageProgress, x, z, entry }, state.ziggurats[] et state.zigguratPlacementMode. Cote placements.js, nouvelles fonctions makeZiggurat, addZiggurat(gx, gz) (footprint 3x3, verifie hors-eau et libre) et rebuildZigguratMesh(entry) (mesh voxel procedural reconstruit a chaque changement de stage : stage 1 = socle pierre 2 voxels, stage 2 = paliers pierre + bande bronze 5 voxels, stage 3 = sommet bronze + pointe doree 8 voxels). Le footprint 3x3 est integre a isCellOccupied(). Cote age-transitions.js, ajout de canBuildZiggurat(st) (conditions : age >= 2, forge active, grenier actif, bronze >= 30 stock, au moins 1 filon ore-iron decouvert dans state.ores, 8 colons minimum), canStartNextZigguratStage(st) (l etape 3 exige iron >= 5 et toutes techs age 2 recherchees via TECH_TREE_DATA), startZigguratPlacement/cancelZigguratPlacement/confirmZigguratPlacement (consomment le cout stage 1 depuis le JSON buildings.json#ziggurat.stages[0].cost), startNextZigguratStage() (consomme le cout du stage suivant, reactive isUnderConstruction, reset progress, appelle rebuildZigguratMesh). Le bouton actionbar place-ziggurat est cree par initZigguratButton() (cache si age < 2, grise si conditions manquantes, click ouvre placement). Cote colonist.js, pickConstructionSite() etend la liste avec state.ziggurats, et onBuildingComplete() distingue : stage 1 ou 2 termine -> toast "etape achevee, cliquez pour lancer la suivante", stage 3 termine -> import dynamique de triggerAgeTransitionIron(). Cote interaction.js, ajout des hooks de placement Ziggurat (pointerdown, ESC, cancelAllModes). Cote worldgen.js, nouvelle fonction spawnIronOres({ minCount, maxCount, minDistFromSpawn }) appelee depuis _applyBronzeAge() (3 a 5 filons en biome rock/snow, distance > 30 cases du spawn). Cote age-test-modes.js, helper _spawnTestIronOres(ox, oz, count, minR, maxR) ajoute en fin de setupBronzeTest() pour placer 2 a 3 filons ore-iron dans la couronne [10, 40] autour du spawn, indispensable pour tester le Ziggurat en mode ?mode=bronze-test. Tous les parametres (couts, buildTime de chaque etape) viennent du JSON data/buildings.json via getBuildingById('ziggurat').stages[], aucun hardcode. Le bouton actionbar est rafraichi via checkZigguratOverlay() dans la boucle main.js (1s de cadence, a cote de checkCairnOverlay).
Lot C, Ziggurat actionbar tooltip + panneau de construction par etapes 2026-05-12
  • feat(ui): ziggurat actionbar button and staged construction panel : enrichissement de l UI autour du nouveau monument Ziggurat (age 2 vers 3, deja livre cote engine par Lot B/D). Ajout d un tooltip HTML enrichi au survol du bouton Ziggurat dans #ab-build (id place-ziggurat) : header avec icone 🏛 et titre "Ziggurat", sous-titre "Monument de passage vers l Age du Fer", section "CONDITIONS MANQUANTES :" en rouge (ou "CONDITIONS REUNIES" en vert) listant Age du Bronze atteint, Forge active, Grenier actif, bronze >= 30, filon de fer decouvert, 8 colons minimum. Les conditions remplies sont en vert avec ✓, les manquantes en rouge avec ✗. Le tooltip natif title="" reste en fallback. Le nouveau div #ziggurat-conditions-tooltip est ajoute a index.html, style dedie dans styles/cinematic.css (panneau flottant glassmorphism dore). Cote panneau d info batiment (building-panel.js), ajout du type ziggurat dans BUILDING_META (icon 🏛, name "Ziggurat") et dans NON_DESTRUCTIBLE. Le clic sur la Ziggurat ouvre desormais le building-panel au lieu d un window.confirm (suppression de l interception _onZigguratClicked dans interaction.js, le dispatch strates:buildingClicked prend la suite). Le panneau contient les sections : Etape courante (Fondations / Corps / Sanctuaire et n / 3), Progression (barre de chantier vivante lue depuis entry.constructionProgress), Prochaine etape (coûts stone / bronze / coal / iron rouges si manquant, bouton "Lancer l etape N" desactive via canStartNextZigguratStage, et pour l etape 3 ligne "Toutes techs age 2 recherchees ✓/✗"), Constructeurs (colons avec assignedJob === 'builder' ciblant l entry ou dans le footprint 3x3). Quand l etape 3 est terminee, l etat affiche "Ziggurat terminee. Transition vers l Age du Fer...". La refresh loop est etendue pour mettre a jour la barre de progression a 4 Hz et basculer automatiquement entre les etapes. Le titre du panneau est dynamique : "Ziggurat -- Fondations" / "Corps" / "Sanctuaire". Fichiers touches : modules/age-transitions.js (tooltip ziggurat, conditions UI dynamiques), modules/ui/building-panel.js (ajout type ziggurat, helpers _renderZigguratSections, _wireZigguratButton), modules/interaction.js (clic ziggurat -> building-panel), styles/cinematic.css (styles tooltip ziggurat), index.html (div tooltip). Aucune modification a data/*.json ni aux IDs internes (cairn, ziggurat, state.ziggurat).
Lot D, bronze-test refactorise en etat sauvegarde fin age 1 2026-05-12
  • refactor(transitions): bronze-test as completed age 1 save state : refonte complete de setupBronzeTest() dans age-test-modes.js. La fonction simule desormais une sauvegarde reelle de fin d'age de Pierre plutot qu'un scenario artificiel. Worldgen normal conserve sans modification (pas de defrichage r=40, pas de flatten). Batiments age 1 poses autour du foyer central : foyer, 3 cabanes, hutte du sage, promontoire, grosse maison (4x4), champ (2x2 fertile). Cairn pose et marque termine pour signifier le passage d'age accompli. Population : chef Francois + 7 colons. Ressources : 100 bois, 100 pierre, 50 baies, 20 raw-meat, 10 bone, 10 hide (plus de copper/tin injecte a la main). Fog of war se revele naturellement autour des batiments poses. Toutes les techs age 1 debloquees en iterant TECH_TREE_DATA (plus liste statique de fallback). Bascule age 2 identique a _applyBronzeAge (achievements bronze_age + bronze_age_test, ageUnlockedAt[2], techs Bronze marquees _bronzeAvailable). Ajout de deux helpers prives : _findFreeCellForBigHouse (verifie footprint 4x4) et _findFertileCell (verifie footprint 2x2 + cellFertile===1). Suppression des imports addTree, addDeer, addRock, addOre, rebuildTerrainFromState, revealAround (plus utilises). Badge "TEST MODE : BRONZE" inchange.
Lot D, bronze-test aplanit la zone centrale avant spawn 2026-05-12
  • feat(transitions): bronze-test flattens grass zone before spawn : dans setupBronzeTest() (age-test-modes.js), ajout d'une etape de defrichage central executee juste apres la selection du point de spawn, avant tout placement. Toute cellule a distance euclidienne inferieure ou egale a 40 du centre village (sx, sz) au-dessus de SHALLOW_WATER_LEVEL est ramenee a cellTop=3 (hauteur plaine) et cellBiome='grass'. Appel de rebuildTerrainFromState() (terrain.js) pour reconstruire le mesh instancie depuis le nouvel etat, puis appel de revealAround(sx, sz, 40) pour lever le fog of war sur la zone aplanie. Corrige le "donut" (biome forest marron + rock gris) visible autour du village en mode bronze-test : c'etait le worldgen procédural normal qui restait visible faute de defrichage. Les deux imports ajoutes (rebuildTerrainFromState, revealAround) viennent de terrain.js. Le reste de setupBronzeTest (spawn colons, batiments, ressources naturelles) est inchange.
Lot D, bronze-test spawns arbres, cerfs et filons 2026-05-07
  • feat(transitions): bronze-test spawns trees, deer and ore veins : setupBronzeTest() dans age-test-modes.js spawne desormais des ressources naturelles autour du village de test. 30 arbres (biome grass/forest, rayon 15-25 depuis le spawn), 4-5 cerfs (meme criteres biome), 8-10 rochers pierre (via addRock), 4-5 filons de cuivre (ore-copper), 3-4 filons de fer (ore-iron, substitut car ore-tin absent de ORE_TYPES), 3-4 filons de charbon (ore-coal). Tous places dans un anneau rayon 15-25 pour ne pas ecraser les batiments du centre. Nouvelles fonctions importees depuis placements.js : addTree, addDeer, addRock, addOre. Log final recapitulatif des quantites effectivement placees. Note : ore-tin n'existe pas dans ORE_TYPES du moteur ; le filon etain reste a creer cote data (ticket Lot A).
Lot A, pick-bronze unlocks tin + description mise a jour 2026-05-07
  • feat(data) pick-bronze unlocks tin + updated description : ajout de "tin" dans unlocks.resources de la tech pick-bronze (etait ["copper", "coal"], devient ["copper", "coal", "tin"]). Description mise a jour pour mentionner l'etain et le role de la Forge (cuivre + etain -> bronze, charbon = combustible). La ressource tin existait deja dans resources.json (ajoutee lors de la session du 2026-05-06).
Lot C, refactor routage des liens du tech tree (Y par noeud, plus d axe central) 2026-05-07
  • refactor(ui) tech tree links per-node Y routing instead of central axis : avant ce fix, l ancien routage orthogonal des liens dans la vue detaillee d une branche utilisait un seul midX horizontal (ou midY vertical) calcule sur les centres des deux noeuds. Resultat : sur la branche Outils, les chaines Pioche -> Pioche Bronze, Hache -> Hache Bronze et Arc -> Pointes de bronze partaient du meme axe Y central de la colonne Pierre et arrivaient toutes confondues sur une seule ligne horizontale, impossible de distinguer les chaines. Nouveau routage en courbe Bezier cubique dans techtree-panel.js : chaque lien part du bord droit du noeud source au Y de ce noeud (sx = from.x + from.w/2, sy = from.y) et arrive au bord gauche du noeud cible au Y de ce noeud (tx = to.x - to.w/2, ty = to.y). Control points a un tiers et deux tiers du delta horizontal. Routage vertical local (sortie bas, entree haut) quand source et cible partagent quasiment la meme colonne (delta X inferieur a la moitie de la largeur d un noeud), pour les chaines verticales type Pioche -> Terraformation. Les chaines parallele sont maintenant visuellement distinctes a leur Y respectif. CSS techtree.css : stroke-linecap et stroke-linejoin passes en round (transitions courbes propres), couleur repos rgba(255,255,255,0.15) 1.5px, hover req-hi 2.5px (toujours or pulse). Le hover req-highlight, le filtre de branche et l auto-scroll par age (livre 676e686) sont preserves. La vue globale constellation (spokes) n est pas touchee.
Lot C, fix auto-scroll de la vue detaillee branche vers l age actif 2026-05-06
  • feat(ui) auto-scroll tech tree branch view to active age : le cache _lastNodePos declare au top de techtree-panel.js n etait jamais alimente, ce qui faisait sortir centerOnViewedAge() immediatement (if (!ids.length) return). Resultat : a l ouverture d une branche en mode bronze-test (currentAge=2), la vue restait calee a gauche sur la colonne Pierre au lieu d etre centree sur la colonne Bronze. Fix : _lastNodePos = nodePos en fin de renderBranchDetail, juste apres le placement des cards. Le centrage smooth (transition 320ms) deja branche sur openBranch et sur le clic du rail bas des ages se declenche maintenant correctement, le barycentre des techs de l age vise est ramene au centre du wrap.
Lot D, infra modes de test par age + scenario bronze-test 2026-05-06
  • feat(transitions): test mode infra + bronze-test scenario : nouveau module modules/age-test-modes.js. Lecture du parametre URL ?mode=bronze-test via getTestModeFromURL(). Fonction applyTestMode(mode) qui dispatch vers un handler dedie. setupBronzeTest() injecte un etat complet de fin d'age 1 : 6 colons (Francois chef + 5), foyer + 2 cabanes + hutte du sage + promontoire places et marques termines, stocks wood:100 / stone:100 / food:50 / copper:20 / tin:20, 13 techs age 1 toutes debloquees, currentAge=2 sans cinematique ni Cairn, techs age 2 marquees _bronzeAvailable (meme pattern que _applyBronzeAge), 50 pts de recherche disponibles. Pas d'ecriture localStorage (mode session pur). Badge HUD rouge "TEST MODE : BRONZE" inject sous la topbar via _showTestModeBadge(). Branchement dans main.js apres buildTerrain() et avant le spawn par defaut, bypass complet du flow save/new-game. Le tutoriel est neutralise automatiquement par isPartieAvancee() (currentAge > 1). Architecture extensible : pour ajouter iron-test, creer setupIronTest() qui appelle setupBronzeTest() puis injecte les conditions age 2->3.
Lot A, dependances data du forgeron (forge, etain, bronze, stocks) 2026-05-06
  • feat(data) add forge + tin/bronze resources + stocks for forgeron : mise en coherence des donnees requises par le job forgeron cable par Lot B. Mise a jour de la forge dans buildings.json (cost stone:20 wood:10, buildTime:40, provides smelting:true, description alignee spec). Ajout de la ressource tin (Etain, age 2, minerai) dans resources.json. Mise a jour de la ressource bronze (source et usage alignes spec). Ajout de tin et bronze dans STOCK_KEYS et STOCK_LABELS dans constants.js (stocks.js initialise automatiquement copper:0, tin:0, bronze:0 au boot via la boucle STOCK_KEYS). Ajout de copper, tin, bronze dans state.resources initial. Ajout de state.forges = [] dans les placements de state.js.
Lot C, tech tree centre la vue sur l age courant a l ouverture d une branche 2026-05-05
  • feat(ui) tech tree centers on current age when section clicked : a l ouverture d une branche du tech tree (clic sur la constellation ou bascule via le rail bas des ages), le viewport est desormais cadre automatiquement sur les techs de l age en cours (_viewedAge). Avant ce fix, l ouverture restait calee a gauche et les techs de l age 2+ tombaient hors viewport, obligeant a panner manuellement. Nouvelle fonction centerOnViewedAge() dans techtree-panel.js : calcule le barycentre des cards correspondant a _viewedAge (priorite aux techs non encore acquises pour mettre l attention sur ce qui reste a chercher), fallback sur la derniere tech debloquee si toutes sont prises, puis ajuste view.tx / view.ty pour amener ce point au centre du wrap. Ajout d une transition CSS transform 320ms ease retiree apres 360ms pour ne pas freiner le pan manuel. Reporte d une rAF si le viewport n est pas encore layoute. Le clic sur le rail bas des ages declenche aussi le recentrage si on est en mode detail. Cache _lastNodePos partagee entre renderBranchDetail et le centrage pour eviter la recompute des positions.
Lot B, champ a deux etats (sauvage / cultive) avec transformation par le fermier 2026-05-05
  • feat(engine) two-stage field (wild grain / cultivated wheat) with fermier transformation : tout champ pose demarre desormais a l etat sauvage (field.stage = 'sauvage', defaut). Au stage sauvage, le champ produit du grain en autonomie au taux faible defini par BUILDINGS_DATA.field.stages.sauvage.provides.grain_per_tick (0.02 / tick de 2s par defaut), sans fermier requis, comestible direct comme les baies. Quand la tech wheat-field est debloquee et qu un fermier (profession === 'agriculteur', assignedJob === 'farmer') est libre, il cible en priorite 1 le champ sauvage le plus proche, marche jusqu a lui, bascule en nouvel etat FARMING_TRANSFORM pendant transformFrom.duration secondes (30s par defaut, lu via BUILDINGS_DATA). A la fin, transformField(field) passe field.stage a 'cultive', remet transformProgress a zero et emet l event strates:fieldTransformed (consomme par Lot E pour le swap visuel). Le fermier reste alors assigne au champ et bascule en FARMING pour produire du wheat au taux eleve (stages.cultive.provides.wheat_per_tick, 0.10 / tick par defaut). Priorite 2 : si tous les champs sont deja cultives, le fermier prend un champ cultive libre (capacite 1 par champ) comme avant. Persistence : stage, transformProgress et wheat sont serialises et restaures, le FARMING_TRANSFORM est remis a zero au reload pour repartir proprement depuis IDLE. Ajout de state.stocks.wheat et state.resources.wheat. Compatibilite arriere : si la nouvelle entry field (Lot A) est absente, fallback sur l ancien champ-ble.provides.grain_per_tick pour le taux cultive.
fix Lot B, quetes batiments comptent les chantiers termines 2026-05-05
  • fix(engine) building quests count only completed buildings : les quetes "Construire N maisons" (houses-3, houses-5) reposaient sur le compteur state.gameStats.housesPlaced incremente des la pose du chantier, validant la quete avant la fin reelle de la construction. Nouveau type de goal buildingsBuilt dans quests.js qui compte les batiments du tableau d etat (state.houses, state.bigHouses, etc.) en filtrant isUnderConstruction et isUnderUpgrade. Pas de double-comptage entre cabane source en upgrade et big-house finale. Les quetes deja validees a tort en save ne sont pas devalidees retroactivement (juste plus produites a l avenir).
recapitulatif des commits non encore documentes (UI actionbar, social, dialogues, divers) 2026-05-04

Synthese groupee par lot des commits du 04 mai non couverts par les sections detaillees ci-dessous. Tri chronologique inverse (commit le plus recent en tete dans chaque lot).

Lot E (world / UI)

  • 4b5b0a0 feat(engine) defaults residents and couple links for new houses : a la completion d une cabane, spawn d un couple homme + femme, lien partnerId reciproque, building.residents cable, colonist.homeBuildingId pose. Grosse cabane : 2 couples spawnes, 2 places restantes.
  • 6d65a3b feat(world) show residents in house panel and add social MVP modal : fiche batiment cabane / grosse cabane affiche les residents avec gender, metier, badge couple. Click resident ouvre charsheet. Nouvelle modale Social (rail droit) read-only listant couples, celibataires, sans-abri.
  • 9343a2d chore(data) rename big-house to Grosse cabane and document residents schema : renommage Manoir -> Grosse cabane pour les 3 premiers ages, documentation du champ residentsCapacity et de socialRelations.
  • 896a945 feat(world) upgrade button from house to big-house in building panel : bouton "Ameliorer en grosse maison" sur fiche cabane terminee, gating tech / ressources / footprint 4x4, barre de progression pendant l upgrade, bascule auto sur fiche big-house a l achevement.
  • 1c1b1e8 chore(world) disable medieval field visual evolution : annulation du swap visuel du champ vers la version medievale, conservation du glb farm de base a toutes etapes de croissance.
  • b76345c feat(world) show assigned colonists in job picker greyed : modale Population / Metiers liste tous les colons en 3 groupes (libres, autres metiers, deja assignes grise avec badge ✓ Assigne et bouton Changer de metier).
  • a379662 chore(world) remove builder tutorial step : suppression complete du tuto Constructeur (apparait trop tard, plus de valeur pedagogique). Flag builderTutoDone nettoye automatiquement par resetTutorialFlags.
  • 5b501c6 feat(world) redesign speech bubbles slate gold style : bulles colons passent de blanc translucide a fond ardoise opaque + bordure or 3px + texte creme. Contraste 9:1, lisibilite garantie sur tous biomes. Variantes hint (bleu) et idle (couleur metier) preservees.
  • b3bd21f fix(world) shrink tool labels to fit actionbar buttons : suppression d un bloc CSS .tool duplique qui overrideait silencieusement les regles POC A. Labels passent en mono 7.5px avec wrap autorise sur 2 lignes (HUTTE DU SAGE -> 2 lignes).
  • 7b809b5 fix(world) align in-game actionbar with POC A reference : alignement du markup et du CSS de #actionbar sur le POC A (3 zones ancrees, espacements, typo).
  • 6740fa4 fix(world) cell tooltip distinguishes basic rock from mountain rock : utilisation de classifyMineableBlock comme source de verite. Roche de base -> "Necessite une Pioche". Roche de montagne -> "Mineur Niv.5+ requis".
  • 0fa3e40 feat(world) three-zone anchored actionbar layout : refonte d actionbar en CSS Grid 3 zones (Naviguer gauche, outils centre, Annuler+Demolir droite). Annuler et Demolir restent au meme pixel quel que soit l onglet.
  • d14229e fix(world) bubble scale grows with camera distance : echelle des bulles colons ajustee selon distance camera (clamp [0.95, 2.2]), plus de troncature visuelle au dezoom.
  • cc37f96 feat(world) enlarge colonist speech bubbles : premiere passe d agrandissement bulles, canvas 768x240, font Inter 700 38px, scale Sprite 3.4x1.1.

Lot B (engine)

  • df08560 fix(engine) single-click activation for cancel and destroy buttons : helper exitAllModesAndEnter(modeName) consolide la sortie atomique de tous les modes (outil, field placement, cairn placement, destroy). Un clic unique sur Annuler ou Demolir entre directement dans le mode cible, sans double-clic.
  • ef2769c fix(engine) cancel zone removes construction sites and frees builders : cancelJobsInRect etendu pour detecter les chantiers via findConstructionSitesInCells, suppression via removeConstructionSite qui libere les colons BUILDER (reset IDLE, vide targetConstructionSite). Demolir bloque sur chantier non termine via toast informatif.
  • d03e453 feat(engine) promontoire houses up to 2 researchers at night with active light : OBSERVATORY_CAPACITY = 2, helpers enterObservatory / releaseFromObservatory / releaseAllObservatoryOccupants, PointLight 0xffd070 active si occupants > 0, mesh chercheur masque a l entree, sortie automatique au lever du jour.

Lot A (data)

  • e32aa5a feat(data) add upgrade cost from house to big-house : ajout du bloc upgradeFrom sur big-house dans buildings.json. From cabane, cost wood:8 stone:5, buildTime 24s. Schema documente via _upgradeFromSchema.
  • 88214d9 chore(data) remove bones requirement from cairn : suppression de la condition os pour la finalisation du Cairn (rare sur petites iles entierement explorees, blocage de fin d age I).
Lot B, fusion auto retiree au profit d un upgrade explicite Cabane vers Grosse maison 2026-05-04

La fusion automatique de 4 cabanes en manoir 2x2 etait buguee (declenchee des le placement, sans attendre la construction), bloquant les colons en BUILDER. Elle a ete entierement retiree. Elle est remplacee par un upgrade explicite Cabane -> Grosse maison (4x4) declenche depuis le bouton du panneau bâtiment (UI deja en place). La progression suit le meme moteur que la construction : un constructeur prend le chantier (isUnderUpgrade === true) et fait monter upgradeProgress jusqu a 1, puis la cabane est swappee en Grosse maison construite.

Cote moteur (placements.js)

  • Suppression de checkManorMerge et isPlainHouseOn.
  • Suppression des appels checkManorMerge dans interaction.js (apres pose d une cabane).
  • Ajout de upgradeBuilding(building, targetType) : verifie le type source, le footprint cible (4x4 libre autour de la cabane), debite les ressources upgradeFrom.cost (lecture buildings.json), pose les flags isUnderUpgrade, upgradeProgress, upgradeTargetType, upgradeBuildTime. Toast d erreur en cas d emplacement insuffisant ou de ressources insuffisantes.
  • Ajout de completeUpgrade(sourceBuilding) : retire la cabane source, place une Grosse maison deja construite (4x4) au meme emplacement, transfere les residents.
  • Helper canUpgradeTo(fromType, toType) : prefere upgradeFrom.from du JSON, fallback sur HARDCODED_UPGRADES = { 'big-house': ['cabane'] } au cas ou.

Cote tick constructeur (colonist.js)

  • pickConstructionSite selectionne aussi les batiments isUnderUpgrade === true.
  • L etat BUILDING distingue construction et upgrade : si site.isUnderUpgrade, on incremente upgradeProgress au lieu de constructionProgress. A l achevement, appel a completeUpgrade puis liberation de tous les constructeurs qui visaient le site (la cabane disparait).
  • _hasNoTaskForProfession du constructeur compte aussi les chantiers en upgrade (pas d idle si une cabane upgrade est en cours).

Persistence

  • serializeSnapshot sauvegarde isUnderUpgrade, upgradeProgress, upgradeTargetType, upgradeBuildTime par cabane.
  • applySnapshot restaure ces flags : un upgrade interrompu reprend a la progression sauvegardee.

Tests mentaux valides

1. Poser 4 cabanes cote a cote, attendre constructions : restent 4 cabanes distinctes (plus aucune fusion auto).

2. Sur une cabane terminee, cliquer "Améliorer en grosse maison" : ressources debitees, etat upgrade, le constructeur vient travailler, big-house en place apres upgradeFrom.buildTime secondes.

3. Emplacement 4x4 insuffisant : toast d erreur, rien ne se passe.

4. Save en cours d upgrade puis reload : reprise propre a la progression sauvegardee.

Lot B, comportement du fermier (1 par champ, production de ble) 2026-05-04

Implementation du metier agriculteur (Lot A) cote moteur. Un colon assigne fermier va le jour vers le champ de ble libre le plus proche, s y stationne (etat FARMING) et le champ produit du grain au rate provides.grain_per_tick lu dans buildings.json. Capacite stricte 1 fermier par champ. Sans fermier en FARMING, un champ ne produit plus rien (la production automatique precedente est retiree).

Comportement

  • placements.js : addWheatField attribue desormais un id stable et assignedColonistIds: [] a chaque champ. Helpers findWheatFieldById(id) et releaseFromWheatFields(colonistId) exposes pour le moteur.
  • state.js : ajout de wheatFieldNextId (compteur d ids stables, comme researchBuildingNextId).
  • colonist.js : nouveau champ assignedFieldId par colon, persiste a la save. Etat FARMING (similaire a RESEARCHING). En IDLE, si profession agriculteur + assignedJob farmer + jour + tech wheat-field debloquee, le colon trouve un champ libre, s y rend, bascule en FARMING. La nuit ou changement de metier, il libere le champ et revient IDLE. Le champ reste assigne entre deux journees pour qu il y revienne le lendemain. dispose() libere aussi le champ. _hasNoTaskForProfession etendu a agriculteur (idle bubble si tous champs occupes).
  • main.js : production de grain conditionnee a la presence d un fermier reellement en FARMING sur le champ. Rate 0.08/tick (2s) lu dynamiquement dans buildings.json via BUILDINGS_DATA.
  • persistence.js : serialisation/restauration de id, assignedColonistIds par champ, wheatFieldNextId global, assignedJob et assignedFieldId par colon. Reset propre dans clearEverything.
  • ui/population-modal.js : ajout de l entree agriculteur dans JOB_DEFS avec techGate: 'wheat-field'. Le metier reste invisible tant que la tech n est pas debloquee. Mapping agriculteur -> farmer ajoute dans PROFESSION_TO_ASSIGNED_JOB.

Tests mentaux valides

  • Passer en age 2, debloquer wheat-field, construire un champ, assigner un fermier : le colon part au champ, du blé est credite.
  • 2e fermier avec un seul champ : reste IDLE (capacite 1 stricte).
  • 2e champ construit : 2e fermier y va.
  • Reload mi-FARMING : la save retient assignedFieldId et assignedColonistIds. Au boot, le colon redemarre en IDLE et retourne au champ assigne.
Lot B, recompenses de quete diversifiees (resource, boost, colonist) 2026-05-04

Le schema rewards (Lot A v1) supporte desormais quatre types de recompenses (research, resource, speedBoost, colonist). Le moteur applique ces recompenses a la completion d une quete et notifie le joueur via toast HUD.

Comportement

  • quests.js : ajout de applyRewards / applySingleReward qui parcourent le tableau rewards[] et creditent les ressources via state.resources[id], ajoutent les points de recherche, posent un boost de vitesse global temporaire (state.speedBoost = { factor, expiresAt }) ou spawnent N colons au Cairn ou pres de la premiere maison via spawnColonsAroundHouse.
  • quests.js : normalizeRewards convertit l ancien objet reward { researchPoints, wood, ... } en tableau Lot A v1, retro compatibilite preservee pour les quetes legacy non encore migrees.
  • quests.js : toast HUD a la completion listant titres et recompenses (ex : "Quete reussie : Recolte de baies (+30 recherche, +30 baies, +20% vitesse 60s)").
  • quests.js : tick tickSpeedBoost decrémente le boost et le supprime a expiration. Toast d annonce de fin. Badge HUD vert pendant la duree active (mis a jour chaque frame avec compte a rebours).
  • state.js : nouveau champ state.speedBoost = null.
  • productivity.js : helper getGlobalSpeedFactor(state) exporte ; computeJobProductivity multiplie le resultat par ce facteur (s applique a la recherche).
  • colonist.js : la progression de chantier multiplie aussi par getGlobalSpeedFactor(state), le boost accelere donc construction comme recherche.

Quetes catalogue migrees

Trois quetes hardcodees passees en rewards[] pour exposer les nouveaux types :

  • berries-75 : research 30 + resource berries 30 + speedBoost 60s a +20%.
  • houses-3 : research 20 + resource wood 10.
  • colons-8 : research 25 + colonist 1.

Les autres quetes restent en format reward legacy, normalisees a la volee.

Lot B, fix trajet chercheur vers le promontoire la nuit 2026-05-04

Suite aux fix precedents, le compteur nightPoints ne progressait toujours pas en pratique : le chercheur, libere de la Hutte du sage a la tombee de la nuit, ne se deplacait pas visiblement vers le Promontoire. Cause racine : pickObservatory posait isWandering = true, ce qui declenchait l annulation immediate du trajet dans la branche MOVING des qu un job apparaissait (state.jobs.size > 0). En presence quasi-permanente de jobs joueurs, le chercheur n atteignait jamais le promontoire.

Comportement

  • colonist.js : nouveau flag observatoryTarget (par colon). Pose par pickObservatory quand le path est valide, coupe par MOVING en fin de path (passage propre en IDLE) ou si le jour se leve en cours de trajet (rebascule IDLE pour aller a la Hutte du sage).
  • colonist.js : pickObservatory n active plus isWandering. Le path n est donc plus vulnerable a la cancellation par state.jobs.size > 0.
  • colonist.js : pickObservatory retombe sur findApproach si aStar echoue (palier trop haut sur la cellule du promontoire), pour deposer le chercheur sur une cellule voisine atteignable, coherent avec la tolerance Manhattan <= 1 de isObservatoryOn et isColonistOnObservatory.
Lot B, fix production night points au promontoire 2026-05-04

Apres la migration astronome -> chercheur (commit f73b67d), le compteur nightPoints ne progressait plus la nuit meme avec un chercheur visiblement present au promontoire. Cause : le pathfinding deposait souvent le colon sur une cellule adjacente au promontoire (palier de hauteur trop important pour atteindre la cellule exacte), or isColonistOnObservatory exigeait une egalite stricte sur (x, z).

Comportement

  • daynight.js : isColonistOnObservatory accepte desormais Manhattan <= 1 autour de la cellule du promontoire (cellule meme + 4 voisins).
  • placements.js : isObservatoryOn aligne sur la meme tolerance, pour rester coherent avec pickObservatory (le colon ne reesssaiera pas un trajet s il est deja dans le voisinage) et avec stayingOnObservatory (le chercheur reste IDLE au promontoire la nuit, ne part pas au campfire).
Lot B, refonte recherche nocturne (option A) 2026-05-03

Les points nocturnes deviennent une ressource accumulee par le temps passe en mode nuit avec un astronome assigne sur un promontoire. Une fois accumules, ils peuvent etre depenses a tout moment (jour ou nuit) pour debloquer des techs avec cost.night > 0.

Comportement

  • daynight.js : production basee sur computeJobProductivity agregee sur les astronomes sur promontoire. Flux continu (par seconde, plus par paliers de 5 s). Conditionnee a state.isNight === true. Pas de plafond. Synchro state.nightPoints (int) avec un accumulateur float interne.
  • tech.js : queueTech ne bloque plus une tech cost.night > 0 en mode jour. Les points nocturnes sont consommes a la completion via unlockTech. Si stocks insuffisants : retour false, la recherche reste plafonnee a 100 % et reessaiera chaque tick.
  • main.js : deja en place, plafonne activeResearch.progress quand unlockTech echoue et reessaie au tick suivant.
  • ui/techtree-panel.js : suppression du bouton "Disponible uniquement la nuit". Affichage du cout night dans la fiche. Bouton "Ajouter a la file" affiche le cout night, tooltip explicatif si stocks insuffisants.
  • ui/techtree-node.js : cout night affiche dans la card.

Bug Astronomie I

La tech Astronomie I est desormais debloquable de maniere coherente : mise en file de jour, progression normale, completion au moment ou les stocks de points nocturnes suffisent.

TODO

  • Tutoriel premier deblocage tech nocturne (Lot E).
(session 18) : Lot A -- corrections conformite pilotage v0.3 2026-04-23

Conformite verifee

Tous les JSON et gamedata.js ont ete relus et compares au pilotage (age-pierre-pilotage-v0.9.html, anciennement -v0.3 et -v0.4).

Corrections apportees dans techtree.json

  • spear-wood renomme de "Arc" (icone fleche) en "Lance en bois" (icone boomerang). Le pilotage v0.3 Checklist branche Outils nomme explicitement "Lance en bois".
  • pick-stone : ajout de "copper" et "coal" dans unlocks.resources, conformement a l'onglet Contrat du pilotage qui montre ces filons "visibles mais non exploitables age I".
  • Description de pick-stone mise a jour pour mentionner la visibilite des filons Bronze.

Fichiers conformes (aucune modification)

  • data/buildings.json : conforme, tous les batiments age I presents (Foyer, Abri, Cabane, Longhouse, Hutte du sage, Champ, Buisson, Potager, Promontoire, Totem, Cairn).
  • data/jobs.json : conforme, 8 metiers ages I (Cueilleur, Bucheron, Mineur, Chasseur, Chercheur, Astronome, Terrassier, Explorateur).
  • data/needs.json : conforme, 3 besoins ages I (Faim, Sans-abri, Blesse).
  • data/resources.json : conforme, 13 ressources dont 2 stubs age II (Cuivre, Charbon).
  • modules/gamedata.js : conforme, 5 accesseurs exposes (getTechsForAge, getBuildingById, getJobsRequiringTech, getResourceById, getNeedsForAge), linter croise actif, 7 tests unitaires purs.

Linter au boot

Simulation Node.js du linter : 0 erreur de reference croisee sur les 5 JSON.

Fichiers modifies

  • public/strates/editor/data/techtree.json
(session 17) : Lot C -- pulse visuel tech en cours de recherche 2026-04-23

Feature 2 de la maquette v2

  • techtree-node.js ajoute la classe ttp-research sur les nodes dont le status est researching, en plus de la classe researching.
  • techtree.css ajoute un pseudo-element ::before sur .ttp-tech.ttp-research (inset -4px, border 1px couleur de branche, border-radius 8px), animé par @keyframes ttp-pulse (2.4s ease-in-out infinite, opacity 0.6 -> 0 et scale 1 -> 1.04). Le glow --vc sur la card est reconfirme pour que la couleur de branche reste visible.
  • Les Features 1 (branch header hexagonal) et 3 (vue constellation globale) etaient deja implementees dans les sessions precedentes.

Fichiers modifies

  • public/strates/editor/modules/ui/techtree-node.js
  • public/strates/editor/styles/techtree.css
(session 16) : Lot C -- hover highlight prerequis, fiche droite, rail file de recherche 2026-04-23

Hover highlight des prerequis

  • Survol d'une tech locked / available / ready : les prerequis passent en .req-highlight (halo pulsed or, animation ttp-reqpulse), les liens SVG correspondants en .req-hi (stroke or, glow), les autres techs en .faded (opacity 0.15, saturate 0.3).
  • Handlers mouseenter / mouseleave branchés sur chaque node lors de renderBranchDetail. Les path SVG exposent data-from / data-to pour le ciblage.

Fiche droite (340px)

  • Nouvel aside.ttp-fiche dans .ttp-detail-view, à droite du canvas. Header avec icone 32px, nom serif 20px, nom de branche coloré. Sections Cout, Description, Murmure (citation italique avec bordure gauche dorée), Prerequis (mini-cartes cliquables pour naviguer), Debloque (jobs / batiments / ressources / outils depuis tech.unlocks).
  • Pied de fiche : bouton contextuel (Debloque / Annuler la recherche / Retirer de la file / Ajouter a la file / Mettre le chemin en file).
  • Ouverture de branche auto-selectionne la première tech interessante (active > ready > first).

Rail file de recherche

  • Nouveau div.ttp-queue positionne en bas à droite (right: 360px pour cohabiter avec la fiche), visible uniquement en mode detail. Capacite 5 slots : tete = tech active (64px, bord doré, anneau SVG de progression), suivants = queue (54px bulles circulaires). Bouton × au hover pour retirer (via cancelResearch). Placeholders pointillés + pour slots vides. Click sur un slot sélectionne la tech correspondante.
  • "Mettre le chemin en file" : calcule recursivement l'ordre topologique des prerequis non debloqués et enfile jusqu'a QUEUE_MAX = 5.

Fichiers modifies

  • public/strates/editor/modules/ui/techtree-panel.js
  • public/strates/editor/styles/techtree.css
(session 15) : Lot C -- UI file de recherche (boutons etat, barre progression, popup completion) 2026-04-23

Nouveaux etats de cards

  • techtree-node.js : bouton "Mettre en file &rarr;" pour les techs ready / available, disabled "En cours..." pour researching (+ barre de progression .ttp-tech-bar en bas de card), disabled "En file (N)" pour queued, disabled "Verrouille" pour locked, badge doré "&check; Acquis" pour done. Cost row affiche progress/cost en chiffres quand recherche active.
  • techtree-panel.js : techStatus() reconnait researching (tech == state.activeResearch.id) et queued (tech dans state.researchQueue). Helpers activeResearchId, activeResearchProgress, queuePositionOf. queueLocal(id) remplace unlockLocal et appelle queueTech(id) importee de tech.js.
  • techtree.css : styles pour .ttp-tech.researching (glow bleu, barre .ttp-tech-bar animee), .ttp-tech.queued (bordure dashed doree), bouton disabled, badge .ttp-tech-acquis.

Refresh dynamique

  • Ecoute des events strates:researchStarted, strates:queueChanged, strates:techComplete pour redessiner immediatement le tree.
  • Timer 2Hz (setInterval 500ms) quand le panneau est ouvert et qu une recherche est en cours, pour animer la barre sans refresh total permanent.
  • renderConstellation() ajoute la progression partielle de activeResearch dans la .bar de la branche concernee (done + fraction progress/cost).

Popup de completion

  • Nouveau module modules/ui/research-popup.js. Injecte (une seule fois) un element #tech-complete-popup + styles inline dans le head. Ecoute strates:techComplete et affiche un toast en bas au centre pendant 4s avec icone, nom, et liste des unlocks (jobs / buildings). Transitions fade-in/out 300ms, backdrop-filter blur, cohérent palette --gold --panel --ink. Installe automatiquement par initTechTreePanel().

Fichiers modifies

  • public/strates/editor/modules/ui/techtree-node.js
  • public/strates/editor/modules/ui/techtree-panel.js
  • public/strates/editor/styles/techtree.css
  • public/strates/editor/modules/ui/research-popup.js (nouveau)
(session 15) : Lot B -- file de recherche (queueTech, activeResearch) 2026-04-23

Mecanique

  • Les techs ne se debloquent plus instantanement quand researchPoints atteint le cout. Le joueur enfile une tech via queueTech(id), la tech passe en state.activeResearch = { id, progress }, la progression monte de n (nombre de chercheurs actifs) toutes les RESEARCH_TICK secondes. A progress >= cost, unlockTech est appelee automatiquement et la file avance.
  • state.researchQueue (Array<string>) est FIFO, state.activeResearch est null quand rien n est en cours.

Fichiers

  • modules/state.js : ajout de researchQueue: [] et activeResearch: null.
  • modules/tech.js : nouvelle fonction exportee queueTech(id) qui enfile ou demarre la recherche, cancelResearch(id) qui retire de la file ou annule l active. unlockTech accepte desormais un 3e arg opts.alreadyPaid pour que le chemin "completion depuis le tick moteur" ne rededuise pas le cout en points. Log des effets unlocks.jobs / unlocks.buildings (cablage reel dans un ticket dedie).
  • main.js : le tick RESEARCH_TICK incremente state.activeResearch.progress au lieu de state.researchPoints. A la completion, unlockTech(id, refreshTechsPanel, { alreadyPaid: true }) + event strates:techComplete, puis avancement auto de la queue (strates:researchStarted). HUD rPointsEl affiche progress / cost tant qu une tech est active. Exposition window.StratesResearch = { queue, unlock } pour consommation externe.
  • modules/colonist.js : en IDLE, si state.activeResearch != null, que le colon isChief n a pas de researchBuildingId et qu une hutte du sage existe, il s auto-assigne a state.researchHouses[0].id et part en MOVING vers le batiment. Indispensable en debut de partie ou le chef est le seul colon disponible.
  • modules/persistence.js : serialisation et restauration de researchQueue et activeResearch, reset dans clearEverything.

Contrat API pour Lot C (UI tech tree XXL)

  • window.dispatchEvent de 3 events : strates:queueChanged, strates:researchStarted { detail: { id } }, strates:techComplete { detail: { id, tech } }.
  • techtree-panel.js importe deja queueTech, cancelResearch depuis ../tech.js (session precedente Lot C) et lit state.activeResearch, state.researchQueue pour l affichage status (researching, queued).
(session 15) : Lot C -- B8bis, B14, B15, B16 2026-04-23

B8bis -- suppression panneau save/load haut écran

  • index.html : suppression complete du bloc #save-controls (boutons Sauver / Charger / Nouvelle) en haut de l'écran. Faisait doublon avec le menu Échap. Le bouton disquette rapide #btn-quicksave (U8) reste.

B14/B15 -- fix TypeError classList null + bouton ? aide

  • index.html : correction des deux sélecteurs null-sûrs qui déclenchaient TypeError: Cannot read classList of null au chargement (ligne 1532) et au clic sur le bouton ? (ligne 1514). Guard null ajouté avant chaque accès .classList. Le bouton ? en bas à droite ouvre désormais l'aide correctement.

B16 -- son été synthétique via Web Audio API

  • modules/audio.js : son synthétique saison Été généré par Web Audio API (même approche que la fanfare Bronze). Remplace la référence manquante audio/ete.mp3 (404). Cohérence avec les autres sons synthétiques du projet.
(session 15) : Lot B -- REVERT B10 + B17 + B18 + B19 2026-04-23

REVERT B10 -- desactivation auto-collecte colons

  • modules/colonist.js : pickAutoCollect() et son call site dans la machine a etat IDLE sont commentes avec la mention desactive - remplace par systeme 3 boutons (pioche/hache/baie). Le code est conserve pour reprise ulterieure. Les colons IDLE ne collectent plus de ressources tout seuls, ils restent au repos ou flanent.

B17 -- protection fondation batiments contre minage

  • modules/placements.js : isMineBlocked(x, z) etend la protection au-dela de isHouseOn (qui couvrait deja houses, manors, researchHouses). Ajout de la verification sur state.observatories et state.cairns pour qu aucune fondation de batiment pose ne puisse etre minee. Les appels existants dans interaction.js (toolAllowedOnCell, applyToolAtCell, applyToolToStrata) rejettent deja le clic de minage quand isMineBlocked retourne true, ce qui assure le feedback visuel existant (curseur not-allowed).

B18 -- arbres animation recolte resolue par revert B10

  • Aucun code ne declenchait d animation de branches d arbre hors du chemin pickAutoCollect desactive. Seule la pousse progressive via tickTreeGrowth subsiste (growth < 1 sur 12 s). Commit de constat, bug resolu par le revert B10.

B19 -- condition Cairn sur total cumule depense

  • modules/state.js : nouveau champ totalResearchSpent (init 0, jamais decremente).
  • modules/tech.js (unlockTech) et modules/ui/techtree-panel.js (unlockLocal) : chaque deblocage incremente state.totalResearchSpent += cost en plus de decrementer le solde.
  • modules/age-transitions.js : canBuildCairn, getCairnProgress et le tooltip conditions lisent desormais state.totalResearchSpent au lieu du solde courant state.researchPoints. Le label devient "100 pts recherche depenses". Cause racine : B11 gele l accumulation quand toutes les techs dispo sont prises, le solde pouvait donc rester inferieur a 100 meme apres avoir depense plus de 100 pts.
  • modules/persistence.js : serialisation et restauration de totalResearchSpent, reset dans clearEverything.
  • modules/worldgen.js : reset de totalResearchSpent dans le reset monde.
(session 15) : Lot C -- U7 palette vignette grisee batiment unique 2026-04-23

U7 -- styles CSS + refresh HUD pour batiment unique deja pose

  • index.html : nouvelle classe .tool.disabled-unique (opacity 0.4, cursor not-allowed, filter grayscale, suppression des effets hover et active). Appliquee sur les boutons data-tool de la palette actionbar.
  • modules/hud.js : nouvelle fonction exportee refreshUniqueBuildingsPalette() appelee depuis refreshHUD(). Mapping TOOL_TO_BUILDING_ID (research -> hutte-du-sage, cairn -> cairn-pierre) et INSTANCE_CHECKS (lectures dediees de state.researchHouses et state.cairns). Helper buildingIsUnique(id) lit BUILDINGS_DATA pour valider le flag unique. Cohabite proprement avec la logique Lot B (placements.checkUniqueBuildingButtons) qui applique la meme classe depuis un tick lent.
  • Scope UI respecte : aucune modification de placements.js, interaction.js, data/*.json.
(session 14) : Lot B -- B10, B11 2026-04-23

B10 -- auto-collecte colons au repos (revert en session 15)

  • modules/colonist.js : ajout de pickAutoCollect() (rayon Manhattan 8, priorité LEISURE, jour uniquement). Les colons IDLE ciblaient rochers puis arbres. Désactivé en session 15 (voir REVERT B10) au profit du système 3 boutons (pioche/hache/baie) à venir.

B11 -- gel points de recherche si toutes techs débloquées

  • modules/tech.js : nouvelle fonction hasPendingResearchableTech(), filtre les techs de l'âge courant non débloquées dont les prérequis sont remplis.
  • main.js : accumulation des points de recherche gelée si hasPendingResearchableTech() retourne false.
(session 14) : Lot B -- U7 consommation flag unique batiment 2026-04-23

U7 -- bouton de pose grise pour batiment unique deja pose

  • modules/placements.js : nouvelles fonctions countBuildingInstances(id), isBuildingUniqueAndPlaced(id) et checkUniqueBuildingButtons(). La premiere lit la map interne UNIQUE_STATE_ARRAY_BY_BUILDING (hutte-du-sage -> researchHouses, cairn-pierre -> cairns). La deuxieme combine getBuildingById(id).unique === true avec le comptage d instances dans state. La troisieme parcourt la map UNIQUE_TOOL_TO_BUILDING et applique la classe disabled-unique + disabled=true + pointer-events none sur le bouton correspondant quand le batiment est pose.
  • modules/interaction.js : guard isBuildingUniqueAndPlaced('hutte-du-sage') avant placement dans les deux branches case 'research' (pose simple et pose strata). Empeche la double pose meme si le bouton n est pas encore grise au moment du clic.
  • main.js : appel de checkUniqueBuildingButtons() a chaque tick lent (~1s), a cote de checkCairnOverlay(). Import ajoute depuis placements.js.
  • Scope respecte : aucune modification de data/*.json, ni de modules/ui/*, ni de hud.js. Le bouton Cairn garde son traitement propre dans age-transitions.js (cinematique + conditions cumulatives).
(session 14) : Lot C -- B8 B9 B12 B13 + U5 U8 2026-04-23

B8 -- save/load sans chevauchement panneau quetes

  • index.html : #save-controls repositionne a top: 16px; right: 312px (decale de la largeur du panneau #quests + marge) pour eviter la superposition en haut a droite.

B9 -- aide sans references editeur terrain

  • index.html : suppression complete de l'ancien #help-panel (et de #help-hint, classes .help-body, .help-header) qui documentait les outils godmod editeur (Foret, Rocher, Filon, Champ, Baies, cadre bleu). L'aide active reste #help-overlay style Clair Obscur, deja centree sur le mode jeu.

B12 -- liens SVG tech tree alignes sur bords cards

  • modules/ui/techtree-panel.js : refonte du calcul des paths Bezier. Sortie au milieu du bord droit de la card source, entree au milieu du bord gauche de la cible. Tangentes horizontales proportionnelles a la distance (tension 0.5) au lieu de midX systematique. Cas particulier meme colonne (prereq vertical) : ancrage haut/bas avec tangentes verticales.

B13 -- filtres branches tech tree operationnels

  • modules/ui/techtree-panel.js : logique inversee. filter.branches est desormais l'ensemble des branches SURBRILLANCE (cliquees). null = aucun filtre actif, toutes visibles. Clic sur un filtre ajoute/retire la branche de l'ensemble. Ensemble vide = retour a null.
  • styles/techtree.css : nouvelle classe .ttp-node--faded (opacity 0.2, grayscale 0.6, pointer-events none) appliquee aux cards hors branche filtree. Remplace ttp-node--hidden (display:none) qui masquait totalement et cassait la lisibilite structurelle du tree.

U5 -- renommer ordre Placer en Placer bloc

  • index.html : le bouton data-tool="build" dans la barre ordres affiche desormais Placer bloc au lieu de Placer, avec tooltip clarifiant qu'il s'agit de poser un bloc puise dans les stocks (terre ou pierre). Le raccourci 3 est inchange.

U8 -- bouton disquette sauvegarde rapide bas droite

  • index.html : nouveau bouton circulaire flottant #btn-quicksave (48x48 px, position fixed bottom:16 right:16). Au clic, appelle saveGame('auto') via un petit module inline qui importe modules/persistence.js. Feedback visuel : le bouton passe en vert 1.4 s avec toast "Partie sauvegardee". Placeholder UI en attendant la refonte complete.
(session 14) : Lot A -- U6 U7 hutte du sage flag unique 2026-04-23

U6 U7 - Champs unique et _note sur la Hutte du sage

  • buildings.json : ajout de "unique": true et "_note": "flag unique consomme par Lot B" sur le batiment hutte-du-sage.
  • L'id hutte-du-sage etait deja correct depuis la session 11 (renommage precedemment fait). Aucune reference residuelle a un id recherche dans techtree.json, jobs.json ni gamedata.js.
  • Linter cross-fichiers non impacte : toutes les references croisees restent valides.
(session 13) : Lot C -- corrections bugs B4, B6, B7 + quick wins U1 U2 U3 U4 2026-04-21

B4 - Tech tree age II placeholder apres transition Bronze

  • techtree-panel.js : techStatus() et matchesQuery() utilisent desormais state.currentAge comme source de verite (au lieu de la valeur statique age >= 2). Les colonnes d'ages inferieurs ou egaux a l'age courant ne sont plus floutees.
  • Aucune tech n'etant encore definie pour l'age 2 dans techtree.json, un placeholder "Techs Bronze, a venir dans une prochaine session" s'affiche dans la colonne de l'age debloque mais sans tech.
  • Les classes visuelles ttp-age-head--locked et ttp-col--locked sont desormais calculees par comparaison ageNum > state.currentAge.

B6 - Filtres branche fonctionnels

  • techtree-panel.js : les liens SVG entre prerequis sont masques si au moins un des deux noeuds appartient a une branche cachee (ajout de branchId dans la cartographie nodePos). La logique des toggles sur les boutons etait correcte mais les liens n'etaient pas filtres et le feedback visuel etait faible.
  • styles/techtree.css : les boutons de filtre inactifs (branches masquees) ont maintenant un style barre (line-through), couleur attenuee, dot diminue. Contraste fort avec les boutons actifs.

B7 - Escape ferme panneau sans ouvrir le menu pause

  • modules/interaction.js : le listener Echap verifie desormais l'etat de trois panneaux plein ecran (#ttp-root.open, #char-panel.open, #help-overlay.open) avant de traiter le menu pause. Si un panneau est ouvert, l'evenement est ignore et le panneau gere sa fermeture via son propre listener. Le menu pause ne s'ouvre plus que lorsqu'aucun panneau n'est visible.

U1 - Suppression de la fenetre Tech flottante obsolete

  • index.html : retrait complet du bloc #techs (mini panel en haut a droite) et de son CSS (~80 lignes). Ajout d'un bouton flottant #btn-techtree-float (icone diagramme) sous #btn-audio pour ouvrir l'arbre XXL.
  • main.js : cablage du nouveau bouton, compatibilite avec l'ancien id preservee.
  • hud.js : refreshTechsPanel() devient silencieuse (early return si techsBodyEl absent).

U2 - Boutons save/load/nouvelle partie deplaces

  • index.html : retrait des 3 boutons depuis la barre #actionbar > .group.controls. Creation d'une zone dediee flottante #save-controls en haut a droite (pill avec blur, icones disquette/dossier/recycle, bouton Nouvelle en rouge pour clarte destructive). L'actionbar ne contient plus que des outils.

U3 - Compteur temperature dans HUD meteo

  • index.html : ajout d'un span #season-temp dans #season-pill.
  • main.js : valeurs indicatives fixes par saison (Printemps 12°, Ete 22°, Automne 10°, Hiver -2°) mises a jour a chaque tick HUD saison.

U4 - Page aide version courte par defaut + Voir tout

  • index.html : nouvelle section .ho-essentiel en haut du .ho-body avec 5 lignes (deplacement ZQSD + souris, miner 2, placer 3, tech tree T, Echap pour sauvegarder). Wrapping #ho-full-sections (attribut hidden) autour des anciennes sections Camera, Outils, Actions, Menus. Bouton #help-toggle-full style dore.
  • help-overlay.js : gere le toggle du bouton (texte "Voir tout" / "Masquer le detail", bascule l'attribut hidden).
(session 13) : Lot D -- corrections bugs B2, B3, B5 2026-04-21

B2 - Son fanfare retravaille

  • modules/cinematics.js : la fanfare synthetique utilise desormais 3 oscillateurs sine en accord Do majeur (Do4 261.63 Hz, Mi4 329.63 Hz, Sol4 392.00 Hz) avec enveloppe ADSR douce (attaque 0.05s, gain max 0.15, decay progressif sur 1.5s). Suppression des ondes square, du bruit impulsionnel et du filtre lowpass agressifs.

B3 - Rendu voxel Cairn

  • modules/placements.js : nouvelle fonction makeCairn() (colonne de 4 blocs BoxGeometry pierre grise empiles, scales decroissants, etoile OctahedronGeometry doree au sommet), addCairn(gx, gz) qui place le Group Three.js dans la scene et pousse dans state.cairns, findFreeCellNear(cx, cz, maxRadius) pour trouver une cellule libre en spirale autour du spawn.
  • modules/age-transitions.js : _onCairnClick appelle findFreeCellNear puis addCairn avant de declencher la cinematique (delai 200ms pour laisser le mesh apparaitre).
  • modules/persistence.js : serialisation cairns[], restauration et reset dans clearEverything.
  • modules/state.js : champ cairns: [] ajoute dans Lot D.

B5 - Bouton Cairn unique

  • modules/age-transitions.js : _cairnAlreadyBuilt() detecte si state.cairns.length > 0 ou state.currentAge >= 2. checkCairnOverlay() grise le bouton (opacity 0.4, pointer-events none, cursor not-allowed) quand le Cairn est deja pose. initAgeTransitions() appelle checkCairnOverlay() au boot pour gerer le reload apres passage en Bronze.
(session 12) : Lot C -- hook refreshTechTreeAfterAgeChange pour Lot D 2026-04-21
  • Nouvelle fonction exportee refreshTechTreeAfterAgeChange(age) dans modules/ui/techtree-panel.js, egalement exposee sur window pour consommation par age-transitions.js (Lot D) sans import direct.
  • Si le panneau est ouvert au moment de l'appel, re-render immediat (les techs nouvellement debloquees passent de teased a available/ready). Sinon, flag dirty consomme au prochain openTechTreePanel().
  • Source de verite inchangee : TECH_TREE_DATA.ages[].unlocked (SPEC v1). La fonction force uniquement le re-render DOM.
(session 12) : Lot C -- UI Tech tree XXL (pan, zoom, filtres, anti-spoiler) 2026-04-21
  • Nouveau panneau plein ecran modules/ui/techtree-panel.js + noeuds modules/ui/techtree-node.js, feuille de style dediee styles/techtree.css.
  • Ouverture via touche **T** ou bouton HUD (cablages existants dans main.js, non modifies). Fermeture via **Echap**, clic backdrop, ou bouton Fermer.
  • Grille 7 ages x 6 branches, lecture directe de TECH_TREE_DATA (SPEC v1). ROW_H dynamique selon empilage max de techs dans la ligne (utile pour la branche outils age I, 4 techs).
  • Etats visuels des noeuds : locked (opacite 0.3), available (bordure neutre), ready (glow bleu + bouton Rechercher), researching (progress bar, reserve), done (glow dore), teased (flou + ?????). Les teased sortent leur tech.id, branch et cout du DOM pour prevenir toute lecture via devtools sur les ages 2+.
  • Liens SVG Bezier entre prerequis, stroke dore plein si prereq debloque, pointille gris sinon. Les teased ne recoivent ni n'emettent de lien.
  • Pan : drag souris sur le stage (cursor grab/grabbing). Zoom : molette de 0.5x a 2x, centre sur la position du curseur. Une seule transform CSS translate+scale, perf OK pour 48x48x8+.
  • Filtres par branche : toggle par branche en haut, bouton Toutes pour reset. Recherche texte : filtre en opacity 0.18 les noeuds dont le nom ou l'id ne contient pas la query. Les teased ne matchent jamais (preserve la surprise).
  • Deblocage : unlockLocal puise dans state.researchPoints, ecrit state.techs[id].unlocked = true, appelle refreshTechsPanel puis re-render. Pas de modification de data/*.json, pas de modification de tech.js, jobs.js, needs.js (scope Lot C respecte).
  • techtree-ui.js devient un shim qui delegue au nouveau panel, ce qui evite de toucher a main.js.
(session 12) : Lot B -- moteur comportemental, besoins Faim + Sans-abri 2026-04-21
  • **Pre-fix SPEC v1** (commit lot-B: pre-fix SPEC v1) : main.js injecte les techs du JSON gele en normalisant cost: { research: N } vers number et requires: [] vers le champ plat req, le champ requires[] etant conserve a cote. tech.js/unlockTech accepte un tableau de prerequis (iteration sur t.requires) en plus du t.req legacy.
  • Nouveau module **modules/needs.js** : lecture needs.json via gamedata.js, initialisation c.needs Map par colon, tick des jauges (hunger monte a need.rate * 100% /s), evaluation des regles (shelter via assignedBuildingId), calcul productivityMul compose en parsant effects.productivity_N%. Seuils THRESHOLD_LOW = 50%, THRESHOLD_CRITICAL = 80%.
  • Nouveau module **modules/tasks.js** : scheduler de taches par colon. Constantes PRIORITY (SURVIVAL 100, WORK 60, LEISURE 30, IDLE 10) et TASK_KIND. Fonctions enqueueTask, peekTask, popTask, hasTaskOfKind, clearLowPriorityTasks, canPreempt. Renomme depuis jobs.js pour eviter collision avec les marqueurs de minage du joueur et data/jobs.json (metiers).
  • **state.js** : champs needsTickAccum, needsBuckets (cache d index needs.json).
  • **colonist.js** :
  • instance recoit needs (Map), jobQueue (Array), currentTask, assignedBuildingId, productivityMul, wasAttacked.
  • initColonistNeeds(this) au constructeur.
  • En IDLE, si isNeedCritical(this, 'hunger'), le colon abandonne tout et appelle pickHarvest en priorite SURVIVAL (jour et nuit).
  • Fin de WORKING sur un buisson : si currentTask.kind === EAT_SEEK_FOOD, les baies sont mangees sur place (baisse de hunger data-driven via satisfied_by[berries].amount / 20 par baie), sinon elles vont au stock (cueillette leisure).
  • spawnColonsAroundHouse attribue assignedBuildingId = 'cabane' a chaque colon, fixant le besoin shelter.
  • Restauration de save : assignedBuildingId lu depuis la save si present, sinon attribue 'cabane' par defaut tant qu une maison existe.
  • **main.js** : tickAllNeeds(dt) appele a chaque frame avant la MAJ des colons.
  • productivityMul est expose sur chaque colon. Le cablage avec les vitesses de production (placements.js, tech.js) est un ticket separe post-Lot-B.
  • Scope respecte : pas de modification de data/*.json, ui/*, hud.js, placements.js, techtree-ui.js.
(session 12) : Lot D -- Monument Cairn, conditions Bronze, cinematique de passage 2026-04-21

Nouveaux fichiers

  • modules/age-transitions.js : logique complete du passage Age de Pierre vers Age du Bronze.
  • canBuildCairn(state) : verifie les 7 conditions cumulatives (population, stocks bois/pierre/nourriture, hutte du sage, chercheur, points de recherche). Retourne { ok, missing }.
  • getCairnProgress(state) : ratio 0-1 de progression (pour le badge "Monument proche").
  • triggerAgeTransitionBronze() : declenche la cinematique puis applique la bascule d'age.
  • initAgeTransitions() : injecte le bouton Cairn dans le groupe "Monument" de l'actionbar.
  • checkCairnOverlay() : appele en tick lent (1s), met a jour le badge et l'etat visuel du bouton.
  • Flag DEV_SKIP_BONES = true : bypass la condition "os" tant que Lot B (Chasseur) n'est pas livre.
  • modules/cinematics.js : cinematique generique playCinematic({ title, subtitle, onEnd }).
  • Fanfare synthetique Web Audio API (aucun fichier MP3).
  • Sequence : fade noir 1s, titre "AGE DU BRONZE" 2s, son fanfare 0.5s, fade retour 1s.
  • styles/cinematic.css : overlay noir, titre epique, badge "Monument proche", tooltip conditions Cairn.

Modifications

  • modules/state.js : ajout currentAge = 1, ageUnlockedAt, achievements.
  • modules/persistence.js : serialisation et restauration de ces trois champs. Reinitialisation dans clearEverything().
  • modules/gamedata.js : ajout getBuildingsForAge(n) et getTotalFood(state).
  • index.html : <link> vers cinematic.css, trois divs Lot D (#cinematic-overlay, #cairn-overlay-badge, #cairn-conditions-tooltip).
  • main.js : import et appel de initAgeTransitions() au boot, checkCairnOverlay() dans le tick lent.

Definition of Done partielle

  • Conditions verifiees, bouton Cairn grise (visuel) si conditions non reunies, tooltip au survol.
  • Badge "Monument proche" apparait quand progression > 80%.
  • Cinematique : fade noir + titre "AGE DU BRONZE" + fanfare + fade retour.
  • state.currentAge = 2, state.achievements enregistre l'evenement, sauvegarde checkpoint auto.
  • Persistance : reload apres passage confirme state.currentAge === 2.

Ticket ouvert pour Lot C

  • techtree-panel.js doit tenir compte de state.currentAge dans techStatus() pour que les techs Bronze deviennent "recherchables" (et non "teased") apres la transition. La condition actuelle (tech.age >= 2) => 'teased' ne lit pas state.currentAge. Lot D appelle deja refreshTechTreeAfterAgeChange(2) en import dynamique -- Lot C doit exposer cette fonction.
(session 11) : Lot A -- SPEC v1 gelee, JSON data-driven age I complet 2026-04-21
  • **BREAKING** : data/techtree.json et data/buildings.json protos remplacés par les versions SPEC v1 gelées.
  • Nouveau data/resources.json (13 entrees) : toutes les ressources age I + stubs cuivre/charbon age II.
  • data/techtree.json reécrit (SPEC v1) : 7 ages (1 actif, 2-7 stubs), 6 branches, 13 techs age I exhaustives.
  • data/buildings.json reécrit (SPEC v1) : 11 batiments age I (habitation, production, recherche, alimentation, nocturne, monument). Cairn avec onBuild: trigger_age_transition_bronze.
  • Nouveau data/jobs.json (8 metiers age I) : Cueilleur, Bucheron, Mineur, Chasseur, Chercheur, Astronome, Terrassier, Explorateur.
  • Nouveau data/needs.json (3 besoins age I) : Faim, Sans-abri, Blesse.
  • modules/gamedata.js etendu : charge les 5 nouveaux JSON, expose 5 accesseurs (getTechsForAge, getBuildingById, getJobsRequiringTech, getNeedsForAge, getResourceById), linter cross-fichiers au boot, 7 tests unitaires purs (runUnitTests()).
  • Schema SPEC v1 gele -- tout changement de schema necessite un bump vers v2 avec validation utilisateur.
  • Note pour Lot B/C : modules/tech.js et modules/techtree-ui.js sont a adapter pour lire les nouveaux JSON au lieu des donnees hardcodees.
(session 10) : MVP cycle jour/nuit (axe 4.2) 2026-04-20
  • Nouveau module modules/daynight.js : toggle manuel via touche **N** ou icône HUD soleil/lune, transition d'ambiance lissée (~1.5s) sur la couleur de fond, fog, directional light (intensité 2.4 le jour, 0.35 la nuit, teinte lunaire bleutée), hemisphere light et uniforms du Sky.
  • Nouvelle ressource state.nightPoints (int) et state.isNight (bool), sérialisées dans les sauvegardes.
  • Nouveau placement "Promontoire d'observation" (touche **P**, bouton dans la barre Construire) : tour simple en pierre et bois avec une étoile dorée au sommet. Quand un colon IDLE y stationne la nuit, +1 point nocturne toutes les 5 secondes.
  • HUD : pastille soleil/lune (clic pour basculer) à côté de la pastille saison, nouvelle ligne "Points nocturnes" dans le panneau Ressources.
  • Colons : la nuit, attirance vers le foyer le plus proche (feu de camp social) depuis l'état IDLE, moral nocturne cumulé tant qu'ils sont proches d'une maison ou d'un manoir. La cueillette de baies (pickHarvest) est désactivée la nuit (activité exclusive jour).
  • Nouvelle chaîne de bâtiments "Astronomie" dans data/buildings.json : Promontoire, Observatoire, Grand télescope, Radio-télescope.
  • Overlay d'aide (touche H) et guide texte mis à jour avec les touches **N** (jour/nuit) et **P** (promontoire).
(session 9) : Data-driven configs JSON (axe 1.5) 2026-04-20
  • Nouveau dossier data/ avec trois fichiers JSON : speech.json (phrases, dialogues, bulles contextuelles), colonists.json (prénoms M/F), quests.json (définitions des quêtes).
  • Nouveau module gamedata.js : charge les trois fichiers en Promise.all au démarrage, exports en live bindings ES6.
  • constants.js allégé de ~200 lignes : ne contient plus que des constantes numériques et THREE.Color.
  • colonist.js, speech.js, tech.js, quests.js importent depuis gamedata.js au lieu de constants.js.
  • quests.js : QUEST_DEFS devient un let initialisé par initQuestDefs() après chargement des données.
  • main.js : await loadGameData() + initQuestDefs() avant buildTerrain().
  • Ajouter une phrase ou une quête ne nécessite plus de toucher au code JS.
(session 8) : Landing enrichie, menu pause, navigation accueil 2026-04-20

Landing page (/strates/index.html)

  • Section "Mécaniques" (4 cartes : colons nommés, saisons, ressources, recherche) avec icônes SVG.
  • Section "Moments de jeu" (3 vignettes CSS animées : premier hiver avec flocons, minage d'un filon avec étincelle, recherche active avec labo).
  • Section "Origine FDA" : logo Forge des Âges, texte de filiation, lien vers jeux.fmenguy.fr/fda/.
  • Bulles rotatives dans la scène héros : cycle toutes les 7 s sur les vraies phrases du jeu.
  • Panneau "Charger une partie" depuis l'accueil : lit le localStorage, affiche les 5 slots manuels + slot auto avec métadonnées (date, nb colons, cycle). Bouton "Charger" grisé si slot vide.
  • Menu contextuel "Continuer / Nouvelle partie" : si une sauvegarde existe, le bouton 01 devient "Continuer" et un bouton "Nouvelle partie" apparaît. La vraie nouvelle partie passe un flag strates-new-game et ignore les saves existantes.

Jeu (editor/)

  • Menu pause (Échap) dans le style du jeu : Continuer, Sauvegardes, Retour à l'accueil.
  • main.js : lecture de strates-pending-load (charger un slot spécifique) et strates-new-game (ignorer les saves) au démarrage.
  • Mini-map retirée (non fonctionnelle, à réintroduire plus tard).

Roadmap

  • Axe 1.5 coché, axes 2.1 et 5.4 cochés, cases détaillées mises à jour.
(session 7) : Abattage d'arbres et hache en pierre (axe 3.2 MVP) 2026-04-19

Nouvelle tech

  • axe-stone (Hache en pierre), coût 4 pts, âge de pierre, sans prérequis. Débloque l'abattage des arbres.

Abattage

  • L'outil **Miner (2)** détecte désormais si la tuile contient un arbre.
  • **Arbre présent** : le colon abat l'arbre, remplit resources.wood, l'arbre est retiré du mesh. Le voxel sous reste en place.
  • **Filon** : extraction (session 6).
  • **Sinon** : minage classique.
  • canMineCell requiert axe-stone pour les tuiles à arbre. Sans la tech, bulle indice bleue "Il nous faudrait une hache pour cet arbre".
  • Nouveau helper isTreeOn(x, z) et chopTreeAt(x, z) dans placements.js.

Audio playlist par âge

  • Refactor modules/audio.js : lecteur <audio> HTML5 avec crossfade 2,5 s.
  • Mapping âge → fichier dans public/strates/editor/audio/stone.mp3, bronze.mp3, iron.mp3, gold.mp3.
  • README dans public/strates/editor/audio/ avec sources suggérées (Pixabay, FMA, OpenGameArt, Incompetech).
  • Âge courant déduit automatiquement des pioches débloquées.
(session 6) : Extraction des filons (axe 3.1) 2026-04-19

Extraction

  • Les filons sont désormais récoltables via l'outil **Miner (2)**.
  • Le colon se rend sur la tuile du filon, anime le minage, puis :
  • **si cellule avec filon** : retire le filon, incrémente le stock correspondant (copper, iron, coal, silver, gold, amethyst), laisse le voxel sous intact.
  • **sinon** : minage normal, retire le voxel top, incrémente stone ou dirt.
  • extractOreAt(x, z) dans placements.js retire proprement le filon et renvoie son type.
  • isMineBlocked allégé : ne bloque plus sur filon, seulement maison et buisson.

Gating tech côté joueur

  • applyToolAtCell et applyToolToStrata appellent désormais canMineCell avant addJob et enregistrent lastBlockedMineTech si la tech manque, ce qui déclenche la bulle indice bleue.
  • Le minerai suit le mapping ORE_TECH existant : cuivre/charbon → pick-bronze, fer/argent → pick-iron, or/améthyste → pick-gold.

Doc

  • Panneau d'aide mis à jour : section dédiée à l'extraction des filons.
(session 5) : Saisons, audio lofi, décorations végétales 2026-04-19

Cycle de saisons

  • Nouveau module seasons.js : 4 saisons (printemps, été, automne, hiver), 120 s chacune, 8 min par cycle complet.
  • Transition linéaire sur les 20 dernières secondes de chaque saison.
  • Palette de tints multiplicatifs par biome (grass, forest, snow, sand) + champs, appliquée sur les voxels top toutes les 2 s.
  • Printemps : herbe vive, fleurs. Été : vert saturé, champs dorés. Automne : feuillage roux/bronze. Hiver : palette ternie, champs au repos.
  • Sauvegarde de l'état saison dans le snapshot (idx, elapsed, cyclesDone).
  • Pastille HUD en haut centre avec icône emoji et nom de saison.

Décorations végétales

  • Nouveau module vegetation.js : 3 InstancedMesh (brins d'herbe, fleurs, épis de blé).
  • Brins d'herbe : dispersés sur biomes grass (dense) et forest (épars), 2 à 6 brins par tuile concernée.
  • Fleurs : 5 couleurs (rose, jaune, lavande, corail, blanc), clairsemées sur herbe (~7% de densité).
  • Épis de blé : 10 à 18 par tuile field, petites barres verticales dorées.
  • buildVegetation() reconstruit tout à partir de cellBiome / cellSurface, appelé après populateDefaultScene, après loadGame et dans resetWorld.
  • clearVegetation() vidage propre lors des resets.

Audio lofi procédural

  • Nouveau module audio.js, Web Audio API, aucun fichier externe.
  • Drone bas deux oscillateurs détunés (La1/Mi2), filtre lowpass, vibrato LFO lent.
  • Arpège aléatoire pentatonique, notes 2-5 s espacées, filtre résonant.
  • Noise bandpass 800 Hz très discret pour texture cassette.
  • Bouton HUD flottant qui toggle mute. État muté par défaut (respect autoplay policies), mémorisé en localStorage.
  • Démarrage au premier geste utilisateur, fondu doux.

Plus

  • Correction persistance : sauvegarde/restauration de l'état saison (idx, elapsed, cyclesDone).
(session 4) : Modularisation ES6 et persistance localStorage 2026-04-19

Modularisation (axe technique 1.1)

  • Découpage de main.js (3533 lignes) en 18 modules ES6 natifs dans public/strates/editor/modules/ : constants, state, rng, scene, terrain, placements, stocks, tech, pathfind, jobs, bubbles, speech, colonist, quests, worldgen, interaction, camera-pan, hud.
  • main.js devient un bootstrap de 25 lignes qui importe et démarre la tick loop.
  • Pas de bundler, imports ES6 natifs relatifs, importmap Three.js inchangée.
  • Etat mutable partagé via un objet container state exporté par state.js (contourne les limites des let exportés).
  • PRNG réassignable via container prng pour permettre à worldgen de changer le seed.
  • Aucune régression fonctionnelle (terrain, colons, pathfind, bulles, jobs, tech tree, Shift+strate, ZQSD, quêtes).

Persistance localStorage (axe technique 1.2)

  • Nouveau module persistence.js avec saveGame, loadGame, hasSave, deleteSave, startAutoSave.
  • Format JSON versionné (v1), clé strates-save-auto.
  • Sauvegarde complète du terrain (heightmap, biomeNoise, cellTop, cellBiome, cellSurface, cellOre), des entités (arbres, rochers, filons, buissons, maisons, labo), des colons (id, nom, genre, chef, position, état, labo assigné), des jobs, stocks, techs, ressources, stats, quêtes.
  • Sauvegarde auto toutes les 30 s, au beforeunload et au passage en onglet caché.
  • Au démarrage, si une save existe elle est chargée automatiquement, sinon populateDefaultScene génère un monde neuf.
  • Nouveaux boutons HUD : "Sauver" (manuel), "Charger" (recharge la save auto), "Nouvelle" (confirme puis efface la save et regénère).
  • Reconstruction du terrain via nouvelle fonction rebuildTerrainFromState() dans terrain.js qui recrée l'InstancedMesh sans régénérer le Perlin.
  • Colonist accepte désormais une option restore pour forcer nom, genre, chef, ty, state, researchBuildingId.
  • addBush retourne désormais le bush créé (au lieu de true) pour permettre la restauration des baies et regenTimer.
(session 3) : Colons vivants, hints, tech tree visuel, monde peuplé 2026-04-19

Colons enrichis

  • Silhouettes retravaillées : torse + jambes + bras + tête + cheveux différenciés M/F.
  • François porte une couronne dorée voxel (base + 4 pointes) à la place de la simple étoile de bandeau.
  • Animation de marche : swing jambes et bras en opposition, bob vertical calé sur le pas.
  • Retour au repos progressif (lerp) quand le colon passe en IDLE, WORKING ou RESEARCHING.

Noms permanents au-dessus de la tête

  • Étiquette nom + genre (+ étoile chef) affichée en permanence au-dessus de chaque colon (auparavant seulement au survol).
  • Bulle de dialogue remontée à y=2.75 pour ne pas chevaucher l'étiquette.
  • Suppression du toggle HUD "Afficher les noms dans les bulles". Les bulles ne portent plus jamais le nom (pensée intérieure du colon).

Bulles indice bleues

  • Nouvelle variante sayHint(line) dessinant une bulle bleu pâle cerclée de bleu vif avec une pastille jaune style ampoule.
  • Utilisée automatiquement pour les situations pédagogiques : minage bloqué par manque de tech, champs posés sans labo, labo sans chercheur.
  • Durée d'affichage rallongée (6 s vs 4 s) pour laisser le joueur lire.

Monde peuplé au spawn

  • Forêts denses en bosquets de 3 à 6 arbres (~55 arbres en zone forêt), plus 15 arbres isolés sur herbe à distance du hameau.
  • 30 rochers répartis (majorité montagne, quelques-uns épars sur herbe).
  • 10 filons initiaux (cuivre, fer, charbon, argent, or) en montagne.
  • 14 buissons de baies distribués sur herbe et forêt.
  • 3 parcelles de champs cultivés pré-posées autour du hameau.

Tech tree visuel

  • Panneau #techs refait : cards avec pastille colorée (pierre grise, bronze, fer, or), nom, âge, barre de progression remplie par les points de recherche accumulés.
  • Connecteurs verticaux entre techs, colorés quand la tech précédente est débloquée.
  • États visuels : locked, available, ready (glow bleu), done (glow doré).
  • Bouton de recherche stylisé (gradient, hover lift).
(session 2) : Stocks, Placer, Tech tree, Bords eau, Shift-sélection 2026-04-19

Stocks de ressources

  • Objet stocks avec 8 types : stone, dirt, copper, silver, iron, coal, gold, amethyst.
  • Incrément automatique au minage selon le biome (incrStockForBiome).
  • Affichage HUD compact sous "Ressources", types vides masqués.

Outil Placer (raccourci 3)

  • Nouveau job buildJobs séparé de jobs.
  • Marker vert clair semi-transparent sur la tile cible.
  • Contraintes : tile libre, hors eau, hauteur max, portée verticale colon <= 3.
  • Exécution : A\*, animation bounce 1,5 s, voxel ajouté, biome conservé, stock consommé.
  • Clic droit bref annule le job.
  • InstancedMesh sur-alloué (GRID x GRID slots) pour absorber les voxels posés.

Tech tree et gate minage

  • 4 techs : pick-stone (5 pts) → pick-bronze (15) → pick-iron (30) → pick-gold (60).
  • canMineCell : roche/neige gate par pick-stone, filons gate par ORE_TECH.
  • Jobs refusés declenchent bulle contextuelle (cooldown 60 s par tech).
  • Panneau HUD "Tech" avec bouton "Rechercher" et flash doré au déblocage.

Bâtiment de recherche et points de recherche

  • Outil Recherche (raccourci 0), toit bleu (#3c7fb8).
  • Attribution automatique du colon IDLE le plus proche au placement.
  • Etat RESEARCHING : 1 pt toutes les 3 s, bob lent, orienté vers le bâtiment.
  • Effacer un labo : colons attribués repassent en IDLE proprement.
  • Bulles contextuelles : field-no-research et empty-lab.

Bords de carte insulaire

  • Falloff smoothstep sur la heightmap : ring extérieur 3 tiles forcé à eau profonde, ring 2 tiles intermédiaires à eau peu profonde.
  • A\* : eau profonde bloquante, eau peu profonde traversable.

Shift + sélection de strate

  • Shift + clic gauche lance un BFS : sélectionne toutes les tiles du même biome à la même hauteur, max 200.
  • Applique l'outil actif sur toute la sélection en une opération.

Correctifs divers

  • Pinceau default corrigé de 3 à 1 (moins surprenant au minage).
  • Q/D inversés corrigés (vecteur right recalculé).
  • Champs et baies contraints à biome herbe ou forêt.
  • Filons contraints à biome roche ou neige.
  • Toggle affichage prénoms colons (bouton HUD).
  • Prénoms absents des bulles par défaut.
(session 1) : Fusion proto1-5 en éditeur consolidé 2026-04-19

Proto1 (référence)

  • Terrain Perlin FBM avec biomes 5 types.
  • Rendu Three.js, face culling, chunks 16x16.
  • Caméra : clic gauche rotation, clic droit pan, molette zoom (style que l'utilisateur préfère).
  • Eau double plan avec shader ondulations.
  • Sky shader + EffectComposer (UnrealBloom + vignette).

Proto2 (abandon)

  • Pinceau de sculpt terrain direct (élever/abaisser/niveler).
  • Bonne idée conservée en concept : l'outil de sculpt existe comme outil in-game pour les colons, pas comme pouvoir divin.

Proto3 (référence visuelle)

  • Style Dorfromantik confirmé (palettes désaturées, post-process léger).
  • Minage par désignation de zone, premier proof-of-concept colons.
  • Filons cyclables 6 types (cuivre, argent, fer, charbon, or, améthyste).

Proto4 (référence gameplay)

  • Colons avec identité (nom, genre), bulles de dialogue.
  • Machine à états : IDLE / MOVING / WORKING / wander.
  • A\* pathfinding avec gravité (tombe si sol retiré).
  • Barre de placement : arbres, rochers, filons, maisons, champs.
  • Spawn 2 colons par maison. François = chef (étoile dorée).
  • Colons contraints au biome (champs sur herbe, filons sur roche).

Proto5 (partiel, fusionné)

  • Baies, bâtiment de recherche (concept initial).
  • Quêtes basiques.

Éditeur consolidé

  • Fusion en un seul fichier main.js (~2500 lignes).
  • Anciens protos supprimés.
  • ZQSD pour panner la caméra (ajout confirmé).
  • Sélection Shift + BFS strate.
  • Hameau par défaut enrichi avec bâtiment de recherche et colon attribué.
  • InstancedMesh frustumCulled = false pour arbres/rochers (fix bug ombres visibles mais mesh absent).