# Phase 8 – Attractivité et visiteurs (billeterie, cap, score) **Objectif :** Plafonner les visiteurs par la capacité billeterie, calculer un score d’attractivité (valeur, espèces, rareté, remplissage, pénalités morts, bonus naissances) et l’afficher sur la carte du monde. **Référence :** `docs/plan-implementation-rappel-grandes-regles.md` phase 8. ## Impacts - Les visiteurs sont plafonnés par la capacité billeterie (20 × niveau total billeterie). Sans billeterie, le plafond n’est pas appliqué (comportement inchangé). - Nouveau score d’attractivité exposé dans `state.attractivityScore` et affiché sous le nom du zoo (joueur) sur la carte du monde. - La formule d’attractivité pourra servir plus tard à l’allocation des visiteurs depuis les villes (phase 11). ## Modifications - **income.js** : suppression des imports dupliqués ; `getBilleterieCapacity(state)` ; dans `getVisitorParams`, plafonnement de `visitorCount` par `getBilleterieCapacity(state)` ; `getAttractivityScore(state)` (valeur cumulée, nombre d’espèces, rareté moyenne, taux de remplissage, pénalité morts, bonus naissances). - **config.js** : `Visitor.AttractivityDeathPenalty`, `Visitor.AttractivityBirthBonus`. - **types.js** : `GameState.attractivityScore?`. - **game-loop.js** : import `getAttractivityScore` ; après `tickResearch`, `state.attractivityScore = getAttractivityScore(state)`. - **ui.js** : pour le zoo joueur, ligne « Score attractivité: X.X » sous le score de reproduction (classe `world-map-zoo-attractivity-score`). - **main.css** : style `.world-map-zoo-attractivity-score`. ## Disparition des animaux non visités (§2) - **Config** : `Visitor.MaxSecondsWithoutVisit` (300 s par défaut). Un animal dont aucune visite n’a été enregistrée sur sa case depuis plus de cette durée est retiré (case vidée, `deathCountRecent` incrémenté). - **animal-visits.js** : `tickAnimalVisits(state, nowUnix, nowMs)` met à jour `lastVisitedAt` des cases animales sous la position actuelle des visiteurs (orbites autour du centre d’attraction). - **food.js** : `checkDeathCauses(state, nowUnix)` utilise `MaxSecondsWithoutVisit` ; si `nowUnix - lastVisitedAt >= maxVisit`, le bloc animal est supprimé. - **game-loop.js** : `tickAnimalVisits` est appelé avant `tickFeeding` et `checkDeathCauses`. ## Stagnation (§3) - **Config** : `Visitor.StagnationDecayAfterSeconds` (60 s), `Visitor.StagnationDecayPerMinute` (0.05). Après ce délai sans action d’évolution, le multiplicateur de demande visiteurs décroît (plancher 10 %). - **income.js** : `getStagnationMultiplier(state, nowUnix)` utilise `state.lastEvolutionAt` ; appliqué dans `getVisitorDemand(state, nowUnix)`. - **lastEvolutionAt** est mis à jour sur : upgrade (plot, conveyor, truck, world map, school, nursery, shop, research, billeterie, food, reception, biome), place (egg, baby, reception animal), vente (sellAnimalToNpc, addMatureBabyToSale, addReceptionAnimalToSale), prestige, moveCell. ## Implémenté ultérieurement (modèle visiteurs par entité) - **Durée max 1 journée par visiteur** : `state.visitorArrivals[]` avec `{ arrivedAt }` ; base = `Time.DayLengthSeconds` (1 jour) ; sortie quand `now > arrivedAt + getStayDurationSeconds(state)`. - **Prolongation par boutiques et diversité** : `getStayMultiplier(state)` = 1 + (niveaux boutique × `Visitor.StayMultiplierPerShopLevel`) + (espèces distinctes × `Visitor.StayMultiplierPerSpecies`) ; `getStayDurationSeconds(state) = base × getStayMultiplier(state)`. - **Config** : `Visitor.StayMultiplierPerShopLevel`, `Visitor.StayMultiplierPerSpecies` (base jour = `Time.DayLengthSeconds`). - **Game loop** : `tickVisitorArrivals(state, nowUnix)` appelé avant `incomeTick` ; `getVisitorParams(state)` utilise `state.visitorArrivals.length` ; sans billeterie, fallback sur l’ancienne formule pour rétrocompatibilité. ## Modalités de déploiement - Aucun déploiement serveur. Rechargement client suffit. ## Modalités d’analyse - Sans billeterie : le nombre de visiteurs reste calculé comme avant (sans plafond). - Avec au moins une case billeterie : le nombre de visiteurs affiché et utilisé pour les revenus ne dépasse pas (niveau total billeterie × 20). - Carte du monde, zoo joueur : affichage « Score attractivité: X.X » mis à jour à chaque tick.