diff --git a/src/data/archetypes.json b/src/data/archetypes.json index 494259f..1eec675 100644 --- a/src/data/archetypes.json +++ b/src/data/archetypes.json @@ -1,13 +1,17 @@ { - "_comment": "Bibliothèque d'archétypes PNJ. Le LLM pioche ici pour instancier un PNJ de passage (tier 3) ou nommé (tier 2). Chaque archétype définit le template — le LLM choisit le nom, la faction et la localisation.", + "_comment": "Bibliothèque d'archétypes PNJ. origin_pool = liste d'origines possibles. faction_origin_override = si la faction choisie correspond, l'origine est forcée (lore). Le LLM choisit nom, faction, localisation.", "garde_faction": { "role": "garde", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri", "initie_confrerie", "goule"], + "faction_origin_override": { + "cda": "initie_confrerie", + "grand_krewe": "goule" + }, "tier": 3, "description": "Garde d'une faction, posté à un point stratégique", - "faction_pool": ["union", "cda", "syndicat_capitole", "regie", "dynaste_oak"], - "location_hints": ["zone contrôlée par la faction d'appartenance"], + "faction_pool": ["union", "cda", "syndicat_capitole", "regie", "dynaste_oak", "grand_krewe"], + "location_hints": ["nola_cbd", "independance", "baton_rouge", "la_paroisse", "oak_plantation"], "caps_range": [50, 250], "personality_traits": ["loyal", "méfiant envers étrangers", "obéit aux ordres"], "motivations": ["protéger son territoire", "nourrir sa famille", "progresser dans la hiérarchie"], @@ -16,7 +20,8 @@ "vendeur_ambulant": { "role": "marchand", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri", "goule"], + "faction_origin_override": {}, "tier": 3, "description": "Marchand itinérant qui circule entre les zones sûres", "faction_pool": [null, "consortium", "regie"], @@ -29,7 +34,8 @@ "habitant_nola": { "role": "survivant_generique", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri"], + "faction_origin_override": {}, "tier": 3, "description": "Habitant ordinaire du Vieux Carré ou d'Indépendance", "faction_pool": [null, "union", "regie"], @@ -42,7 +48,8 @@ "pillard_errant": { "role": "pillard", - "origin": "survivant", + "origin_pool": ["survivant", "goule"], + "faction_origin_override": {}, "tier": 3, "description": "Pillard solitaire ou en petite bande, sans base fixe", "faction_pool": ["ecumeurs", null], @@ -55,11 +62,14 @@ "passeur_bayou": { "role": "passeur", - "origin": "survivant", + "origin_pool": ["survivant", "goule"], + "faction_origin_override": { + "grand_krewe": "goule" + }, "tier": 3, - "description": "Guide qui connaît les routes secrètes à travers les bayous", - "faction_pool": [null, "ecumeurs"], - "location_hints": ["pearl_river", "donaldsonville", "oak_plantation"], + "description": "Guide qui connaît les routes secrètes à travers les bayous — souvent une goule très ancienne", + "faction_pool": [null, "grand_krewe"], + "location_hints": ["pearl_river", "donaldsonville", "la_paroisse", "nola_fleuve"], "caps_range": [100, 600], "personality_traits": ["taciturne", "fiable si payé", "indépendant", "survivaliste"], "motivations": ["caps", "indépendance totale", "connaître les secrets des factions"], @@ -68,7 +78,8 @@ "chasseur_primes": { "role": "chasseur_primes", - "origin": "survivant", + "origin_pool": ["survivant", "goule", "habitant_abri"], + "faction_origin_override": {}, "tier": 3, "description": "Mercenaire qui traite les contrats d'élimination ou de capture", "faction_pool": [null, "syndicat_capitole"], @@ -81,7 +92,8 @@ "scavenger_freelance": { "role": "scavenger", - "origin": "survivant", + "origin_pool": ["survivant", "goule"], + "faction_origin_override": {}, "tier": 3, "description": "Récupérateur de ferraille et de technologie pré-guerre", "faction_pool": [null], @@ -94,7 +106,8 @@ "medecin_freelance": { "role": "docteur", - "origin": "habitant_abri", + "origin_pool": ["habitant_abri", "survivant", "goule"], + "faction_origin_override": {}, "tier": 3, "description": "Médecin indépendant qui soigne sans question contre paiement", "faction_pool": [null, "union", "consortium"], @@ -107,11 +120,12 @@ "messager_faction": { "role": "passeur", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri"], + "faction_origin_override": {}, "tier": 3, "description": "Courrier entre factions, transporte messages et petits colis", "faction_pool": ["union", "consortium", "regie", "syndicat_capitole"], - "location_hints": ["toutes zones sûres"], + "location_hints": ["independance", "laplace", "nola_vieux_carre", "baton_rouge", "nola_cbd"], "caps_range": [50, 300], "personality_traits": ["discret", "rapide", "mémorise sans noter", "refuse de lire les messages"], "motivations": ["caps par livraison", "rester neutre", "éviter d'être intercepté"], @@ -120,11 +134,13 @@ "fugitif": { "role": "survivant_generique", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri", "goule"], + "faction_origin_override": {}, "tier": 3, - "description": "Quelqu'un qui fuit une faction — esclave évadé, déserteur, criminel", + "description": "Quelqu'un qui fuit une faction — esclave évadé, déserteur, criminel, témoin gênant", "faction_pool": [null], - "location_hints": ["pearl_river", "nola_cbd", "donaldsonville"], + "chased_by_faction_pool": ["union", "cda", "syndicat_capitole", "regie", "dynaste_oak", "ecumeurs"], + "location_hints": ["pearl_river", "nola_cbd", "donaldsonville", "la_paroisse"], "caps_range": [0, 50], "personality_traits": ["terrorisé", "méfiant extrême", "désespéré", "potentiellement dangereux"], "motivations": ["ne pas être retrouvé", "atteindre une zone sûre", "survivre une journée de plus"], @@ -133,7 +149,10 @@ "technicien_freelance": { "role": "technicien", - "origin": "initie_confrerie", + "origin_pool": ["initie_confrerie", "habitant_abri", "survivant"], + "faction_origin_override": { + "cda": "initie_confrerie" + }, "tier": 3, "description": "Ingénieur indépendant qui répare armes, générateurs et pip-boys", "faction_pool": [null, "consortium"], @@ -146,11 +165,15 @@ "lieutenant_faction": { "role": "lieutenant", - "origin": "survivant", + "origin_pool": ["survivant", "habitant_abri", "initie_confrerie", "goule"], + "faction_origin_override": { + "cda": "initie_confrerie", + "grand_krewe": "goule" + }, "tier": 2, "description": "Officier de rang intermédiaire, bras droit opérationnel d'un chef", "faction_pool": ["union", "cda", "syndicat_capitole", "regie", "dynaste_oak", "ecumeurs", "consortium", "grand_krewe"], - "location_hints": ["QG ou zone clé de la faction"], + "location_hints": ["independance", "baton_rouge", "nola_vieux_carre", "la_paroisse", "oak_plantation", "pearl_river", "laplace", "nola_cbd"], "caps_range": [200, 800], "personality_traits": ["ambitieux", "dévoué à la faction", "respecté des gardes", "jaloux du boss"], "motivations": ["monter en grade", "protéger la faction", "accumuler des ressources personnelles"], diff --git a/src/engine/character_creator.py b/src/engine/character_creator.py index 4bcd75d..491c82f 100644 --- a/src/engine/character_creator.py +++ b/src/engine/character_creator.py @@ -203,11 +203,11 @@ _ROLE_TO_ORIGIN: dict[str, list[str]] = { "docteur": ["survivant", "habitant_abri"], "technicien": ["initie_confrerie", "habitant_abri", "survivant"], "raider_boss": ["survivant", "super_mutant"], - "lieutenant": ["survivant", "habitant_abri"], - "chasseur_primes": ["survivant"], + "lieutenant": ["survivant", "habitant_abri", "initie_confrerie", "goule"], + "chasseur_primes": ["survivant", "goule", "habitant_abri"], "passeur": ["survivant", "goule"], "scavenger": ["survivant", "goule"], - "chef_faction": ["survivant", "habitant_abri"], + "chef_faction": ["survivant", "habitant_abri", "goule"], "survivant_generique": ["survivant", "habitant_abri"], } diff --git a/src/engine/pnj_factory.py b/src/engine/pnj_factory.py index 1520008..6009d7b 100644 --- a/src/engine/pnj_factory.py +++ b/src/engine/pnj_factory.py @@ -122,14 +122,27 @@ def spawn_from_archetype( arch = archetypes[archetype_key] rng = random.Random(seed) - role = arch["role"] - origin = arch.get("origin", "survivant") - tier = tier_override if tier_override is not None else arch.get("tier", 3) + role = arch["role"] + tier = tier_override if tier_override is not None else arch.get("tier", 3) if faction_slug is None: - pool = [f for f in arch.get("faction_pool", [None])] + pool = arch.get("faction_pool", [None]) faction_slug = rng.choice(pool) + # Résolution de l'origine : faction_origin_override prime sur origin_pool + origin_override = arch.get("faction_origin_override", {}) + if faction_slug and faction_slug in origin_override: + origin = origin_override[faction_slug] + elif "origin_pool" in arch: + origin = rng.choice(arch["origin_pool"]) + else: + origin = arch.get("origin", "survivant") + + # Métadonnées narratives supplémentaires (ex: fugitif → chased_by) + chased_by = None + if "chased_by_faction_pool" in arch: + chased_by = rng.choice(arch["chased_by_faction_pool"]) + char = cc.generate_character( session_id = session_id, role = role, @@ -148,6 +161,8 @@ def spawn_from_archetype( items = equip_character(char["id"], equip_role, session_id, seed=seed) char["items_equipped"] = items + if chased_by: + char["chased_by_faction"] = chased_by return char