Battle Field Architecture - xopherdeep/do-it-for-the-xp GitHub Wiki
Battle Field Architecture (v2, 2026-10-09)
How a chore becomes a fight, how the fight feels, how it ends, and where the code lives. Gameplay background (beasts, HP, temples) is in Battle-System-Engine; the design contract this build followed is Battle-Field-Contract-v2.
The loop
- A beast is a chore. Its checklist items are the hero's attacks.
- Attack slides the checklist up; confirming a chore ("Did you finish it?" → Done!) deals
ceil(maxHealth / chores)damage, pops a "-50" over the beast, empties a chore pip and refills the clock. - The attack clock runs:
attackCooldownminutes (default 3) per remaining chore. When it empties the beast strikes for about 12% of the hero's max HP (bosses bite harder). The hit lands when the hero taps through "{Beast} attacks!": the screen shakes, flashes red, the phone buzzes and the HP odometer rolls down while the damage line types. - Defend takes a glancing hit now (half a strike, one second later) and buys a fresh full clock. Run Away asks first ("Your finished chores won't count"), then always gets away: one parting strike on the way out (softened for a Thief or Ninja or a bracing hero, dodged by Chronos' ice, Thor's Hammer, a Ninja's owed miss or trained agility), nothing at all in a free fight.
- Last chore = beast defeated. Rewards land immediately (or are held as a pending Quest Log entry when the Game Master requires approval), the outcome is recorded, HP is restored to full, and only then the celebration plays: strobe, arena collapse, fanfare, the victory card counting up XP / GP / AP ("2 chores in 4:12", LEVEL UP ribbon), level-up lines, walk home.
- Free fights (random encounters, the room editor's Test Fight) pay 1 AP per beast and no XP/GP.
- A win keeps the HP the beast left; a defeat admits the hero to the Hospital (full HP and MP, a flat bill, the hero grounded while it is owed). See Battle-Field-Contract-v2.md.
- Chores finished earlier today are remembered per hero and fight (
battleProgress.ts); reopening the fight restores them ("{Beast} is still hurt from earlier!"). - Every exit (victory, escape, game over, hardware back → Run Away confirm) returns to where the fight started.
Routes
| Kind | Route | Comes from | Returns to |
|---|---|---|---|
| quest | /battle/:userId/quest/:questId |
Quest Log → "Fight!" on a beast quest | Quest Log, quest Tamed (or Pending) |
| temple | /battle/:userId/temple/:templeId/:level/:x/:y |
Temple room → "Fight" | The same temple room, now cleared |
| free | /battle/:userId/free/:beastIds? |
Room editor "Test Fight", random encounters | Home Town |
| free | /battle (optional ?beastIds=a,b) |
Hands-on testing with whoever is signed in | Home Town |
Build links only through questBattleRoute, templeBattleRoute, freeBattleRoute in battle/battleContext.ts. x is the column, y the row.
Screen (portrait, three zones + overlays)
- Top —
BattleDialogBox: the Earthbound message box; the command menu lives inside it (Attack full-width, Defend | Run Away), with a prompt line above it when useful ("Do its chores to win!", "Run away?"). One tap finishes a line, the next moves on; result lines move on by themselves. The zone has a fixed height so the arena never jumps. - Middle —
BattleEnemyStageover the animatedearthbound-battle-backgroundscanvas: name plate, sprite (entrance, idle float, hit shake, strobe), chore pips with "{n} chores left", the clock ("strikes in 2:40", red pulse under 10 s), floating damage numbers ("SMAAAASH!!" on the last blow). - Bottom —
BattleHeroCard: avatar, nickname, level, the rolling HP odometer with a bar, a small MP line; shakes when hit, dims when downed. - Overlays (siblings of the shaking container): the checklist bottom sheet, the red hit-flash vignette, the centered victory card.
Code map (src/app/Console/BattleField/)
BattleField.vue / .ts / _BattleField.scss thin page: wires the pieces below; layout + message-box styles + transitions
battle/
battleView.ts shared types: EnemyAnimation, HitPopup, VictoryCardState, DialogLine...
battleContext.ts BattleContext from the route; return-route + link helpers
encounterLoader.ts quest / temple room / beast ids -> Encounter {enemies, rewards, quest, background}
battleRewards.ts GM-authored rewards win; temple beasts use the health formula; free fights pay AP only
battleProgress.ts today's finished chores per hero + fight (localStorage)
battleMenu.ts BattleMenuAction type
composables/
useBattleDialog.ts typing queue, tap-to-advance, auto-advancing result lines, append mode (level ups)
useBattleOutcome.ts victory (pay or hold, record, restore HP, VICTORY_TIMINGS, card), defeat, escape
useBattleFeedback.ts screen shake, red flash, haptics, hero-hit signal, reduced-motion aware
useBattleBackground.ts earthbound-battle-backgrounds canvas; room overrides beat beast layers
useBattleAudio.ts sound effects (valid keys only), fanfare, BGM pause
useAtbDisplay.ts chores, clock percent / label / critical, damage per chore, sprite size
components/
BattleDialogBox.vue message box with the command menu and prompt inside it
BattleEnemyStage.vue name plate, sprite, pips, clock, damage popups
XpBattleTaskMenu.vue the checklist bottom sheet ("Attack")
BattleHeroCard.vue the hero (avatar, name, level, rolling HP / MP)
BattleVictoryCard.vue the loot, counted up
hooks/useBattlePlayer.ts the hero card's view model (bound to the route's hero, reactive, no DB reads)
__tests__/ one spec per component + BattleField.spec.ts (a full fight in jsdom)
Engine: src/lib/services/battle/BattleService.ts (per-battle reactive state, deadline-based Task-ATB clock, single-strike guarantee, brace trade, strikeDamage, restoreCompletedTasks, dispose), unit-tested in tests/unit/BattleService.spec.ts. Beasts: BestiaryService converts a Bestiary beast into an Enemy.
Outcomes written elsewhere: quest victories call recordBattleCompletion (or completeQuest(..., "battle") when approval is required, which logs a pending entry); temple victories call TempleSystem.markRoomCleared.
Verification
pnpm test: component specs, the engine spec and the page spec (the whole fight in jsdom, including a strike and a defend).pnpm exec cypress run --browser chrome --headless --config baseUrl=http://127.0.0.1:8080 --spec tests/e2e/specs/battle.spec.js(dev server on :8080): the real page in Chrome at 390×844 with a screenshot of every phase undertests/e2e/screenshots/. Seeding goes through the development-onlywindow.__xpseam (src/main.ts) andtests/e2e/fixtures/battle-backup.json.
Deliberate omissions
- Goods / Abilities stay off the battle menu until items have in-battle effects (Equipment-Mechanics).
- Battle music tracks are remote (starmen.net) and silent offline; the fanfare and every effect are local.
- The hospital's paid heal and the free bench are real (
Lodging.ts); a hero at 0 HP is admitted automatically and billed (hospitalAdmission.ts).