# Open The Box ## Project Overview A console-based incremental/idle game built in C# (.NET 9) where players open boxes to discover items, unlock features, and progress through themed adventures. Uses Spectre.Console for rich terminal rendering and Loreline for interactive narrative scripting. ## Architecture See [specifications.md](specifications.md) for detailed content organization. ## Key Directories - `src/OpenTheBox/` — Main game source code - `content/data/` — JSON data files (items, boxes, crafting recipes) - `content/adventures/` — Loreline `.lor` adventure scripts (one folder per theme) - `content/localization/` — Translation files ## Source Code Structure - `Core/` — Game state, enums, item/character models - `Core/Enums/` — All enum types (StatType, ResourceType, AdventureTheme, CosmeticSlot, etc.) - `Adventures/` — AdventureEngine (Loreline bridge + custom functions) - `Simulation/` — Game engines (BoxEngine, MetaEngine, ResourceEngine, CraftingEngine, GameSimulation) - `Rendering/` — IRenderer interface, SpectreRenderer, RenderContext, panel components - `Rendering/Panels/` — Individual UI panels (PortraitPanel, ResourcePanel, StatsPanel, etc.) - `Data/` — ContentRegistry, ItemDefinition, BoxDefinition data loading - `Persistence/` — SaveManager, SaveData (JSON serialization) - `Localization/` — LocalizationManager, Locale enum ## Build & Run ``` dotnet build dotnet run --project src/OpenTheBox # Classic Spectre.Console mode dotnet run --project src/OpenTheBox -- --tui # Terminal.Gui panel layout dotnet run --project src/OpenTheBox -- --snapshot 5 # Load snapshot save #5 ``` ## Test ``` dotnet test ``` ## Adventure System Adventures use Loreline `.lor` script format. Custom functions available in scripts: - Inventory: `grantItem(id, qty)`, `hasItem(id)`, `removeItem(id)` - Resources: `hasResource(name, min)`, `getResourceValue(name)`, `addResource(name, amount)` - Stats: `hasStat(name, min)`, `getStatValue(name)` - Cosmetics: `hasCosmetic(id)`, `hasEquipped(slot, style)` - Progression: `hasCompletedAdventure(theme)`, `markSecretBranch(id)`, `hasSecretBranch(id)`, `countSecretBranches()`, `allSecretBranches()` Hints for disabled choices use `|||` separator: `Option text|||Hint text #label if condition` full documentation: https://loreline.app/fr/docs/ ## Pacing Test To check game progression balance after modifying loot tables (`content/data/boxes.json`): ``` dotnet test --filter "FullRun_PacingReport" --logger "console;verbosity=detailed" ``` This runs a full simulation (3 seeds: 42, 123, 777) and prints a report showing when each milestone is first reached (UI features, adventures, cosmetics, resources, crafting, lore). Use this to verify that rebalancing changes produce the desired early-game pacing. Key things to look for: - **1st Meta UI unlock** should happen before box ~50 for a good early experience - **1st Adventure** should appear before box ~120 - **All content reachable** within ~1000 boxes (game completion) - No long gaps between milestones (>100 boxes without progress feels stale) Weights are in `content/data/boxes.json`. The main generator is `box_of_boxes` (auto-opens, produces the next box). Adjust weights there and in tier boxes (`box_not_great`, `box_ok_tier`, etc.) to tune pacing. ## Save Snapshots (Visual Testing) Generate save files at 9 progression stages for quick visual testing: ``` dotnet test --filter "GenerateSaveSnapshots" --logger "console;verbosity=detailed" ``` This creates `saves/snapshot_1.otb` through `saves/snapshot_9.otb` (from 10 boxes to 2000 boxes opened). Load a snapshot directly: ``` dotnet run --project src/OpenTheBox -- --snapshot 3 ``` Where 1-9 corresponds to: (1) very early, (2) first unlocks, (3) several panels, (4) adventures, (5) crafting, (6) most features, (7) near completion, (8) endgame, (9) post-endgame. ## Terminal.Gui Mode Run with `--tui` for a tmux-like panel layout using Terminal.Gui: ``` dotnet run --project src/OpenTheBox -- --tui ``` ## Conventions - C# 12 with file-scoped namespaces, primary constructors where appropriate - Immutable records for value types, sealed classes for services - GameState is mutable, passed by reference to engines - All user-facing strings go through LocalizationManager - Enum names match JSON string values (PascalCase)