Fix snapshot path to repo root and add workstation info to utility report

Snapshot now writes to tests/snapshots/ at repo root instead of the test
bin output directory, so it can be committed and diffed across changes.

Crafting entries now show the workstation (e.g. "@ DrawingTable") for both
ingredients and outputs. Removed timestamp from report header to keep
snapshots stable across runs.
This commit is contained in:
Samuel Bouchet 2026-03-15 15:09:56 +01:00
parent 7a854ccc15
commit e3aa4cbfe7
2 changed files with 646 additions and 7 deletions

View file

@ -1778,10 +1778,24 @@ public class ContentValidationTests
}
}
// Build workstation transformation map: itemId → [(recipeId, workstation), ...]
var craftedAt = new Dictionary<string, List<(string recipeId, WorkstationType workstation)>>();
foreach (var recipe in registry.Recipes.Values)
{
foreach (var ingredient in recipe.Ingredients)
{
if (!craftedAt.ContainsKey(ingredient.ItemDefinitionId))
craftedAt[ingredient.ItemDefinitionId] = [];
craftedAt[ingredient.ItemDefinitionId].Add((recipe.Id, recipe.Workstation));
}
if (!craftedAt.ContainsKey(recipe.Result.ItemDefinitionId))
craftedAt[recipe.Result.ItemDefinitionId] = [];
craftedAt[recipe.Result.ItemDefinitionId].Add((recipe.Id, recipe.Workstation));
}
// Build report
var report = new System.Text.StringBuilder();
report.AppendLine("# Item Utility Report");
report.AppendLine($"# Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm} UTC");
report.AppendLine($"# Total items: {registry.Items.Count}");
report.AppendLine($"# Total boxes: {registry.Boxes.Count}");
report.AppendLine($"# Total recipes: {registry.Recipes.Count}");
@ -1831,13 +1845,22 @@ public class ContentValidationTests
if (item.CosmeticSlot.HasValue)
usages.Add($"Equip: {item.CosmeticSlot}={item.CosmeticValue}");
// Crafting ingredient
// Crafting ingredient (with workstation)
if (craftIngredientOf.TryGetValue(item.Id, out var recipes))
usages.Add($"Craft ingredient: {string.Join(", ", recipes)}");
{
foreach (var recipeId in recipes)
{
var recipe = registry.Recipes[recipeId];
usages.Add($"Craft ingredient: {recipeId} @ {recipe.Workstation}");
}
}
// Crafting output
// Crafting output (with workstation)
if (craftOutputOf.TryGetValue(item.Id, out var outputRecipe))
usages.Add($"Craft output: {outputRecipe}");
{
var recipe = registry.Recipes[outputRecipe];
usages.Add($"Craft output: {outputRecipe} @ {recipe.Workstation}");
}
// Interaction
if (interactionItems.TryGetValue(item.Id, out var inters))
@ -1880,8 +1903,9 @@ public class ContentValidationTests
string reportText = report.ToString();
// Write snapshot
var snapshotDir = Path.Combine("tests", "snapshots");
// Write snapshot to repo root (tests/snapshots/) so it can be committed
var repoRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
var snapshotDir = Path.Combine(repoRoot, "tests", "snapshots");
Directory.CreateDirectory(snapshotDir);
var snapshotPath = Path.Combine(snapshotDir, "item_utility_report.txt");

View file

@ -0,0 +1,615 @@
# Item Utility Report
# Total items: 146
# Total boxes: 31
# Total recipes: 18
## AdventureToken (31 items)
────────────────────────────────────────────────────────────────────────────────
[****] space_coordinates (Epic) — Coordonnées mystérieuses
Loot: box_adventure_space
Adventure: Space
Craft ingredient: chart_star_navigation @ DrawingTable
Interaction: coordinates_map_combine
[***] space_phone (Rare) — Numéro de téléphone alien
Loot: box_adventure_space
Adventure: Space
Interaction: phone_character_encounter
[***] medieval_crest (Rare) — Blason de chevalier
Loot: box_adventure_medieval
Adventure: Medieval
Craft ingredient: engrave_royal_seal @ EngravingBench
[***] medieval_scroll (Rare) — Parchemin ancien
Loot: box_adventure_medieval
Adventure: Medieval
Craft ingredient: engrave_royal_seal @ EngravingBench
[***] medieval_seal (Epic) — Sceau royal
Loot: box_adventure_medieval
Adventure: Medieval
Craft output: engrave_royal_seal @ EngravingBench
[***] pirate_compass (Rare) — Boussole enchantée
Loot: box_adventure_pirate
Adventure: Pirate
Interaction: pirate_map_compass
[***] contemporary_phone (Common) — Smartphone
Loot: box_adventure_contemporary
Adventure: Contemporary
Interaction: phone_character_encounter
[***] sentimental_phone (Rare) — Numéro de l'ex
Loot: box_adventure_sentimental
Adventure: Sentimental
Interaction: phone_character_encounter
[***] prehistoric_tooth (Uncommon) — Dent de dinosaure
Loot: box_adventure_prehistoric
Adventure: Prehistoric
Craft ingredient: preserve_amber @ StasisChamber
[***] prehistoric_amber (Rare) — Pierre d'ambre
Loot: box_adventure_prehistoric
Adventure: Prehistoric
Craft output: preserve_amber @ StasisChamber
[****] cosmic_shard (Rare) — Éclat de nébuleuse
Loot: box_adventure_cosmic
Adventure: Cosmic
Craft ingredient: engineer_rocket_boots @ EngineerDesk
Craft ingredient: fuse_cosmic_crystal @ MatterSynthesizer
[***] cosmic_crystal (Epic) — Cristal de quasar
Loot: box_adventure_cosmic
Adventure: Cosmic
Craft output: fuse_cosmic_crystal @ MatterSynthesizer
[***] microscopic_bacteria (Uncommon) — Échantillon de bactérie sentiente
Loot: box_adventure_microscopic
Adventure: Microscopic
Craft ingredient: splice_glowing_dna @ GeneticModStation
[***] microscopic_dna (Rare) — Brin d'ADN luminescent
Loot: box_adventure_microscopic
Adventure: Microscopic
Craft output: splice_glowing_dna @ GeneticModStation
[***] microscopic_prion (Epic) — Prion amical (probablement)
Loot: box_adventure_microscopic
Adventure: Microscopic
Craft ingredient: splice_glowing_dna @ GeneticModStation
[***] darkfantasy_ring (Rare) — Anneau maudit
Loot: box_adventure_darkfantasy
Adventure: DarkFantasy
Craft ingredient: enchant_dark_grimoire @ TransformationPentacle
[***] darkfantasy_grimoire (Epic) — Grimoire du nécromancien
Loot: box_adventure_darkfantasy
Adventure: DarkFantasy
Craft output: enchant_dark_grimoire @ TransformationPentacle
[**] space_badge (Rare) — Badge d'astronaute
Loot: box_adventure_space
Adventure: Space
[**] pirate_feather (Common) — Plume de perroquet
Loot: box_adventure_pirate
Adventure: Pirate
[**] pirate_rum (Uncommon) — Bouteille de rhum
Loot: box_adventure_pirate
Adventure: Pirate
[**] contemporary_card (Uncommon) — Carte de crédit
Loot: box_adventure_contemporary
Adventure: Contemporary
[**] contemporary_usb (Rare) — Clé USB suspecte
Loot: box_adventure_contemporary
Adventure: Contemporary
[**] contemporary_badge (Rare) — Badge d'entreprise
Loot: box_adventure_contemporary
Adventure: Contemporary
[**] sentimental_letter (Rare) — Lettre d'amour
Loot: box_adventure_sentimental
Adventure: Sentimental
[**] sentimental_flower (Common) — Fleur séchée
Loot: box_adventure_sentimental
Adventure: Sentimental
[**] sentimental_teddy (Uncommon) — Vieil ours en peluche
Loot: box_adventure_sentimental
Adventure: Sentimental
[**] prehistoric_fossil (Epic) — Fossile de trilobite
Loot: box_adventure_prehistoric
Adventure: Prehistoric
[**] cosmic_core (Legendary) — Cœur d'étoile
Loot: box_adventure_cosmic, box_black
Adventure: Cosmic
[**] darkfantasy_gem (Legendary) — Gemme d'âme
Loot: box_adventure_darkfantasy, box_black
Adventure: DarkFantasy
[**] destiny_token (Mythic) — Jeton du Destin
Loot: box_endgame(G)
Adventure: Destiny
[*] medieval_sword (Epic) — Réplique d'Excalibur
Adventure: Medieval
## Consumable (6 items)
────────────────────────────────────────────────────────────────────────────────
[**] blood_vial (Rare) — Fiole de sang
Loot: box_epic, box_adventure_darkfantasy, box_supply
Consume: +5 Blood
[**] gold_pouch (Common) — Bourse d'or
Loot: box_ok_tier, box_adventure_pirate, box_supply
Consume: +50 Gold
[**] resource_max_gold (Rare) — Amélioration Capacité Or
Loot: box_improvement
Upgrade: max Gold
[**] resource_max_blood (Rare) — Amélioration Capacité Sang
Loot: box_improvement
Upgrade: max Blood
[**] music_melody (Rare) — Mélodie de boîte
Loot: box_music(G)
Ephemeral: plays melody
[**] cookie_fortune (Common) — Fortune Cookie
Loot: box_cookie(G)
Ephemeral: fortune message
## Cosmetic (42 items)
────────────────────────────────────────────────────────────────────────────────
[***] cosmetic_eyes_sunglasses (Uncommon) — Lunettes de soleil
Loot: box_cool, box_style
Equip: Eyes=Sunglasses
Craft ingredient: craft_pilot_glasses @ Workbench
[***] cosmetic_eyes_pilotglasses (Rare) — Lunettes d'aviateur
Loot: box_epic
Equip: Eyes=PilotGlasses
Craft output: craft_pilot_glasses @ Workbench
[***] cosmetic_body_armored (Epic) — Armure
Loot: box_legendary
Equip: Body=Armored
Craft output: forge_armored_plate @ Forge
[***] cosmetic_legs_short (Common) — Short
Loot: box_not_great, box_style
Equip: Legs=Short
Craft ingredient: engineer_rocket_boots @ EngineerDesk
[***] cosmetic_legs_rocketboots (Epic) — Bottes à réaction
Loot: box_legendary
Equip: Legs=RocketBoots
Craft output: engineer_rocket_boots @ EngineerDesk
[**] cosmetic_hair_short (Common) — Cheveux courts
Loot: box_not_great, box_style
Equip: Hair=Short
[**] cosmetic_hair_long (Common) — Cheveux longs
Loot: box_ok_tier, box_style
Equip: Hair=Long
[**] cosmetic_hair_ponytail (Uncommon) — Queue de cheval
Loot: box_cool, box_style
Equip: Hair=Ponytail
[**] cosmetic_hair_braided (Uncommon) — Tresses
Loot: box_style
Equip: Hair=Braided
[**] cosmetic_hair_cyberpunk (Rare) — Cheveux néon cyberpunk
Loot: box_epic, box_legendhair
Equip: Hair=Cyberpunk
[**] cosmetic_hair_fire (Epic) — Cheveux en feu
Loot: box_legendhair
Equip: Hair=Fire
[**] cosmetic_hair_stardust (Legendary) — Coiffure Poussière d'Étoile légendaire
Loot: box_legendhair(G)
Equip: Hair=StardustLegendary
[**] cosmetic_eyes_blue (Common) — Yeux bleus
Loot: box_ok_tier, box_style
Equip: Eyes=Blue
[**] cosmetic_eyes_green (Common) — Yeux verts
Loot: box_ok_tier, box_style
Equip: Eyes=Green
[**] cosmetic_eyes_redorange (Uncommon) — Yeux rouge-orange
Loot: box_style
Equip: Eyes=RedOrange
[**] cosmetic_eyes_brown (Common) — Yeux marron
Loot: box_not_great
Equip: Eyes=Brown
[**] cosmetic_eyes_cybernetic (Epic) — Yeux cybernétiques
Loot: box_legendary
Equip: Eyes=CyberneticEyes
[**] cosmetic_eyes_magician (Rare) — Lunettes de magicien
Loot: box_cool
Equip: Eyes=MagicianGlasses
[**] cosmetic_body_tshirt (Common) — T-shirt basique
Loot: box_not_great, box_style
Equip: Body=RegularTShirt
[**] cosmetic_body_sexy (Uncommon) — T-shirt sexy
Loot: box_cool, box_style
Equip: Body=SexyTShirt
[**] cosmetic_body_suit (Rare) — Costume
Loot: box_epic
Equip: Body=Suit
[**] cosmetic_body_robotic (Legendary) — Châssis robotique
Loot: box_legendary
Equip: Body=Robotic
[**] cosmetic_legs_panty (Uncommon) — Culotte
Loot: box_style
Equip: Legs=Panty
[**] cosmetic_legs_pegleg (Rare) — Jambe de bois
Loot: box_epic
Equip: Legs=PegLeg
[**] cosmetic_legs_tentacles (Legendary) — Tentacules
Loot: box_legendary
Equip: Legs=Tentacles
[**] cosmetic_arms_regular (Common) — Bras normaux
Loot: box_not_great, box_style
Equip: Arms=Regular
[**] cosmetic_arms_mechanical (Epic) — Bras mécaniques
Loot: box_legendary
Equip: Arms=Mechanical
[**] cosmetic_arms_wings (Legendary) — Ailes
Loot: box_legendary
Equip: Arms=Wings
[**] cosmetic_arms_extrapair (Epic) — Quatre bras
Loot: box_epic
Equip: Arms=ExtraPair
[**] tint_neon (Rare) — Néon
Loot: box_epic
Craft ingredient: craft_box_cool @ Forge
[**] tint_silver (Rare) — Argent
Loot: box_epic
Craft ingredient: craft_pilot_glasses @ Workbench
[**] tint_gold (Epic) — Or
Loot: box_legendhair
Craft ingredient: craft_box_epic @ Printer3D
[**] endgame_crown (Mythic) — Couronne d'Accomplissement
Loot: box_endgame(G)
Equip: Hair=crown
[*] cosmetic_gender_error (Mythic) — Nouveau genre (ERREUR : les boîtes n'ont pas de genre. La boîte s'excuse pour la confusion.)
Loot: box_style, box_black
[*] tint_cyan (Common) — Cyan
Loot: box_ok_tier, box_style
[*] tint_orange (Common) — Orange
Loot: box_ok_tier, box_style
[*] tint_purple (Uncommon) — Violet
Loot: box_cool, box_style
[*] tint_warmpink (Uncommon) — Rose chaud
Loot: box_style
[*] tint_light (Common) — Clair
Loot: box_not_great
[*] tint_dark (Common) — Sombre
Loot: box_not_great
[*] tint_rainbow (Legendary) — Arc-en-ciel
Loot: box_legendhair, box_legendary
[*] tint_void (Legendary) — Néant
Loot: box_legendary, box_adventure_cosmic, box_black
## Key (6 items)
────────────────────────────────────────────────────────────────────────────────
[****] space_key (Rare) — Clé d'accès au sas
Loot: box_adventure_space
Adventure: Space
Craft output: chart_star_navigation @ DrawingTable
Interaction: key_chest_auto
[***] medieval_key (Rare) — Clé du donjon
Loot: box_adventure_medieval
Adventure: Medieval
Interaction: key_chest_auto
[***] pirate_key (Rare) — Clé du coffre
Loot: box_adventure_pirate
Adventure: Pirate
Interaction: key_chest_auto
[***] contemporary_key (Uncommon) — Clé d'appartement
Loot: box_adventure_contemporary
Adventure: Contemporary
Interaction: key_chest_auto
[***] darkfantasy_key (Rare) — Clé en os
Loot: box_adventure_darkfantasy
Adventure: DarkFantasy
Interaction: key_chest_auto
[**] mysterious_key (Rare) — Clé mystérieuse
Loot: box_adventure_medieval, box_adventure_pirate, box_adventure_contemporary, box_adventure_darkfantasy, box_black
Interaction: key_chest_auto
## LoreFragment (10 items)
────────────────────────────────────────────────────────────────────────────────
[*] lore_1 (Uncommon) — Fragment : Genèse
Loot: box_cool, box_story
[*] lore_2 (Uncommon) — Fragment : L'Ordre
Loot: box_cool, box_story
[*] lore_3 (Rare) — Fragment : L'Univers
Loot: box_epic, box_story
[*] lore_4 (Rare) — Fragment : Première Ouverture
Loot: box_epic, box_story
[*] lore_5 (Rare) — Fragment : Le Son
Loot: box_epic, box_story
[*] lore_6 (Epic) — Fragment : Le Paradoxe
Loot: box_legendary, box_story
[*] lore_7 (Rare) — Fragment : La Grève
Loot: box_story
[*] lore_8 (Rare) — Fragment : La Machine
Loot: box_story
[*] lore_9 (Rare) — Fragment : Le Manuel
Loot: box_story
[*] lore_10 (Epic) — Fragment : Schrödinger
Loot: box_legendary, box_black, box_story
## Map (2 items)
────────────────────────────────────────────────────────────────────────────────
[****] space_map (Epic) — Carte stellaire
Loot: box_adventure_space
Adventure: Space
Craft ingredient: chart_star_navigation @ DrawingTable
Interaction: coordinates_map_combine
[***] pirate_map (Epic) — Carte au trésor
Loot: box_adventure_pirate(G)
Adventure: Pirate
Interaction: pirate_map_compass
## Material (15 items)
────────────────────────────────────────────────────────────────────────────────
[****] material_bronze_ingot (Uncommon) — Bronze
Loot: box_ok_tier
Craft ingredient: craft_pilot_glasses @ Workbench
Craft ingredient: craft_box_ok_tier @ Workbench
Craft output: smelt_bronze_ingot @ Furnace
[****] material_steel_ingot (Rare) — Acier
Loot: box_epic
Craft ingredient: forge_armored_plate @ Forge
Craft ingredient: craft_box_cool @ Forge
Craft output: smelt_steel_ingot @ Furnace
[****] material_titanium_ingot (Epic) — Titane
Loot: box_legendary
Craft ingredient: engineer_rocket_boots @ EngineerDesk
Craft ingredient: craft_box_epic @ Printer3D
Craft output: smelt_titanium_ingot @ Furnace
[**] material_wood_raw (Common) — Bois
Loot: box_not_great, box_adventure_prehistoric
Craft ingredient: refine_wood @ Foundry
[**] material_wood_refined (Common) — Bois
Craft ingredient: craft_box_ok_tier @ Workbench
Craft output: refine_wood @ Foundry
[**] material_bronze_raw (Common) — Bronze
Loot: box_not_great
Craft ingredient: smelt_bronze_ingot @ Furnace
[**] material_iron_raw (Common) — Fer
Loot: box_ok_tier
Craft ingredient: smelt_iron_ingot @ Furnace
[**] material_iron_ingot (Uncommon) — Fer
Loot: box_cool
Craft output: smelt_iron_ingot @ Furnace
[**] material_steel_raw (Uncommon) — Acier
Loot: box_cool
Craft ingredient: smelt_steel_ingot @ Furnace
[**] material_titanium_raw (Rare) — Titane
Loot: box_epic
Craft ingredient: smelt_titanium_ingot @ Furnace
[**] material_carbonfiber_raw (Rare) — Fibre de carbone
Loot: box_legendary
Craft ingredient: forge_carbonfiber_sheet @ Forge
[***] material_carbonfiber_sheet (Epic) — Fibre de carbone
Craft ingredient: forge_armored_plate @ Forge
Craft ingredient: craft_box_epic @ Printer3D
Craft output: forge_carbonfiber_sheet @ Forge
[*] material_diamond_raw (Epic) — Diamant
Loot: box_legendary, box_black
[*] material_diamond_gem (Legendary) — Diamant
Loot: box_black
[NO USE] material_wood_nail (Common) — Bois
## Meta (34 items)
────────────────────────────────────────────────────────────────────────────────
[**] meta_colors (Rare) — Couleurs de texte
Loot: box_meta_basics
Unlock: TextColors
[**] meta_extended_colors (Rare) — Palette de couleurs étendue
Loot: box_meta_deep
Unlock: ExtendedColors
[**] meta_arrows (Epic) — Navigation avec les flèches
Loot: box_meta_interface
Unlock: ArrowKeySelection
[**] meta_inventory (Rare) — Panneau d'inventaire
Loot: box_meta_basics
Unlock: InventoryPanel
[**] meta_resources (Rare) — Panneau de caractéristiques
Loot: box_meta_basics
Unlock: ResourcePanel
[**] meta_stats (Rare) — Panneau de statistiques
Loot: box_meta_basics
Unlock: StatsPanel
[**] meta_portrait (Epic) — Panneau portrait
Loot: box_meta_basics
Unlock: PortraitPanel
[**] meta_chat (Epic) — Panneau de discussion
Loot: box_meta_deep
Unlock: ChatPanel
[**] meta_layout (Legendary) — Mise en page complète
Loot: box_meta_interface
Unlock: FullLayout
[**] meta_animation (Uncommon) — Animation d'ouverture de boîte
Loot: box_meta_mastery
Unlock: BoxAnimation
[**] meta_crafting (Epic) — Panneau de fabrication
Loot: box_meta_deep
Unlock: CraftingPanel
[**] meta_autosave (Uncommon) — Sauvegarde automatique
Loot: box_meta_mastery
Unlock: AutoSave
[**] meta_completion (Rare) — Suivi de complétion
Loot: box_meta_deep
Unlock: CompletionTracker
[**] blueprint_foundry (Uncommon) — Plan de Fonderie
Loot: box_improvement
Workstation: Foundry
[**] blueprint_workbench (Uncommon) — Plan d'Établi
Loot: box_improvement
Workstation: Workbench
[**] blueprint_furnace (Uncommon) — Plan de Fourneau
Loot: box_improvement
Workstation: Furnace
[**] blueprint_forge (Rare) — Plan de Forge
Loot: box_legendary
Workstation: Forge
[**] blueprint_engineer (Rare) — Plan de Bureau d'Ingénieur
Loot: box_improvement
Workstation: EngineerDesk
[**] blueprint_drawing (Uncommon) — Plan de Table à Dessin
Loot: box_improvement
Workstation: DrawingTable
[**] blueprint_engraving (Rare) — Plan de Banc de Gravure
Loot: box_legendary
Workstation: EngravingBench
[**] blueprint_pentacle (Epic) — Plan de Pentacle de Transformation
Loot: box_legendary
Workstation: TransformationPentacle
[**] blueprint_printer (Epic) — Plan d'Imprimante 3D
Loot: box_legendary
Workstation: Printer3D
[**] blueprint_synthesizer (Epic) — Plan de Synthétiseur de Matière
Loot: box_legendary
Workstation: MatterSynthesizer
[**] blueprint_genetic (Epic) — Plan de Station de Modification Génétique
Loot: box_legendary
Workstation: GeneticModStation
[**] blueprint_stasis (Epic) — Plan de Chambre de Stase
Loot: box_legendary
Workstation: StasisChamber
[*] meta_shortcuts (Rare) — Raccourcis clavier
Unlock: KeyboardShortcuts
[*] meta_resource_blood (Rare) — Sang
Loot: box_meta_resources
[*] meta_resource_gold (Uncommon) — Or
Loot: box_meta_resources
[*] meta_stat_strength (Rare) — Force
Loot: box_meta_mastery
[*] meta_stat_intelligence (Rare) — Intelligence
Loot: box_meta_mastery
[*] meta_stat_luck (Rare) — Chance
Loot: box_meta_mastery
[*] meta_stat_charisma (Rare) — Charisme
Loot: box_meta_mastery
[*] meta_stat_dexterity (Rare) — Dextérité
Loot: box_meta_mastery
[*] meta_stat_wisdom (Rare) — Sagesse
Loot: box_meta_mastery
## Orphan Items (no usage context)
────────────────────────────────────────────────────────────────────────────────
material_wood_nail (Material, Common)