Historique des versions
Historique des itérations du proto. Les anciens protos 1 à 5 ont été fusionnés dans public/strates/editor/.
Refonte de la mecanique de placement du port. Le port ne peut plus etre pose n importe ou.
Detection ocean vs lac (flood-fill)
editor/modules/port-slots.js.state.portSlots et state.portSlotMarkers.Marqueurs pontons fantomes
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.markSlotOccupied()).Validation placement port
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)."
skillLevel('building') >= 10.Sinon : toast "Necessite un batisseur niveau 10."
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.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.BIOMES_DATA exportée.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.coastRoughness : crée des péninsules (bruit positif) et des baies (bruit négatif).mountainLevel et par la distance au centre (intérieur plus montagneux, bords bas = plages).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.treeDensity * 120 en biome forest (clusters), treeDensity * 30 en herbe.oreRichness * 20 filons choisis parmi oreTypes du biome.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
buildTerrain + populateDefaultScene comme avant.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.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.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
switchToMap et registerIsland depuis map-manager.js (declenche l exposition sur window).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
state.isNight && state.foyers.length > 0 && profession !== 'explorateur'.Fenêtres éclairées (age 3)
setNightLights(isNight) et s'éteignent le jour (emissiveIntensity 0).daynight.js au moment du basculement jour/nuit.Tour de flamme (nouveau bâtiment age 3)
tour-flamme 2x2 : colonne de pierre + brasero en fer + flamme visible la nuit uniquement.setNightLights() au même titre que les fenêtres.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)
axe-iron, arrow-iron, windmill, bakery, market, tavern, library, plus gouvernance, navigation, urbanisme et les 4 techs nocturnes.?mode=medieval-test : 12 colons, bâtiments médiévaux posés, techs Âge 3 débloquées, stocks pleins.L1 - Visual Refresh (GLB médiéval)
placement-preview.js.L2 - Techs Nocturnes
astrologie, alchimie, herboristerie-nocturne, meditation) avec cost.night dans techtree.json.silver ajoutée dans resources.json.L3 - Storage Hut
storage-hut 4x4, débloqué par tech granary.buildings.json#provides.resourceCapBoost).enforceResourceCaps() plafonne toutes les state.resources chaque seconde.L4 - Port Maritime
port-lvl1 (4x4) upgradable en port-lvl2 via tech navigation.fish (poisson, saturation 2) ajoutée dans resources.json.level et upgradingUntil.L5 - Mairie (Town Hall)
town-hall via tech gouvernance (coût wood:50, stone:40, iron:5).buildings.json#provides.populationCapBonus).L6 - Champ Légumes
vegetable-farm 2x2, stages Farm Dirt (semis) -> Crops (mature).vegetables (saturation 4, 80 pts de faim).VEGETABLE_PLANTING (3s) et VEGETABLE_HARVESTING (3s, +3 légumes). Maturation via growDuration JSON.UI / UX
urbanisme) : 19 types déplaçables, coût 5 bois, ghost preview vert/rouge.Infra
CompressionStream (gain estimé ~70%), saveGame rendu async.skill-caps.json, cap max par skill, étoiles avec plafond dans charsheet.urbanisme ajoutée (Âge 3), débloque l'outil DÉPLACER.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).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.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.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.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.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.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.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.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.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.**Personnages**
**Progression et tech tree**
**Champs et agriculture**
**Moteur**
**Visuels et UI**
uOpacity dans le fragment shader (mode nuit fonctionnel)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.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).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).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.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.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).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).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.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.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.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.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.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(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).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).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)
checkManorMerge et isPlainHouseOn.checkManorMerge dans interaction.js (apres pose d une cabane).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.completeUpgrade(sourceBuilding) : retire la cabane source, place une Grosse maison deja construite (4x4) au meme emplacement, transfere les residents.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.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.
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
wheat-field, construire un champ, assigner un fermier : le colon part au champ, du blé est credite.assignedFieldId et assignedColonistIds. Au boot, le colon redemarre en IDLE et retourne au champ assigne.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.
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.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).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
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".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.jsonFeature 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.Fichiers modifies
public/strates/editor/modules/ui/techtree-node.jspublic/strates/editor/styles/techtree.cssHover highlight des prerequis
.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).mouseenter / mouseleave branchés sur chaque node lors de renderBranchDetail. Les path SVG exposent data-from / data-to pour le ciblage.Fiche droite (340px)
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).Rail file de recherche
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.Fichiers modifies
public/strates/editor/modules/ui/techtree-panel.jspublic/strates/editor/styles/techtree.cssNouveaux etats de cards
techtree-node.js : bouton "Mettre en file →" 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é "✓ 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
strates:researchStarted, strates:queueChanged, strates:techComplete pour redessiner immediatement le tree.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
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.jspublic/strates/editor/modules/ui/techtree-panel.jspublic/strates/editor/styles/techtree.csspublic/strates/editor/modules/ui/research-popup.js (nouveau)Mecanique
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).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.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
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.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.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.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.data/*.json, ni de modules/ui/*, ni de hud.js. Le bouton Cairn garde son traitement propre dans age-transitions.js (cinematique + conditions cumulatives).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.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.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.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.techtree.json, un placeholder "Techs Bronze, a venir dans une prochaine session" s'affiche dans la colonne de l'age debloque mais sans tech.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).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.refreshTechTreeAfterAgeChange(age) dans modules/ui/techtree-panel.js, egalement exposee sur window pour consommation par age-transitions.js (Lot D) sans import direct.dirty consomme au prochain openTechTreePanel().TECH_TREE_DATA.ages[].unlocked (SPEC v1). La fonction force uniquement le re-render DOM.modules/ui/techtree-panel.js + noeuds modules/ui/techtree-node.js, feuille de style dediee styles/techtree.css.main.js, non modifies). Fermeture via **Echap**, clic backdrop, ou bouton Fermer.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).?????). Les teased sortent leur tech.id, branch et cout du DOM pour prevenir toute lecture via devtools sur les ages 2+.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).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.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.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%.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** :needs (Map), jobQueue (Array), currentTask, assignedBuildingId, productivityMul, wasAttacked.initColonistNeeds(this) au constructeur.isNeedCritical(this, 'hunger'), le colon abandonne tout et appelle pickHarvest en priorite SURVIVAL (jour et nuit).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.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.data/*.json, ui/*, hud.js, placements.js, techtree-ui.js.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.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 }).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
state.currentAge = 2, state.achievements enregistre l'evenement, sauvegarde checkpoint auto.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.data/techtree.json et data/buildings.json protos remplacés par les versions SPEC v1 gelées.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.data/jobs.json (8 metiers age I) : Cueilleur, Bucheron, Mineur, Chasseur, Chercheur, Astronome, Terrassier, Explorateur.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()).modules/tech.js et modules/techtree-ui.js sont a adapter pour lire les nouveaux JSON au lieu des donnees hardcodees.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.state.nightPoints (int) et state.isNight (bool), sérialisées dans les sauvegardes.pickHarvest) est désactivée la nuit (activité exclusive jour).data/buildings.json : Promontoire, Observatoire, Grand télescope, Radio-télescope.data/ avec trois fichiers JSON : speech.json (phrases, dialogues, bulles contextuelles), colonists.json (prénoms M/F), quests.json (définitions des quêtes).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().Landing page (/strates/index.html)
jeux.fmenguy.fr/fda/.strates-new-game et ignore les saves existantes.Jeu (editor/)
main.js : lecture de strates-pending-load (charger un slot spécifique) et strates-new-game (ignorer les saves) au démarrage.Roadmap
Nouvelle tech
axe-stone (Hache en pierre), coût 4 pts, âge de pierre, sans prérequis. Débloque l'abattage des arbres.Abattage
resources.wood, l'arbre est retiré du mesh. Le voxel sous reste en place.canMineCell requiert axe-stone pour les tuiles à arbre. Sans la tech, bulle indice bleue "Il nous faudrait une hache pour cet arbre".isTreeOn(x, z) et chopTreeAt(x, z) dans placements.js.Audio playlist par âge
modules/audio.js : lecteur <audio> HTML5 avec crossfade 2,5 s.public/strates/editor/audio/stone.mp3, bronze.mp3, iron.mp3, gold.mp3.public/strates/editor/audio/ avec sources suggérées (Pixabay, FMA, OpenGameArt, Incompetech).Extraction
copper, iron, coal, silver, gold, amethyst), laisse le voxel sous intact.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.ORE_TECH existant : cuivre/charbon → pick-bronze, fer/argent → pick-iron, or/améthyste → pick-gold.Doc
Cycle de saisons
seasons.js : 4 saisons (printemps, été, automne, hiver), 120 s chacune, 8 min par cycle complet.Décorations végétales
vegetation.js : 3 InstancedMesh (brins d'herbe, fleurs, épis de blé).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
audio.js, Web Audio API, aucun fichier externe.♪ qui toggle mute. État muté par défaut (respect autoplay policies), mémorisé en localStorage.Plus
Modularisation (axe technique 1.1)
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.state exporté par state.js (contourne les limites des let exportés).prng pour permettre à worldgen de changer le seed.Persistance localStorage (axe technique 1.2)
persistence.js avec saveGame, loadGame, hasSave, deleteSave, startAutoSave.strates-save-auto.beforeunload et au passage en onglet caché.populateDefaultScene génère un monde neuf.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.Colons enrichis
Noms permanents au-dessus de la tête
Bulles indice bleues
sayHint(line) dessinant une bulle bleu pâle cerclée de bleu vif avec une pastille jaune style ampoule.Monde peuplé au spawn
Tech tree visuel
#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.Stocks de ressources
stocks avec 8 types : stone, dirt, copper, silver, iron, coal, gold, amethyst.incrStockForBiome).Outil Placer (raccourci 3)
buildJobs séparé de jobs.InstancedMesh sur-alloué (GRID x GRID slots) pour absorber les voxels posés.Tech tree et gate minage
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.Bâtiment de recherche et points de recherche
#3c7fb8).RESEARCHING : 1 pt toutes les 3 s, bob lent, orienté vers le bâtiment.field-no-research et empty-lab.Bords de carte insulaire
smoothstep sur la heightmap : ring extérieur 3 tiles forcé à eau profonde, ring 2 tiles intermédiaires à eau peu profonde.Shift + sélection de strate
Correctifs divers
Proto1 (référence)
Proto2 (abandon)
Proto3 (référence visuelle)
Proto4 (référence gameplay)
Proto5 (partiel, fusionné)
Éditeur consolidé
main.js (~2500 lignes).InstancedMesh frustumCulled = false pour arbres/rochers (fix bug ombres visibles mais mesh absent).