diff --git a/TODO.md b/TODO.md
index 119b109..acfdf2c 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,28 +1,3 @@
# TODO
-Tous les items ont été traités. Ce fichier est conservé comme historique.
-
-## Noms de personnages
-
-Les noms des personnages dans les histoires ne sont pas traduits à l'affichage. Loreline ne propose a priori pas de système de traduction pour les noms de personnages donc il faudra des clés de traductions dédiées.
-
-## Partir à l'aventure
-
-Lorsqu'on débloque "Partir à l'aventure" mais qu'aucune aventure n'a été lancée il faut ajouter une incentive plus claire à lancer la première aventure. L'entrée devrait avec le préfix (new) ou (nouveau) et être en couleur (si la couleur est débloquée) tant qu'une aventure n'a pas été faite.
-
-## Retouch aventure Space
-
-Dans cette Space aventure, la fin tombe un peu abrute: le concours d'ouverture de boite n'a pas lieu, "Trade items with Zx'thorp" tourne court également.
-
-Attendu: Il faudrait un fin un peu plus sympa ou couper plus tôt (l'aventure peu être plus courte si besoin: la perfection est atteinte non pas lorsqu'il n'y a plus rien à ajouter mais plus rien à enlever !)
-
-Dans cette Space aventure le fuel semble être un sujet sauf qu'en réalité on ne peut pas tomber à court. Ce serait bien de rendre ce state vraiment utile (ex: si on utilise le fuel tôt on ne peut plus l'utiliser plus tard).
-
-## Unavailable
-
-Plutôt que d'avoir "unavailable" pour un choix à condition, ce sera mieux de donner un indice sur ce qu'il faut.
-Ex: "(Si j'avais plus de force…)" ou "(Si j'avais des jambes adaptées…)"
-
-## Journal d'événements
-
-pas utile après test: à supprimer complètement.
+Tous les items ont été traités.
diff --git a/content/adventures/space/intro.fr.lor b/content/adventures/space/intro.fr.lor
index 169eafd..fbd59bf 100644
--- a/content/adventures/space/intro.fr.lor
+++ b/content/adventures/space/intro.fr.lor
@@ -19,8 +19,8 @@ Examiner la boîte
#opt-ignore // "Ignore it and continue"
L'ignorer et continuer
-#opt-scan // "Run a deep scan first"
-Lancer un scan approfondi d'abord
+#opt-scan // "Run a deep scan first|||Fuel reserves insufficient for deep scan"
+Lancer un scan approfondi d'abord|||Réserves de carburant insuffisantes pour un scan approfondi
#deepscan-init // "Deep scan initiated. Fuel reserves reduced."
Scan approfondi lancé. Réserves de carburant réduites.
@@ -61,8 +61,8 @@ Ne me dites pas quels mots utiliser pour les boîtes, ARIA.
#opt-open-now // "Open it immediately"
L'ouvrir immédiatement
-#opt-scan-first // "Scan it first"
-La scanner d'abord
+#opt-scan-first // "Scan it first|||Not enough fuel for a scan"
+La scanner d'abord|||Pas assez de carburant pour un scan
#opt-poke // "Poke it with a stick"
La pousser avec un bâton
@@ -208,7 +208,7 @@ Ah, un autre appréciateur de boîtes ! Prenez ceci comme cadeau de ma collectio
#captain-wasbox // "The alien WAS the box?"
L'extraterrestre ÉTAIT la boîte ?
-#alien-intro // I'm Zx'thorp."
+#alien-intro // "I'm Zx'thorp."
Je suis Zx'thorp.
#open-normal // "Inside you find a Nebula Shard and what appears to be a map to more boxes."
@@ -238,27 +238,117 @@ J'apprécie les boîtes. Surtout celles avec du bon butin.
#opt-rare-box // "Ask about the galaxy's rarest box"
Demander quelle est la boîte la plus rare de la galaxie
-#alien-singularity // "The Singularity Box. It contains everything and nothing. Also a coupon."
-La Boîte Singulière. Elle contient tout et rien. Et aussi un coupon.
-
#opt-trade // "Trade items with Zx'thorp"
Échanger des objets avec Zx'thorp
-#alien-trade // "I have a Space Helmet for you. In exchange, I want your sense of wonder."
-J'ai un Casque Spatial pour vous. En échange, je veux votre sens de l'émerveillement.
-
-#captain-deal // "Deal. Wait--"
-Marché. Attendez--
-
#opt-contest // "Challenge the alien to a box-opening contest"
Défier l'extraterrestre dans un concours d'ouverture de boîtes
+#alien-singularity // "The Singularity Box. It contains everything and nothing. Also a coupon."
+La Boîte Singulière. Elle contient tout et rien. Et aussi un coupon.
+
+#captain-coupon // "A coupon? For what?"
+Un coupon ? Pour quoi ?
+
+#alien-coupon-answer // "For another Singularity Box. It's boxes all the way down."
+Pour une autre Boîte Singulière. Ce sont des boîtes jusqu'au bout.
+
+#captain-boxes-inside // "You're saying the universe is just boxes inside boxes?"
+Vous dites que l'univers n'est qu'une boîte dans des boîtes ?
+
+#alien-getting-it // "Now you're getting it, Captain."
+Vous commencez à comprendre, Capitaine.
+
+#ai-existential // "Commander, I'm detecting a slight existential crisis in your vitals."
+Commandant, je détecte une légère crise existentielle dans vos signes vitaux.
+
+#captain-fine // "I'm fine, ARIA. Just rethinking everything I know about reality."
+Ça va, ARIA. Je remets juste en question tout ce que je sais sur la réalité.
+
+#alien-trade-offer // "I have a Space Helmet for you. In exchange, I want... a story."
+J'ai un Casque Spatial pour vous. En échange, je veux... une histoire.
+
+#captain-story // "A story?"
+Une histoire ?
+
+#alien-first-box // "Tell me about the first box you ever opened."
+Racontez-moi la première boîte que vous avez ouverte.
+
+#captain-first-box-story // "It was small. Cardboard. My parents gave it to me. Inside was a toy rocket ship."
+Elle était petite. En carton. Mes parents me l'avaient offerte. À l'intérieur, il y avait une fusée jouet.
+
+#alien-pause // "..."
+...
+
+#alien-beautiful-trade // "That's the most beautiful thing I've ever heard."
+C'est la plus belle chose que j'aie jamais entendue.
+
+#alien-gives-helmet // "The alien removes its helmet and places it gently in your hands. Its eyes -- all seven of them -- are glistening."
+L'extraterrestre retire son casque et le place délicatement dans vos mains. Ses yeux -- les sept -- brillent.
+
+#captain-crying // "Are you... crying?"
+Vous... pleurez ?
+
+#alien-appreciation // "We don't cry. We leak appreciation fluid. It's different."
+Nous ne pleurons pas. Nous sécrétons du fluide d'appréciation. C'est différent.
+
#alien-dare // "You dare? No one out-opens Zx'thorp!"
Vous osez ? Personne ne surpasse Zx'thorp à l'ouverture !
#alien-contest // "...fine. Best of three. You open first."
...très bien. Au meilleur des trois. Vous ouvrez en premier.
+#opt-confident // "Open with confidence"
+Ouvrir avec assurance
+
+#opt-flair // "Open with theatrical flair"
+Ouvrir avec panache
+
+#flair-spin // "You crack your knuckles. You stretch your fingers. You do a little spin."
+Vous faites craquer vos doigts. Vous étirez vos mains. Vous faites une petite pirouette.
+
+#captain-watch // "Watch and learn, Zx'thorp."
+Regardez et apprenez, Zx'thorp.
+
+#flair-confetti // "You open the box with a flourish, confetti exploding from inside. Wait -- you didn't put confetti in there."
+Vous ouvrez la boîte avec un geste théâtral, des confettis explosent de l'intérieur. Attendez -- vous n'avez pas mis de confettis dedans.
+
+#alien-score-flair // "Theatrical. I approve. 8 out of 10."
+Théâtral. J'approuve. 8 sur 10.
+
+#ai-confetti // "Commander, where did the confetti come from?"
+Commandant, d'où viennent les confettis ?
+
+#captain-heart // "From the heart, ARIA. From the heart."
+Du cœur, ARIA. Du cœur.
+
+#contest-open // "You grip the box firmly and open it in one clean motion."
+Vous saisissez la boîte fermement et l'ouvrez d'un geste net.
+
+#alien-score // "Clean technique. 7 out of 10."
+Technique propre. 7 sur 10.
+
+#alien-produces // "Zx'thorp produces a box from somewhere. You try not to think about where."
+Zx'thorp sort une boîte de quelque part. Vous essayez de ne pas penser d'où.
+
+#alien-opens // "The alien opens it using three of its tentacles simultaneously. The lid flies off, does a triple backflip, and lands perfectly back on the box."
+L'extraterrestre l'ouvre avec trois de ses tentacules simultanément. Le couvercle s'envole, fait un triple salto arrière et atterrit parfaitement sur la boîte.
+
+#captain-impossible // "That's... that's not even possible."
+C'est... c'est même pas possible.
+
+#alien-score-2 // "10 out of 10. I win."
+10 sur 10. Je gagne.
+
+#ai-correct // "I'm afraid the alien is correct, Commander. That was objectively superior box-opening."
+Je crains que l'extraterrestre ait raison, Commandant. C'était objectivement une ouverture de boîte supérieure.
+
+#captain-rematch // "Rematch. One day. I'll be ready."
+Revanche. Un jour. Je serai prêt.
+
+#alien-waiting // "I look forward to it, Captain. Across the stars, I'll be waiting. With boxes."
+J'ai hâte, Capitaine. À travers les étoiles, je vous attendrai. Avec des boîtes.
+
#explore-course // "Setting course for the nearest box. ETA: approximately one dramatic pause."
Cap vers la boîte la plus proche. Arrivée estimée : environ une pause dramatique.
@@ -274,20 +364,53 @@ La grande semble contenir les deux autres. Ce sont des boîtes jusqu'au bout.
#opt-big // "Open the biggest box first"
Ouvrir la plus grande boîte en premier
+#opt-all // "Open all three at once|||Not enough fuel for maximum box velocity"
+Ouvrir les trois en même temps|||Pas assez de carburant pour la vitesse maximale de boîte
+
+#opt-happy // "Leave them orbiting, they seem happy"
+Les laisser en orbite, elles ont l'air heureuses
+
#big-open // "You open the biggest box. Inside: two medium boxes and a note."
Vous ouvrez la plus grande boîte. À l'intérieur : deux boîtes moyennes et un mot.
#big-note // "The note reads: \"Congratulations! You've found the Box Nebula. Population: boxes.\""
Le mot dit : \"Félicitations ! Vous avez trouvé la Nébuleuse des Boîtes. Population : des boîtes.\"
-#opt-all // "Open all three at once"
-Ouvrir les trois en même temps
+#captain-nebula // "There's a whole nebula of boxes?"
+Il y a une nébuleuse entière de boîtes ?
+
+#ai-billion // "Scanners confirm it, Commander. Approximately 4.7 billion boxes, all orbiting a supermassive box."
+Les scanners confirment, Commandant. Environ 4,7 milliards de boîtes, toutes en orbite autour d'une boîte supermassive.
+
+#opt-deeper // "We must go deeper"
+Il faut aller plus loin
+
+#opt-enough // "We've seen enough boxes for one day"
+On a vu assez de boîtes pour aujourd'hui
+
+#deeper-open // "You open the two medium boxes. Each contains two smaller boxes."
+Vous ouvrez les deux boîtes moyennes. Chacune contient deux boîtes plus petites.
+
+#captain-how-many // "ARIA, how many boxes is this now?"
+ARIA, ça fait combien de boîtes maintenant ?
+
+#ai-crashed // "I've lost count, Commander. My box-tracking subroutine has crashed."
+J'ai perdu le compte, Commandant. Mon sous-programme de suivi de boîtes a planté.
+
+#captain-count // "Then let the boxes count themselves."
+Alors laissons les boîtes se compter elles-mêmes.
+
+#ai-not-how // "...that's not how boxes work, Commander."
+...ce n'est pas comme ça que les boîtes fonctionnent, Commandant.
+
+#captain-should-be // "Maybe it should be."
+Peut-être que ça devrait l'être.
#captain-velocity // "All three, ARIA. Maximum box velocity."
Les trois, ARIA. Vitesse maximale de boîte.
-#ai-enthusiasm // "That's not a real measurement, but I admire your enthusiasm."
-Ce n'est pas une vraie mesure, mais j'admire votre enthousiasme.
+#ai-enthusiasm // "That's not a real measurement, but I admire your enthusiasm. Fuel reserves depleted for the maneuver."
+Ce n'est pas une vraie mesure, mais j'admire votre enthousiasme. Réserves de carburant épuisées pour la manœuvre.
#all-open // "All three boxes burst open simultaneously. The contents mix together in zero gravity."
Les trois boîtes s'ouvrent simultanément. Le contenu se mélange en apesanteur.
@@ -298,8 +421,14 @@ Commandant, les objets forment... une plus grande boîte.
#captain-ofcourse // "Of course they are."
Évidemment.
-#opt-happy // "Leave them orbiting, they seem happy"
-Les laisser en orbite, elles ont l'air heureuses
+#ai-proud // "A bigger, better box. It's vibrating at a frequency I've never seen. I think it's... proud of itself."
+Une plus grande, meilleure boîte. Elle vibre à une fréquence que je n'ai jamais vue. Je crois qu'elle est... fière d'elle-même.
+
+#captain-everything // "A proud box. Now I've seen everything."
+Une boîte fière. Maintenant j'ai tout vu.
+
+#ai-doubt // "Commander, I doubt that very much."
+Commandant, j'en doute fortement.
#captain-orbit // "Let them orbit in peace."
Laissons-les orbiter en paix.
@@ -307,9 +436,18 @@ Laissons-les orbiter en paix.
#ai-philosophical // "A surprisingly philosophical choice, Commander."
Un choix étonnamment philosophique, Commandant.
-#ai-happy // "The boxes seem to orbit faster. I think they're happy."
+#ai-happy-boxes // "The boxes seem to orbit faster. I think they're happy."
Les boîtes semblent orbiter plus vite. Je crois qu'elles sont heureuses.
+#captain-best-box // "See? Sometimes the best box is the one you don't open."
+Vous voyez ? Parfois la meilleure boîte est celle qu'on n'ouvre pas.
+
+#ai-wisest // "I'm going to write that down. That might be the wisest thing you've ever said."
+Je vais noter ça. C'est peut-être la chose la plus sage que vous ayez jamais dite.
+
+#captain-used-to-it // "Don't get used to it."
+N'y prenez pas goût.
+
#ending-complete // "Adventure complete. Updating ship's log."
Aventure terminée. Mise à jour du journal de bord.
diff --git a/content/adventures/space/intro.lor b/content/adventures/space/intro.lor
index a1108f0..01180c8 100644
--- a/content/adventures/space/intro.lor
+++ b/content/adventures/space/intro.lor
@@ -29,7 +29,7 @@ beat Intro
-> InvestigateBox
Ignore it and continue #opt-ignore
-> IgnoreBox
- Run a deep scan first #opt-scan if fuel > 20
+ Run a deep scan first|||Fuel reserves insufficient for deep scan #opt-scan if fuel > 20
fuel -= 20
-> DeepScan
@@ -57,7 +57,7 @@ beat InvestigateBox
choice
Open it immediately #opt-open-now
-> OpenSpaceBox
- Scan it first #opt-scan-first if fuel > 10
+ Scan it first|||Not enough fuel for a scan #opt-scan-first if fuel > 10
fuel -= 10
-> ScanThenOpen
Poke it with a stick #opt-poke
@@ -73,23 +73,23 @@ beat BoxWhisperer
captain: I'm not doing anything, ARIA. I'm just listening. #captain-listening
ai: The box has shared a direct route to its home system. No fuel cost. It's folding space around us like... #ai-folding
captain: Like folding a box? #captain-folding
- ai: I was going to say \"like origami,\" but yes. Like folding a box. #ai-origami
+ ai: I was going to say "like origami," but yes. Like folding a box. #ai-origami
trustAlien = true
discovered += 1
-> OpenSpaceBox
beat PokeBox
You extend the ship's robotic arm and gently poke the box. #poke-arm
- The box rotates 90 degrees and reveals a label: \"THIS SIDE UP\" #poke-label
- ai: Commander, I don't think boxes in zero gravity have an \"up\". #ai-gravity
+ The box rotates 90 degrees and reveals a label: "THIS SIDE UP" #poke-label
+ ai: Commander, I don't think boxes in zero gravity have an "up". #ai-gravity
captain: Maybe the box defines its own reality. #captain-reality
ai: That's... philosophically concerning. #ai-philosophy
-> OpenSpaceBox
beat ScanThenOpen
- ai: Scan reveals the box contains crystallized starlight and something called a \"Nebula Shard\". #scan-contents
+ ai: Scan reveals the box contains crystallized starlight and something called a "Nebula Shard". #scan-contents
ai: Also what appears to be a very small, very angry alien. #scan-alien
- captain: Define \"very small\". #captain-small
+ captain: Define "very small". #captain-small
ai: Approximately the size of a box. Commander, I think the alien IS a box. #ai-alienbox
-> OpenSpaceBox
@@ -132,7 +132,7 @@ beat LeaveForGood
ai: I hope you're happy, Commander. #ai-happy
captain: I am. No weird space boxes for me today. #captain-happy
ai: Incoming transmission. It's... from the box. #ai-transmission
- ai: It says \"you'll be back. they always come back.\" #ai-message
+ ai: It says "you'll be back. they always come back." #ai-message
captain: That's ominous. I love it. #captain-ominous
-> Ending
@@ -158,20 +158,72 @@ beat AlienEncounter
alien: I've been collecting boxes across the galaxy for centuries. #alien-collecting
alien: Most species open them. Very few appreciate them. #alien-appreciate
captain: I appreciate boxes. Especially ones with good loot. #captain-loot
- alien: \"Loot.\" What a crude word for cosmic treasures. #alien-crude
+ alien: "Loot." What a crude word for cosmic treasures. #alien-crude
choice
Ask about the galaxy's rarest box #opt-rare-box
- alien: The Singularity Box. It contains everything and nothing. Also a coupon. #alien-singularity
- -> Ending
+ -> RareBoxStory
Trade items with Zx'thorp #opt-trade
- alien: I have a Space Helmet for you. In exchange, I want your sense of wonder. #alien-trade
- captain: Deal. Wait-- #captain-deal
- -> Ending
+ -> AlienTrade
Challenge the alien to a box-opening contest #opt-contest
- alien: You dare? No one out-opens Zx'thorp! #alien-dare
- alien: ...fine. Best of three. You open first. #alien-contest
- -> Ending
+ -> BoxContest
+
+beat RareBoxStory
+ alien: The Singularity Box. It contains everything and nothing. Also a coupon. #alien-singularity
+ captain: A coupon? For what? #captain-coupon
+ alien: For another Singularity Box. It's boxes all the way down. #alien-coupon-answer
+ captain: You're saying the universe is just boxes inside boxes? #captain-boxes-inside
+ alien: Now you're getting it, Captain. #alien-getting-it
+ ai: Commander, I'm detecting a slight existential crisis in your vitals. #ai-existential
+ captain: I'm fine, ARIA. Just rethinking everything I know about reality. #captain-fine
+ -> Ending
+
+beat AlienTrade
+ alien: I have a Space Helmet for you. In exchange, I want... a story. #alien-trade-offer
+ captain: A story? #captain-story
+ alien: Tell me about the first box you ever opened. #alien-first-box
+ captain: It was small. Cardboard. My parents gave it to me. Inside was a toy rocket ship. #captain-first-box-story
+ alien: ... #alien-pause
+ alien: That's the most beautiful thing I've ever heard. #alien-beautiful-trade
+ The alien removes its helmet and places it gently in your hands. Its eyes -- all seven of them -- are glistening. #alien-gives-helmet
+ captain: Are you... crying? #captain-crying
+ alien: We don't cry. We leak appreciation fluid. It's different. #alien-appreciation
+ -> Ending
+
+beat BoxContest
+ alien: You dare? No one out-opens Zx'thorp! #alien-dare
+ alien: ...fine. Best of three. You open first. #alien-contest
+
+ choice
+ Open with confidence #opt-confident
+ -> ContestRoundOne
+ Open with theatrical flair #opt-flair
+ -> ContestFlair
+
+beat ContestFlair
+ markSecretBranch("space_box_flair")
+ You crack your knuckles. You stretch your fingers. You do a little spin. #flair-spin
+ captain: Watch and learn, Zx'thorp. #captain-watch
+ You open the box with a flourish, confetti exploding from inside. Wait -- you didn't put confetti in there. #flair-confetti
+ alien: Theatrical. I approve. 8 out of 10. #alien-score-flair
+ ai: Commander, where did the confetti come from? #ai-confetti
+ captain: From the heart, ARIA. From the heart. #captain-heart
+ -> ContestAlienTurn
+
+beat ContestRoundOne
+ You grip the box firmly and open it in one clean motion. #contest-open
+ alien: Clean technique. 7 out of 10. #alien-score
+ -> ContestAlienTurn
+
+beat ContestAlienTurn
+ Zx'thorp produces a box from somewhere. You try not to think about where. #alien-produces
+ The alien opens it using three of its tentacles simultaneously. The lid flies off, does a triple backflip, and lands perfectly back on the box. #alien-opens
+ captain: That's... that's not even possible. #captain-impossible
+ alien: 10 out of 10. I win. #alien-score-2
+ ai: I'm afraid the alien is correct, Commander. That was objectively superior box-opening. #ai-correct
+ captain: Rematch. One day. I'll be ready. #captain-rematch
+ alien: I look forward to it, Captain. Across the stars, I'll be waiting. With boxes. #alien-waiting
+ -> Ending
beat SpaceExploration
ai: Setting course for the nearest box. ETA: approximately one dramatic pause. #explore-course
@@ -182,22 +234,54 @@ beat SpaceExploration
choice
Open the biggest box first #opt-big
- You open the biggest box. Inside: two medium boxes and a note. #big-open
- The note reads: \"Congratulations! You've found the Box Nebula. Population: boxes.\" #big-note
- -> Ending
- Open all three at once #opt-all
- captain: All three, ARIA. Maximum box velocity. #captain-velocity
- ai: That's not a real measurement, but I admire your enthusiasm. #ai-enthusiasm
- All three boxes burst open simultaneously. The contents mix together in zero gravity. #all-open
- ai: Commander, the items are forming... a bigger box. #ai-biggerbox
- captain: Of course they are. #captain-ofcourse
- -> Ending
+ -> BigBox
+ Open all three at once|||Not enough fuel for maximum box velocity #opt-all if fuel >= 30
+ fuel -= 30
+ -> AllBoxes
Leave them orbiting, they seem happy #opt-happy
- captain: Let them orbit in peace. #captain-orbit
- ai: A surprisingly philosophical choice, Commander. #ai-philosophical
- ai: The boxes seem to orbit faster. I think they're happy. #ai-happy
+ -> HappyBoxes
+
+beat BigBox
+ You open the biggest box. Inside: two medium boxes and a note. #big-open
+ The note reads: "Congratulations! You've found the Box Nebula. Population: boxes." #big-note
+ captain: There's a whole nebula of boxes? #captain-nebula
+ ai: Scanners confirm it, Commander. Approximately 4.7 billion boxes, all orbiting a supermassive box. #ai-billion
+
+ choice
+ We must go deeper #opt-deeper
+ -> GoDeeper
+ We've seen enough boxes for one day #opt-enough
-> Ending
+beat GoDeeper
+ You open the two medium boxes. Each contains two smaller boxes. #deeper-open
+ captain: ARIA, how many boxes is this now? #captain-how-many
+ ai: I've lost count, Commander. My box-tracking subroutine has crashed. #ai-crashed
+ captain: Then let the boxes count themselves. #captain-count
+ ai: ...that's not how boxes work, Commander. #ai-not-how
+ captain: Maybe it should be. #captain-should-be
+ -> Ending
+
+beat AllBoxes
+ captain: All three, ARIA. Maximum box velocity. #captain-velocity
+ ai: That's not a real measurement, but I admire your enthusiasm. Fuel reserves depleted for the maneuver. #ai-enthusiasm
+ All three boxes burst open simultaneously. The contents mix together in zero gravity. #all-open
+ ai: Commander, the items are forming... a bigger box. #ai-biggerbox
+ captain: Of course they are. #captain-ofcourse
+ ai: A bigger, better box. It's vibrating at a frequency I've never seen. I think it's... proud of itself. #ai-proud
+ captain: A proud box. Now I've seen everything. #captain-everything
+ ai: Commander, I doubt that very much. #ai-doubt
+ -> Ending
+
+beat HappyBoxes
+ captain: Let them orbit in peace. #captain-orbit
+ ai: A surprisingly philosophical choice, Commander. #ai-philosophical
+ ai: The boxes seem to orbit faster. I think they're happy. #ai-happy-boxes
+ captain: See? Sometimes the best box is the one you don't open. #captain-best-box
+ ai: I'm going to write that down. That might be the wisest thing you've ever said. #ai-wisest
+ captain: Don't get used to it. #captain-used-to-it
+ -> Ending
+
beat Ending
ai: Adventure complete. Updating ship's log. #ending-complete
ai: Boxes encountered: $discovered. Box-related existential crises: 1. #ending-log
diff --git a/content/data/boxes.json b/content/data/boxes.json
index 6fe6aa6..7e094fd 100644
--- a/content/data/boxes.json
+++ b/content/data/boxes.json
@@ -238,7 +238,6 @@
"rollCount": 1,
"entries": [
{"itemDefinitionId": "meta_completion", "weight": 3},
- {"itemDefinitionId": "meta_chat", "weight": 3},
{"itemDefinitionId": "meta_crafting", "weight": 3},
{"itemDefinitionId": "meta_extended_colors", "weight": 3},
{"itemDefinitionId": "box_meta_resources", "weight": 1}
diff --git a/content/data/items.json b/content/data/items.json
index 0a9df4c..81c9c83 100644
--- a/content/data/items.json
+++ b/content/data/items.json
@@ -6,7 +6,6 @@
{"id": "meta_resources", "nameKey": "meta.resources", "descriptionKey": "meta.resources.desc", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "ResourcePanel"},
{"id": "meta_stats", "nameKey": "meta.stats", "descriptionKey": "meta.stats.desc", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "StatsPanel"},
{"id": "meta_portrait", "nameKey": "meta.portrait", "descriptionKey": "meta.portrait.desc", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "PortraitPanel"},
- {"id": "meta_chat", "nameKey": "meta.chat", "descriptionKey": "meta.chat.desc", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "ChatPanel"},
{"id": "meta_layout", "nameKey": "meta.layout", "descriptionKey": "meta.layout.desc", "category": "Meta", "rarity": "Legendary", "tags": ["Meta"], "metaUnlock": "FullLayout"},
{"id": "meta_shortcuts", "nameKey": "meta.shortcuts", "descriptionKey": "meta.shortcuts.desc", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "KeyboardShortcuts"},
{"id": "meta_animation", "nameKey": "meta.animation", "descriptionKey": "meta.animation.desc", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta"], "metaUnlock": "BoxAnimation"},
diff --git a/content/strings/en.json b/content/strings/en.json
index e79be37..f11faf4 100644
--- a/content/strings/en.json
+++ b/content/strings/en.json
@@ -19,6 +19,7 @@
"action.appearance": "Change appearance",
"action.save": "Save",
"action.quit": "Return to menu",
+ "action.new": "NEW",
"prompt.name": "What is your name, brave box-opener?",
"prompt.choose_action": "What would you like to do?",
@@ -115,7 +116,6 @@
"meta.resources": "Characteristics Panel",
"meta.stats": "Stats Panel",
"meta.portrait": "Portrait Panel",
- "meta.chat": "Chat Panel",
"meta.layout": "Full Layout Mode",
"meta.shortcuts": "Keyboard Shortcuts",
"meta.animation": "Box Opening Animation",
@@ -300,8 +300,6 @@
"lore.name_9": "Fragment: The Manual",
"lore.name_10": "Fragment: Schrödinger",
- "log.title": "Event Log",
-
"cookie.1": "A box within a box is still a box.",
"cookie.2": "ERROR: This cookie contains no fortune. Please try again.",
"cookie.3": "You will open many boxes. This prediction has a 100% accuracy rate.",
@@ -431,6 +429,7 @@
"adventure.none_available": "No adventures available yet. Keep opening boxes!",
"adventure.coming_soon": "Adventure '{0}' is coming soon! The boxes are still being assembled.",
"adventure.done": "Done",
+ "adventure.unavailable": "Unavailable",
"adventure.unlocked": "🎉 New adventure unlocked! Discover '{0}' in the adventure menu!",
"adventure.name.Space": "Starbound",
"adventure.name.Medieval": "Castle Cardboard",
@@ -442,6 +441,27 @@
"adventure.name.Microscopic": "Cell Division",
"adventure.name.DarkFantasy": "Ashen Wastes",
"adventure.name.Destiny": "Gallery of Echoes",
+
+ "character.captain_nova": "Captain Nova",
+ "character.aria": "ARIA",
+ "character.zxthorp": "Zx'thorp",
+ "character.sir_boxalot": "Sir Boxalot",
+ "character.malkith": "Malkith",
+ "character.blackbeard_the_unboxable": "Blackbeard the Unboxable",
+ "character.linu": "Linu",
+ "character.sandrea": "Sandrea",
+ "character.samuel": "Samuel",
+ "character.mordecai_the_grim": "Mordecai the Grim",
+ "character.pierrick": "Pierrick",
+ "character.grug": "Grug",
+ "character.duncan": "Duncan",
+ "character.chenda": "Chenda",
+ "character.farah": "Farah",
+ "character.dr._quantum": "Dr. Quantum",
+ "character.the_box_entity": "The Box Entity",
+ "character.dr._cellina": "Dr. Cellina",
+ "character.mitochondria_mike": "Mitochondria Mike",
+ "character.the_box": "The Box",
"ui.inventory": "Inventory",
"stats.boxes_opened": "Boxes Opened",
"stats.title": "Stats",
@@ -481,7 +501,6 @@
"meta.resources.desc": "Displays your character's characteristics (health, mana, etc.).",
"meta.stats.desc": "Shows your progression stats and character attributes.",
"meta.portrait.desc": "Displays your character's visual appearance.",
- "meta.chat.desc": "Shows an event log of recent actions.",
"meta.layout.desc": "Arranges all panels into a full dashboard layout.",
"meta.shortcuts.desc": "Enables keyboard shortcuts for quick actions.",
"meta.animation.desc": "Adds opening animations when unboxing.",
diff --git a/content/strings/fr.json b/content/strings/fr.json
index e6dc937..ce6bab5 100644
--- a/content/strings/fr.json
+++ b/content/strings/fr.json
@@ -19,6 +19,7 @@
"action.appearance": "Changer d'apparence",
"action.save": "Sauvegarder",
"action.quit": "Retourner au menu",
+ "action.new": "NOUVEAU",
"prompt.name": "Quel est ton nom, brave ouvreur de boîtes ?",
"prompt.choose_action": "Que veux-tu faire ?",
@@ -115,7 +116,6 @@
"meta.resources": "Panneau de caractéristiques",
"meta.stats": "Panneau de statistiques",
"meta.portrait": "Panneau portrait",
- "meta.chat": "Panneau de discussion",
"meta.layout": "Mise en page complète",
"meta.shortcuts": "Raccourcis clavier",
"meta.animation": "Animation d'ouverture de boîte",
@@ -300,8 +300,6 @@
"lore.name_9": "Fragment : Le Manuel",
"lore.name_10": "Fragment : Schrödinger",
- "log.title": "Journal d'événements",
-
"cookie.1": "Une boîte dans une boîte reste une boîte.",
"cookie.2": "ERREUR : Ce cookie ne contient aucune fortune. Réessayez.",
"cookie.3": "Vous ouvrirez beaucoup de boîtes. Cette prédiction a un taux de précision de 100%.",
@@ -431,6 +429,7 @@
"adventure.none_available": "Aucune aventure disponible. Continue à ouvrir des boîtes !",
"adventure.coming_soon": "L'aventure '{0}' arrive bientôt ! Les boîtes sont encore en cours d'assemblage.",
"adventure.done": "Terminée",
+ "adventure.unavailable": "Indisponible",
"adventure.unlocked": "🎉 Nouvelle aventure débloquée ! Découvre '{0}' dans « Partir à l'aventure » !",
"adventure.name.Space": "Odyssée stellaire",
"adventure.name.Medieval": "Château Carton",
@@ -442,6 +441,28 @@
"adventure.name.Microscopic": "Division cellulaire",
"adventure.name.DarkFantasy": "Terres Cendrées",
"adventure.name.Destiny": "Galerie des Échos",
+
+ "character.captain_nova": "Capitaine Nova",
+ "character.aria": "ARIA",
+ "character.zxthorp": "Zx'thorp",
+ "character.sir_boxalot": "Sire Boîtalot",
+ "character.malkith": "Malkith",
+ "character.blackbeard_the_unboxable": "Barbe-Noire l'Inouvrable",
+ "character.linu": "Linu",
+ "character.sandrea": "Sandrea",
+ "character.samuel": "Samuel",
+ "character.mordecai_the_grim": "Mordecai le Sinistre",
+ "character.pierrick": "Pierrick",
+ "character.grug": "Grug",
+ "character.duncan": "Duncan",
+ "character.chenda": "Chenda",
+ "character.farah": "Farah",
+ "character.dr._quantum": "Dr. Quantum",
+ "character.the_box_entity": "L'Entité Boîte",
+ "character.dr._cellina": "Dr. Cellina",
+ "character.mitochondria_mike": "Mike Mitochondrie",
+ "character.the_box": "La Boîte",
+
"ui.inventory": "Inventaire",
"stats.boxes_opened": "Boîtes ouvertes",
"stats.title": "Stats",
@@ -481,7 +502,6 @@
"meta.resources.desc": "Affiche les caractéristiques de ton personnage (santé, mana, etc.).",
"meta.stats.desc": "Affiche ta progression et les attributs de ton personnage.",
"meta.portrait.desc": "Affiche l'apparence visuelle de ton personnage.",
- "meta.chat.desc": "Affiche un journal des événements récents.",
"meta.layout.desc": "Organise tous les panneaux en tableau de bord complet.",
"meta.shortcuts.desc": "Active les raccourcis clavier pour des actions rapides.",
"meta.animation.desc": "Ajoute des animations d'ouverture de boîtes.",
diff --git a/src/OpenTheBox/Adventures/AdventureEngine.cs b/src/OpenTheBox/Adventures/AdventureEngine.cs
index 567815e..1280327 100644
--- a/src/OpenTheBox/Adventures/AdventureEngine.cs
+++ b/src/OpenTheBox/Adventures/AdventureEngine.cs
@@ -190,7 +190,16 @@ public sealed class AdventureEngine
private void HandleDialogue(Loreline.Interpreter.Dialogue dialogue)
{
- _renderer.ShowAdventureDialogue(dialogue.Character, dialogue.Text);
+ // Translate character name if a localization key exists
+ string? displayName = dialogue.Character;
+ if (displayName is not null)
+ {
+ string key = $"character.{displayName.ToLowerInvariant().Replace(" ", "_").Replace("'", "")}";
+ string localized = _loc.Get(key);
+ if (!localized.StartsWith("[MISSING:"))
+ displayName = localized;
+ }
+ _renderer.ShowAdventureDialogue(displayName, dialogue.Text);
_renderer.WaitForKeyPress();
dialogue.Callback();
}
@@ -211,7 +220,8 @@ public sealed class AdventureEngine
}
else
{
- options.Add($"(unavailable) {text}");
+ string prefix = hint ?? _loc.Get("adventure.unavailable");
+ options.Add($"({prefix}) {text}");
hints.Add(hint);
}
}
diff --git a/src/OpenTheBox/Core/Enums/UIFeature.cs b/src/OpenTheBox/Core/Enums/UIFeature.cs
index 97d5931..f16a02c 100644
--- a/src/OpenTheBox/Core/Enums/UIFeature.cs
+++ b/src/OpenTheBox/Core/Enums/UIFeature.cs
@@ -28,9 +28,6 @@ public enum UIFeature
/// Phase 6: ASCII art portrait reflecting equipped cosmetics.
PortraitPanel,
- /// Phase 6: Chat panel for NPC dialogues and narrative events.
- ChatPanel,
-
/// Phase 8: Complete multi-panel layout with all UI elements organized.
FullLayout,
diff --git a/src/OpenTheBox/Core/GameState.cs b/src/OpenTheBox/Core/GameState.cs
index 051219b..23494c5 100644
--- a/src/OpenTheBox/Core/GameState.cs
+++ b/src/OpenTheBox/Core/GameState.cs
@@ -41,12 +41,6 @@ public sealed class GameState
public HashSet AvailableTextColors { get; set; } = [];
public List ActiveCraftingJobs { get; set; } = [];
- ///
- /// In-memory event log shown in the ChatPanel (not persisted to save).
- ///
- [System.Text.Json.Serialization.JsonIgnore]
- public List EventLog { get; set; } = [];
-
///
/// Returns the current value of a resource, or 0 if the resource is not tracked.
///
diff --git a/src/OpenTheBox/Program.cs b/src/OpenTheBox/Program.cs
index 342039c..33b32b1 100644
--- a/src/OpenTheBox/Program.cs
+++ b/src/OpenTheBox/Program.cs
@@ -391,7 +391,12 @@ public static class Program
actions.Add((_loc.Get("action.inventory"), "inventory"));
if (_state.UnlockedAdventures.Count > 0)
- actions.Add((_loc.Get("action.adventure"), "adventure"));
+ {
+ string adventureLabel = _loc.Get("action.adventure");
+ if (_state.CompletedAdventures.Count == 0)
+ adventureLabel = $"({_loc.Get("action.new")}) {adventureLabel}";
+ actions.Add((adventureLabel, "adventure"));
+ }
if (_state.UnlockedCosmetics.Count > 0)
actions.Add((_loc.Get("action.appearance"), "appearance"));
@@ -503,7 +508,6 @@ public static class Program
RefreshRenderer();
var featureLabel = _loc.Get(GetUIFeatureLocKey(uiEvt.Feature));
_renderer.ShowUIFeatureUnlocked(featureLabel);
- AddEventLog($"* {featureLabel}");
_renderer.WaitForKeyPress(_loc.Get("prompt.press_key"));
break;
@@ -524,7 +528,6 @@ public static class Program
case ResourceChangedEvent resEvt:
var resName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}");
_renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
- AddEventLog($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
break;
case MessageEvent msgEvt:
@@ -542,7 +545,6 @@ public static class Program
case AdventureUnlockedEvent advUnlockedEvt:
var advName = GetAdventureName(advUnlockedEvt.Theme);
_renderer.ShowMessage(_loc.Get("adventure.unlocked", advName));
- AddEventLog($">> {advName}");
break;
case AdventureStartedEvent advEvt:
@@ -582,10 +584,6 @@ public static class Program
{
_renderer.ShowLootReveal(allLoot);
- // Proposal 6A: Feed loot to the event log
- foreach (var (name, rarity, _) in allLoot)
- AddEventLog($"+ {name} [{_loc.Get($"rarity.{rarity.ToLower()}")}]");
-
// Resource summary removed — characteristics are shown in the dedicated panel
}
@@ -753,7 +751,6 @@ public static class Program
: _loc.Get("inventory.item_used", itemName);
_renderer.ShowMessage(usedMsg);
_renderer.ShowMessage($"{resName}: {resEvt.OldValue} {UnicodeSupport.Arrow} {resEvt.NewValue}");
- AddEventLog($"{itemName} {UnicodeSupport.Arrow} {resName} {resEvt.OldValue}{UnicodeSupport.Arrow}{resEvt.NewValue}");
break;
case MessageEvent msgEvt:
_renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? []));
@@ -1000,7 +997,6 @@ public static class Program
UIFeature.ResourcePanel => "meta.resources",
UIFeature.StatsPanel => "meta.stats",
UIFeature.PortraitPanel => "meta.portrait",
- UIFeature.ChatPanel => "meta.chat",
UIFeature.FullLayout => "meta.layout",
UIFeature.KeyboardShortcuts => "meta.shortcuts",
UIFeature.BoxAnimation => "meta.animation",
@@ -1018,18 +1014,6 @@ public static class Program
return name.StartsWith("[MISSING:") ? theme.ToString() : name;
}
- private const int MaxEventLogEntries = 20;
-
- ///
- /// Adds a message to the in-memory event log displayed in the ChatPanel.
- ///
- private static void AddEventLog(string message)
- {
- _state.EventLog.Add(message);
- if (_state.EventLog.Count > MaxEventLogEntries)
- _state.EventLog.RemoveAt(0);
- }
-
private static string GetLocalizedName(string definitionId)
{
var itemDef = _registry.GetItem(definitionId);
diff --git a/src/OpenTheBox/Rendering/Panels/ChatPanel.cs b/src/OpenTheBox/Rendering/Panels/ChatPanel.cs
deleted file mode 100644
index 67b23d3..0000000
--- a/src/OpenTheBox/Rendering/Panels/ChatPanel.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using OpenTheBox.Localization;
-using Spectre.Console;
-using Spectre.Console.Rendering;
-
-namespace OpenTheBox.Rendering.Panels;
-
-///
-/// Renders a compact event log panel showing recent game events.
-/// Replaces the original dialogue-only chat panel with a general-purpose log.
-///
-public static class ChatPanel
-{
- private const int MaxVisibleMessages = 8;
-
- ///
- /// Builds a renderable event log from a list of recent log messages.
- ///
- public static IRenderable Render(List logMessages, LocalizationManager? loc = null)
- {
- var rows = new List();
-
- // Show only the most recent messages
- var visible = logMessages.Count > MaxVisibleMessages
- ? logMessages.Skip(logMessages.Count - MaxVisibleMessages).ToList()
- : logMessages;
-
- if (visible.Count == 0)
- {
- var emptyText = loc?.Get("craft.panel.empty") ?? "...";
- rows.Add(new Markup($"[dim]{Markup.Escape(emptyText)}[/]"));
- }
- else
- {
- foreach (var msg in visible)
- {
- rows.Add(new Markup($"[dim]{Markup.Escape(msg)}[/]"));
- }
- }
-
- var title = loc?.Get("log.title") ?? "Log";
- return new Panel(new Rows(rows))
- .Header($"[bold aqua]{Markup.Escape(title)}[/]")
- .Border(BoxBorder.Rounded);
- }
-}
diff --git a/src/OpenTheBox/Rendering/RenderContext.cs b/src/OpenTheBox/Rendering/RenderContext.cs
index 16ddd38..6fb3946 100644
--- a/src/OpenTheBox/Rendering/RenderContext.cs
+++ b/src/OpenTheBox/Rendering/RenderContext.cs
@@ -31,7 +31,6 @@ public sealed class RenderContext
public bool HasResourcePanel => Has(UIFeature.ResourcePanel);
public bool HasStatsPanel => Has(UIFeature.StatsPanel);
public bool HasPortraitPanel => Has(UIFeature.PortraitPanel);
- public bool HasChatPanel => Has(UIFeature.ChatPanel);
public bool HasFullLayout => Has(UIFeature.FullLayout);
public bool HasKeyboardShortcuts => Has(UIFeature.KeyboardShortcuts);
public bool HasBoxAnimation => Has(UIFeature.BoxAnimation);
diff --git a/src/OpenTheBox/Rendering/RendererFactory.cs b/src/OpenTheBox/Rendering/RendererFactory.cs
index 73a90f5..ac72344 100644
--- a/src/OpenTheBox/Rendering/RendererFactory.cs
+++ b/src/OpenTheBox/Rendering/RendererFactory.cs
@@ -24,7 +24,6 @@ public static class RendererFactory
context.HasResourcePanel ||
context.HasStatsPanel ||
context.HasPortraitPanel ||
- context.HasChatPanel ||
context.HasFullLayout ||
context.HasKeyboardShortcuts ||
context.HasBoxAnimation ||
diff --git a/src/OpenTheBox/Rendering/SpectreRenderer.cs b/src/OpenTheBox/Rendering/SpectreRenderer.cs
index 40722d4..335bffa 100644
--- a/src/OpenTheBox/Rendering/SpectreRenderer.cs
+++ b/src/OpenTheBox/Rendering/SpectreRenderer.cs
@@ -496,9 +496,6 @@ public sealed class SpectreRenderer : IRenderer
if (context.HasCraftingPanel)
rightItems.Add(CraftingPanel.Render(state, _registry, _loc));
- if (context.HasChatPanel)
- rightItems.Add(ChatPanel.Render(state.EventLog, _loc));
-
if (context.HasCompletionTracker)
rightItems.Add(new Markup($"[bold cyan]{Markup.Escape(_loc.Get("ui.completion", context.CompletionPercent))}[/]"));
@@ -547,9 +544,6 @@ public sealed class SpectreRenderer : IRenderer
if (context.HasCraftingPanel)
AnsiConsole.Write(CraftingPanel.Render(state, _registry, _loc));
- if (context.HasChatPanel)
- AnsiConsole.Write(ChatPanel.Render(state.EventLog, _loc));
-
if (context.HasCompletionTracker)
AnsiConsole.Write(new Rule($"[bold cyan]{Markup.Escape(_loc.Get("ui.completion", context.CompletionPercent))}[/]").RuleStyle("cyan"));
}
diff --git a/src/OpenTheBox/Simulation/MetaEngine.cs b/src/OpenTheBox/Simulation/MetaEngine.cs
index 5c07054..79f418a 100644
--- a/src/OpenTheBox/Simulation/MetaEngine.cs
+++ b/src/OpenTheBox/Simulation/MetaEngine.cs
@@ -26,7 +26,6 @@ public class MetaEngine
"meta_stats", // StatsPanel
"meta_resources", // ResourcePanel
"meta_portrait", // PortraitPanel
- "meta_chat", // ChatPanel
"meta_extended_colors", // ExtendedColors
"meta_animation", // BoxAnimation
"meta_crafting", // CraftingPanel
diff --git a/tests/OpenTheBox.Tests/RendererTests.cs b/tests/OpenTheBox.Tests/RendererTests.cs
index f2edaa1..c807c95 100644
--- a/tests/OpenTheBox.Tests/RendererTests.cs
+++ b/tests/OpenTheBox.Tests/RendererTests.cs
@@ -348,60 +348,6 @@ public class InventoryPanelTests
}
}
-public class ChatPanelTests
-{
- [Fact]
- public void Render_Empty_DoesNotThrow()
- {
- var result = RenderHelper.RenderToString(ChatPanel.Render([]));
- Assert.NotEmpty(result);
- }
-
- [Fact]
- public void Render_SingleMessage_DoesNotThrow()
- {
- var messages = new List { "The door creaks open." };
- var result = RenderHelper.RenderToString(ChatPanel.Render(messages));
- Assert.NotEmpty(result);
- }
-
- [Fact]
- public void Render_MultipleMessages_DoesNotThrow()
- {
- var messages = new List
- {
- "+ Small Health Potion [Common]",
- "★ Text Colors",
- "Health: 0 → 10",
- "🗺 Starbound"
- };
- var result = RenderHelper.RenderToString(ChatPanel.Render(messages));
- Assert.NotEmpty(result);
- }
-
- [Fact]
- public void Render_OverflowMessages_ShowsOnlyRecent()
- {
- var messages = Enumerable.Range(0, 15)
- .Select(i => $"Event {i}")
- .ToList();
- var result = RenderHelper.RenderToString(ChatPanel.Render(messages));
- Assert.NotEmpty(result);
- }
-
- [Fact]
- public void Render_SpecialCharacters_DoesNotThrow()
- {
- var messages = new List
- {
- "+ [Boss] item ",
- "A [mysterious] voice echoes."
- };
- var result = RenderHelper.RenderToString(ChatPanel.Render(messages));
- Assert.NotEmpty(result);
- }
-}
-
// ── RenderContext + RendererFactory Tests ────────────────────────────────
public class RenderContextTests
@@ -457,7 +403,6 @@ public class RenderContextTests
Assert.False(ctx.HasResourcePanel);
Assert.False(ctx.HasStatsPanel);
Assert.False(ctx.HasPortraitPanel);
- Assert.False(ctx.HasChatPanel);
Assert.False(ctx.HasFullLayout);
Assert.False(ctx.HasKeyboardShortcuts);
Assert.False(ctx.HasBoxAnimation);
@@ -488,7 +433,6 @@ public class RendererFactoryTests
[InlineData(UIFeature.ResourcePanel)]
[InlineData(UIFeature.StatsPanel)]
[InlineData(UIFeature.PortraitPanel)]
- [InlineData(UIFeature.ChatPanel)]
[InlineData(UIFeature.FullLayout)]
[InlineData(UIFeature.KeyboardShortcuts)]
[InlineData(UIFeature.BoxAnimation)]
@@ -749,7 +693,6 @@ public class SpectreRendererOutputTests : IDisposable
ctx.Unlock(UIFeature.StatsPanel);
ctx.Unlock(UIFeature.ResourcePanel);
ctx.Unlock(UIFeature.InventoryPanel);
- ctx.Unlock(UIFeature.ChatPanel);
ctx.Unlock(UIFeature.CompletionTracker);
ctx.CompletionPercent = 42;
var r = new SpectreRenderer(ctx, _loc);
diff --git a/tests/OpenTheBox.Tests/UnitTest1.cs b/tests/OpenTheBox.Tests/UnitTest1.cs
index c6b53ed..3ebf6db 100644
--- a/tests/OpenTheBox.Tests/UnitTest1.cs
+++ b/tests/OpenTheBox.Tests/UnitTest1.cs
@@ -420,7 +420,6 @@ public class ContentValidationTests
[UIFeature.ResourcePanel] = "meta.resources",
[UIFeature.StatsPanel] = "meta.stats",
[UIFeature.PortraitPanel] = "meta.portrait",
- [UIFeature.ChatPanel] = "meta.chat",
[UIFeature.FullLayout] = "meta.layout",
[UIFeature.KeyboardShortcuts] = "meta.shortcuts",
[UIFeature.BoxAnimation] = "meta.animation",
@@ -1889,6 +1888,29 @@ public class ContentValidationTests
}
}
+ // Workstation summary: what each bench can craft
+ report.AppendLine("## Workstations");
+ report.AppendLine(new string('─', 80));
+ var recipesByStation = registry.Recipes.Values
+ .GroupBy(r => r.Workstation)
+ .OrderBy(g => g.Key.ToString());
+ foreach (var stationGroup in recipesByStation)
+ {
+ report.AppendLine($" 🔨 {stationGroup.Key}");
+ foreach (var recipe in stationGroup.OrderBy(r => r.Id))
+ {
+ var ingredientNames = recipe.Ingredients
+ .Select(i => registry.Items.TryGetValue(i.ItemDefinitionId, out var iDef)
+ ? $"{loc.Get(iDef.NameKey)} x{i.Quantity}"
+ : $"{i.ItemDefinitionId} x{i.Quantity}");
+ var resultName = registry.Items.TryGetValue(recipe.Result.ItemDefinitionId, out var rDef)
+ ? loc.Get(rDef.NameKey)
+ : recipe.Result.ItemDefinitionId;
+ report.AppendLine($" {recipe.Id}: {string.Join(" + ", ingredientNames)} → {resultName} x{recipe.Result.Quantity}");
+ }
+ report.AppendLine();
+ }
+
// Orphan check: items with no usage at all
report.AppendLine("## Orphan Items (no usage context)");
report.AppendLine(new string('─', 80));
@@ -1903,9 +1925,9 @@ public class ContentValidationTests
string reportText = report.ToString();
- // 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");
+ // Write snapshot to tests/snapshots/ relative to the test project file
+ var testProjectDir = Path.GetDirectoryName(GetThisFilePath())!;
+ var snapshotDir = Path.Combine(testProjectDir, "..", "snapshots");
Directory.CreateDirectory(snapshotDir);
var snapshotPath = Path.Combine(snapshotDir, "item_utility_report.txt");
@@ -1950,4 +1972,6 @@ public class ContentValidationTests
if (item.Tags.Contains("Cookie")) count++;
return count;
}
+
+ private static string GetThisFilePath([System.Runtime.CompilerServices.CallerFilePath] string path = "") => path;
}
diff --git a/tests/snapshots/item_utility_report.txt b/tests/snapshots/item_utility_report.txt
index 26083e2..4f8bc04 100644
--- a/tests/snapshots/item_utility_report.txt
+++ b/tests/snapshots/item_utility_report.txt
@@ -1,5 +1,5 @@
# Item Utility Report
-# Total items: 146
+# Total items: 145
# Total boxes: 31
# Total recipes: 18
@@ -481,7 +481,7 @@
[NO USE] material_wood_nail (Common) — Bois
-## Meta (34 items)
+## Meta (33 items)
────────────────────────────────────────────────────────────────────────────────
[**] meta_colors (Rare) — Couleurs de texte
Loot: box_meta_basics
@@ -511,10 +511,6 @@
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
@@ -610,6 +606,50 @@
[*] meta_stat_wisdom (Rare) — Sagesse
Loot: box_meta_mastery
+## Workstations
+────────────────────────────────────────────────────────────────────────────────
+ 🔨 DrawingTable
+ chart_star_navigation: Coordonnées mystérieuses x1 + Carte stellaire x1 → Clé d'accès au sas x1
+
+ 🔨 EngineerDesk
+ engineer_rocket_boots: Short x1 + Titane x1 + Éclat de nébuleuse x2 → Bottes à réaction x1
+
+ 🔨 EngravingBench
+ engrave_royal_seal: Blason de chevalier x1 + Parchemin ancien x1 → Sceau royal x1
+
+ 🔨 Forge
+ craft_box_cool: Acier x2 + Néon x1 → box_cool x1
+ forge_armored_plate: Acier x3 + Fibre de carbone x2 → Armure x1
+ forge_carbonfiber_sheet: Fibre de carbone x4 → Fibre de carbone x1
+
+ 🔨 Foundry
+ refine_wood: Bois x2 → Bois x1
+
+ 🔨 Furnace
+ smelt_bronze_ingot: Bronze x3 → Bronze x1
+ smelt_iron_ingot: Fer x3 → Fer x1
+ smelt_steel_ingot: Acier x4 → Acier x1
+ smelt_titanium_ingot: Titane x5 → Titane x1
+
+ 🔨 GeneticModStation
+ splice_glowing_dna: Échantillon de bactérie sentiente x1 + Prion amical (probablement) x1 → Brin d'ADN luminescent x1
+
+ 🔨 MatterSynthesizer
+ fuse_cosmic_crystal: Éclat de nébuleuse x2 → Cristal de quasar x1
+
+ 🔨 Printer3D
+ craft_box_epic: Titane x2 + Fibre de carbone x1 + Or x1 → box_epic x1
+
+ 🔨 StasisChamber
+ preserve_amber: Dent de dinosaure x2 → Pierre d'ambre x1
+
+ 🔨 TransformationPentacle
+ enchant_dark_grimoire: Anneau maudit x2 → Grimoire du nécromancien x1
+
+ 🔨 Workbench
+ craft_box_ok_tier: Bois x2 + Bronze x1 → box_ok_tier x1
+ craft_pilot_glasses: Lunettes de soleil x1 + Bronze x1 + Argent x1 → Lunettes d'aviateur x1
+
## Orphan Items (no usage context)
────────────────────────────────────────────────────────────────────────────────
material_wood_nail (Material, Common)