18 KiB
Architecture
Principles
- Keep rules data-driven.
- Keep battle state independent from rendering.
- Build one complete vertical slice before expanding systems.
- Prefer simple Godot nodes and GDScript until a system proves it needs more structure.
Current Layout
project.godot: Godot 4 project configuration.scenes/battle_scene.tscn: Main playable battle scene.scripts/core/data_catalog.gd: JSON definition loader and deployment hydrator.scripts/core/campaign_state.gd: Thin campaign save state for gold, inventory, roster, completed scenarios, and campaign flags.scripts/core/battle_state.gd: Battle rules, unit state, turns, AI, and victory checks.scripts/scenes/battle_scene.gd: Rendering, input handling, and HUD wiring.audio/bgm/*.wavandaudio/sfx/*.wav: Placeholder music loops and interface/action stingers.art/portraits/*.png,art/units/*.png,art/units/enemies/*.png,art/items/*.png, andart/backgrounds/*.png: Generated visual assets used by dialogue, battle HUD, transparent board-token cutouts, generic-enemy transparent cutouts, transparent item icon cutouts, and map backdrops.data/campaign/campaign.json: Campaign chapter ranges, scenario order, and scenario paths.data/defs/*.json: Officers, classes, terrain, items, and skills.data/scenarios/*.json: Scenario definitions.
Data Files
data/defs/classes.json: Class stats, movement types, skill access.data/defs/officers.json: Named officer base stats, growth, and default portrait paths.data/defs/items.json: Weapons, armor, accessories, and consumables.data/defs/skills.json: MP tactic definitions for damage, healing, support, status effects, target rules, and range.data/defs/terrain.json: Terrain colors, defense, avoid, and movement costs.data/campaign/campaign.json: Chapter ranges, scenario sequence, titles, and resource paths.data/scenarios/*.json: Dialogue, deployments, rewards, and branching flags.
Battle Loop
- Load battle data.
- Hydrate deployments through
DataCatalog. - Dispatch battle-start scenario events.
- New campaigns first show a short title-to-briefing prologue using wide generated story artwork with a bottom caption panel, then continue into the first scenario briefing.
- Show scenario briefing with the campaign chapter header, battle header, objective summary, chapter overview, selectable camp Talk conversations, manual checkpoint menu, and optional pre-battle shop, Armory, Roster, and Formation setup.
- Player selects a unit.
- Briefing completion dispatches battle-begin events, including opening dialogue.
- Unit moves, attacks, chooses a tactic or consumable item from the side menu, changes equipment, counters, gains EXP, levels up, promotes at class thresholds, or waits. Clicking an enemy that can be reached by moving first performs the combined move-and-attack action directly, while the map marks those targets with compact move-attack badges plus the chosen advance origin and attack line before the click; the selected attack origin favors defeat chances and low counter risk, and hover badges plus the forecast panel show possible counter danger even when a miss could cause it. Tactic buttons show MP cost, kind, range, area radius, and power, support, debuff, movement modifier, accuracy/evasion modifier, damage-over-time, or action-lock effect. Physical attacks roll hit chance from unit agility and target terrain avoid, add facing-based side/rear damage bonuses, and equipped weapons can add small damage bonuses against matching target move types; misses give reduced EXP. The scene plays placeholder SFX, a short movement slide, class-specific physical attack motion profiles, low-HP warning rings, hover target preview badges, area tactic overlays, an enemy threat overlay with physical damage and hostile tactic hints, selected/hover unit role, movement, facing, terrain, status, and equipment details, and floating combat text for core action and UI feedback.
- Player ends turn.
- Enemy AI moves, picks physical attacks from damage, defeat, priority, and weapon-effectiveness scores, and can cast single-target or area damage, healing, support, debuff, movement-modifier, accuracy/evasion-modifier, damage-over-time, or action-lock tactics through the same combat and skill resolvers.
- Battle checks scenario-defined defeat conditions first, including commander loss and turn limits, then victory conditions such as enemy defeat or reaching a marked destination tile.
- Turn-start, movement-triggered, and unit-defeat scenario events run as phases change, units enter marked cells, or units fall, can wait for prerequisite events, and may update objectives with HUD notices, grant item or gold pickups, deploy reinforcements, withdraw units from the battlefield, or change AI target priorities.
- On first-time victory,
CampaignStatesnapshots player progression, battle inventory, and battle-only gold pickups, applies rewards once, advancescurrent_scenario_id, and writesuser://campaign_save.json. - If the scenario defines post-battle dialogue, it plays once before the victory result panel opens.
- The victory result panel splits rewards, roster changes, saved player growth, campaign status, and pending choices into sections.
- If the scenario defines post-battle choices, the result panel waits for the player to select one, previews choice rewards including officer join/leave effects, then saves the selected flags and optional choice rewards before enabling the next battle.
- The result panel can load the next scenario, passing saved roster and inventory overlays back into
BattleState. - Once all campaign scenarios are complete, the battle scene shows a campaign completion panel instead of loading another map.
Save Boundary
BattleState owns tactical runtime fields. CampaignState saves only campaign-facing data: completed scenario ids, gold, inventory counts, joined officers, campaign flags, and a player roster snapshot. It does not save map positions, pre-battle Roster/Formation choices, action flags, temporary status effects, or transient battle occupancy.
Presentation preferences are separate from campaign progression. BattleScene reads and writes user://heros_settings.json for BGM/SFX toggles, BGM/SFX volume percentages, and edge-scroll preference, exposing the same values through the title settings menu and the top in-game toolbar settings panel. These values apply to menu and battle audio playback without mutating CampaignState.
Joined officers gate whether requires_joined player deployments are loaded at all. Rewards and post-battle choices can add or remove joined officers without deleting their saved roster snapshot, so a later rejoin can preserve progression. When a save loads, CampaignState scans completed scenarios in campaign order and reapplies only officer join/leave transitions from rewards and the saved applied_post_battle_choices ledger, allowing added recruitment rewards to reach older saves without replaying gold or items. Old saves without the ledger can backfill a choice id only when exactly one completed scenario choice matches saved flags. Roster entries are keyed by officer_id when available, so future scenarios can deploy the same joined officer under different scenario-specific unit_id values. Saved progression overlays final battle stats for now; a later equipment refactor should split base stats from equipment bonuses before adding item leveling or stat recalculation. New battles currently start player units healed to their saved maximum HP/MP. Skill access is carried in roster snapshots, with class defaults still applied for old saves that do not include skills. Promoted class ids are saved in the roster, and the next battle rehydrates class-derived movement, growth, skills, and range from the saved class id before equipment range is recalculated.
Inventory and campaign flags are copied from CampaignState into BattleState when a scenario starts. The pre-battle Chapters overview reads CampaignState chapter progress and can load completed or current battles without mutating the save. Briefings preview first-clear rewards, can expose briefing.camp_conversations through a selectable Talk menu with briefing.camp_dialogue as a fallback, can filter those conversations by saved campaign flags, can claim saved one-time camp conversation supply effects through CampaignState and refresh the loaded battle inventory copy, and show completed replay rewards as already claimed. The pre-battle Save menu can write the current campaign state to user://campaign_manual_save.json, and loading that checkpoint restores it into user://campaign_save.json before re-entering the same pending-choice, completion, or current-briefing branch as startup. Pre-battle shop stock comes from the loaded scenario's shop.items list plus matching shop.conditional_items blocks, with optional finite limits from item entries or stock maps, and shop.merchant lines are presentation-only camp flavor. Shop purchases and 50% sell-back are campaign transactions: they update saved gold, unequipped inventory, and finite-stock purchase counts immediately, write the save file, and refresh the already-loaded battle inventory before the player begins the battle. Camp conversation supply claims are also campaign transactions: they save a per-scenario conversation claim ledger and inventory immediately, then refresh the already-loaded battle inventory. Pre-battle Armory equipment changes use the same equipment rules as battle HUD equipment changes, then immediately save merged roster equipment/stat snapshots and inventory stock. Pre-battle Roster uses scenario roster.max_units, roster.required_officers, and roster.required_units to mark optional player deployments as sortie or reserve; reserve units remain in the candidate list but are excluded from board occupancy, drawing, selection, AI targeting, and living-unit victory checks. Non-controllable player units can act as protected escort targets: enemies can attack them and conditions can reference them, but they are skipped by player selection, Armory, Formation, counterattacks, and campaign roster progression snapshots when persist_progression is false. Scenario ai_target_priority nudges enemy movement and damage-skill scoring toward important targets such as envoys without overriding defeat bonuses or range checks, and set_ai_target_priority events can retune that battle-only pressure after an ambush, gate closure, or escort exposure. Pre-battle Formation uses the loaded scenario's formation.cells and only mutates current battle unit positions before battle-begin events fire. Destination victory cells from unit_reaches_tile conditions are exposed to the scene for objective overlays and tile info, and can be expressed as one pos or a multi-cell cells list; gated victory conditions can also expose the unit_reaches_tile event that unlocks them as the next visible objective marker. Rewarding movement events that grant items or gold can expose optional battlefield side markers and Korean tile-info labels through when.label, without being treated as victory objectives. Movement-triggered unit_reaches_tile events receive the unit that just moved, so ambushes and pickup events fire on entry rather than from units already standing on a marker; unit_defeated events receive the fallen unit before final victory/defeat checks, allowing named-officer reactions and last-moment reinforcements. Briefing data can append matching briefing.conditional_lines, and scenario events can use when.campaign_flags to branch dialogue, objective changes, item and gold pickups, AI target-priority shifts, and reinforcements from saved campaign choices or when.after_event to wait for earlier scenario beats. Consumable use can restore HP or MP, cure matching poison/seal/snare/disarm statuses, or cleanse negative stat debuffs, and in-battle item use, event pickup grants, event gold pickups, plus equipment swaps mutate only the battle copy until victory; defeats and restarts do not spend saved items, keep picked-up items or gold, or preserve in-battle gear changes. Post-battle choices write campaign flags only after the result-panel button is pressed; if the player reloads after rewards are saved but before selecting, the pending scenario id restores the victory choice panel without replaying rewards. The next battle button stays locked until the campaign choice save succeeds. Equipment rewards share the same item_id -> count inventory stock, cover weapon, armor, and accessory slots, can be equipped from the side HUD before the selected unit moves or acts, and do not appear in the consumable action menu. Named equipment currently uses the same stock model with a presentation-only rarity tag rather than unique item instances. Already completed scenarios can be replayed without granting duplicate rewards, choices, replay inventory consumption, event gold pickups, save writes, current-scenario advancement, finite-stock purchases, or camp conversation supply claims; pre-battle Shop, Armory, Roster, Formation, and Save are disabled on completed-scenario replays to avoid side effects.
The battle scene owns presentation feedback: briefing and result states use menu BGM, briefing headers combine CampaignState chapter ranges, campaign order, and title data with BattleState briefing, objective text, condition-progress text, and defeat-risk text, active battles use battle BGM and show an objective/progress/risk panel fed by BattleState, objective-update signals show a short HUD notice and refresh the objective panel immediately, core panels prefer generated lacquer, scroll, jade, and command-seal StyleBoxTexture frames with flat ink/wood/jade styles as fallbacks, core buttons prefer generated lacquer, jade-hover, cinnabar-pressed, and disabled-ink StyleBoxTexture surfaces with flat styles as fallbacks, dialogue lines can show optional cached portrait textures or speaker-initial fallback panels in an expanded visual-novel-style panel, keep the portrait slot fixed even for narration lines, mark speaking side through text alignment, and expose progress plus previous-line controls while a sequence is open, the side HUD reuses cached officer portrait textures or class sprites for the selected unit or hovered unit, uses generated class crest icons for role badges, and formats focus text with role, movement type, facing, current terrain move cost, defense/avoid bonuses, core stats, status effects, guard-zone AI hints, and gear, board maps can draw a scenario background texture under translucent terrain overlays, generated terrain texture tiles, generated terrain feature overlays for connected roads, water, villages, castles, and wasteland accents, generated tactical tile markers for move, attack, selection, target, objective, and recovery overlays, connected edge blends, generated map badge icons for objective, recovery, supply, gold, tactic, and side-event markers, and move-attack target badges for enemies that can be struck after movement, board units render class sprite tokens or generic-enemy transparent class cutouts with generated class crest markers, team rings, facing markers, generic-enemy marks, officer pins, low-HP warning rings, HP bar color states, active support/debuff/poison/seal/snare/disarm status pips, and a scene-clocked idle stance whose intensity reacts to selected, acted, and low-HP unit state, item buttons can show optional cached item icons, hover previews are rendered as target badges from existing BattleState forecast APIs and include compact side/rear attack bonuses when facing matters, equipment option menus show stat/range/effect deltas, result and inventory summaries show named equipment with compact rarity tags, selected area tactics highlight their affected cells, the Threat toggle overlays enemy physical and hostile tactic reach while tile info names threat sources, estimates physical damage, and summarizes hostile tactic damage or status effects against occupied cells, log/result hooks trigger placeholder SFX, support, debuff, poison, seal, snare, disarm, objective updates, AI target-focus changes, and item/gold pickup effects use distinct feedback, BattleState.unit_motion_requested asks the scene to interpolate a unit's draw position during movement, BattleState.unit_action_motion_requested keeps its rules-facing signal shape while the scene maps attacker class ids to infantry, archer, cavalry, command, and heavy attack presentation profiles, and BattleState.combat_feedback_requested asks the scene to draw transient floating combat text without changing battle rules.
Large tactical maps are presented through a fixed visible map window rather than by shrinking the whole board, including the opening chapter-one battle. BattleScene keeps a clamped board_scroll_offset, scrolls the board while the mouse is near or slightly outside the visible map edge, brightens the scrollable frame edge under the cursor, ignores board clicks outside the visible map window, and draws a small minimap plate only when the board actually exceeds the visible map view; the minimap shows terrain, unit dots, objective seals, and side-event markers, and clicking or dragging it recenters the tactical viewport, while compact maps keep the lower-right tiles unobstructed. Board-attached command panels are repositioned after map scrolling so post-move choices stay beside the moved unit. Battle start focuses the board on the deployed player group, pre-battle Formation can focus the board on the selected unit even before the battle opens, and action feedback can focus the board on off-screen cells before motion or combat effects play.
The New Campaign button clears user://campaign_save.json, resets campaign state to the first scenario, and reloads the opening briefing.