Skip to content

Combat and Taming

Dominik Burger edited this page Jun 10, 2026 · 8 revisions

Combat and Taming [CURRENT]

Combat

Instanced & turn-based. Combat is AI-ONLY (FGT-T1): every turn is owned by the judge LLM on the server (OpenAI, the core feature) in both single-player and multiplayer, routed through one shared resolver (server/combat.js aiTurn) so SP and MP resolve identical inputs identically. Both judge prompts are editable in /admin (server/prompts.js: combatJudgeV2System for the default v2 judge, combatSystem for the v1 fallback).

Structured judge (v2 — now the DEFAULT): the flag combatJudgeV2 (ON by default; server/judge.js) gives the judge a richer contract: it receives the action + full monster descriptions incl. passives + the running fight transcript, and returns per-field EDITS — integers as deltas, strings (status) as rewrites — plus a short display line and a special-actions channel (end battle / insta-win / flee) the combat loop acts on. A v2-generated monster fights with its own AI-authored attacks (each a 2-3 word title + a description the judge reads to resolve the move). Set combatJudgeV2 off in /admin to fall back to the v1 absolute-value judge.

Single-player holds no API key in the browser, so it routes each turn through the same server judge over HTTP (POST /api/combat/turn); MP routes it over the WebSocket. Combat needs a connection to the judge. If it's unreachable (offline / no key), the game shows a clear "combat needs connection" message and lets you retreat — it does not silently fall back to an offline fight. The deterministic engine (src/engine/combat.js) is kept ONLY as a transient crash-net: when the judge IS configured but a single call hangs/errors (the 10s abort), that one turn resolves offline so the fight can't freeze — no longer a normal gameplay path. Catch is the deterministic spirit-chain mechanic (not an LLM call), resolved identically in SP and MP.

The deterministic rules below define the crash-net and the baseline the AI is expected to roughly follow.

How a single attack resolves (crash-net baseline):

flowchart TD
    A[Attacker's turn] --> B{Enough energy?}
    B -->|no| S[Skip turn]
    B -->|yes| C{Accuracy roll<br/>hit?}
    C -->|miss| M[No damage]
    C -->|hit| D[Damage = physical + elemental]
    D --> E{Crit roll?}
    E -->|yes| F[× critMultiplier]
    E -->|no| G[base damage]
    F --> I[Apply damage<br/>min 1]
    G --> I
    I --> J{Status roll?}
    J -->|yes| K[Apply status<br/>Burn/Poison/Freeze/Stun]
    J -->|no| L[End turn]
    K --> L
Loading

Turn order

Higher speed acts first; ties favor the player.

First-turn initiative (FGT-T9) overrides speed for turn 1 only, set by how the fight started:

  • Walk into a wild monster → the monster acts first.
  • Collide with another player (PvP) → a server-authoritative seeded coin-flip picks who acts first (neither client can influence it).
  • Land a spirit chain (on a monster or a player) → the thrower acts first.

After turn 1 the initiative is consumed and order reverts to speed. The chosen initiator is carried into the AI judge prompt too, so AI-resolved turns honor it.

Damage formula

physical = attacker.strength * (attack.damage / 100)
           - defender.defense * (1 - attack.penetration)
elemental = attacker.power * attack.elementalDiffusion
dmg      = max(1, floor(physical + elemental))
// crit (see below) may multiply: dmg = floor(dmg * attack.critMultiplier)
// elements are FLAVOUR ONLY — no type-effectiveness multiplier; min 1 per hit

Accuracy

hit if  rand(0..100) <= attack.accuracy * 100 + attacker.luck / 2

Critical hits

crit if rand(0..100) <= attack.critChance * 100 + attacker.luck / 4
on crit: damage *= attack.critMultiplier

Energy

Each attack costs energyCost. If a monster can't afford its action, it skips. Energy does not regenerate during a fight. Between encounters (decision Q8) every living team monster regains 50% of max energy (capped) at the start of each new fight, so a depleted team isn't stuck skipping forever. This applies in both SP and MP (server startCombat restores the same 50% as the SP scene — FGT-T5).

Player actions

On your turn you may Fight (pick an attack — only the active monster's own attacks are honored), Catch (PvE only — attempt a capture, see Taming), Swap (switch to another living team monster — a free action that doesn't cost the turn and keeps any first-turn initiative), use an Item (when you have any — instead of attacking; see Combat Items), Skip, or Flee. Swap and Items both work in both SP and MP (FGT-T4 / #61).

PvP duels [CURRENT]

Player-vs-player fights reuse the same AI turn resolver but differ from PvE in two ways:

  • Interactive turns: both players secretly pick a move; the turn resolves through the AI judge once both have submitted (you wait on the opponent in between).
  • Catch is disabled: you cannot capture another player's monster. Instead, defeating the opponent (KO'ing their last active monster) loots their entire active team into your vault (capped at your vault capacity); the loser refills from their vault or fresh starters and stays in the round. Fleeing is a no-contest (no loot). This is intentional (decision Q11 / FGT-T6).

Initiative on turn 1 follows the turn-order rules: a chain throw favors the thrower; a contact duel uses a server-authoritative seeded coin-flip (FGT-T9). After turn 1, speed order.

Combat Items [CURRENT]

Simple one-shot combat items — a name + a short action description — usable only during a fight, in place of an attack or fleeing. Like monsters, items are AI-generated (an inspiration agent → a designer agent; prompts + model are admin-editable). Their effect is not a fixed stat change: the fight judge reads the item's action description and resolves it exactly like an attack (so an item can damage, heal, cure a status, buff, etc. — whatever its text says), then the item is consumed.

Rule Detail
Source Loot chests roll one item (seeded, ~30% chance; none when the item pool is empty)
Storage A small per-profile item bag (capped at GAME.ITEM_BAG_SIZE); synced to the client on connect
Use In combat, tap Items → pick one (name + description). MP sends {kind:"item", itemId}; SP carries the item's {name, description} in the turn. PvE and PvP.
Resolution The v2 structured judge reads the description and resolves it like an attack (items carry no numeric fields, so they need the v2 judge); the item is removed from the bag after use.

Status Effects

Status Effect
Burn 5% of max HP damage per turn
Poison 3% of max HP per turn (stacks with burn in the spec)
Freeze 30% chance to skip the turn
Stun skip one turn, then clears

Only one status at a time; a new status replaces the old. Applied via attack.statusChance. Now enforced by the deterministic engine (synonyms normalized: Stunned→Stun, Frozen→Freeze, Poisoned→Poison). Status is per-fight: it's only set during a fight (by the AI judge), and is cleared when the fight ends — your team never carries an affliction back to the overworld.

Resolved (Q7 — taxonomy shelved): the attack data names 60+ distinct status labels (Bleed, Blind, Confusion, Fear, Paralysis…) plus buffs (Heal, Shielded, Reflect). Rather than hand-code a taxonomy, the AI resolver interprets statuses freely during live fights — but the judge is now instructed that every status MUST have a real effect (never cosmetic): damage-over-time (Burn/Poison/Bleed), turn-loss (Stun/Freeze/Sleep), or damage-down (Weaken), ticking until it wears off. The four mechanics above are the deterministic fallback's canonical set for offline/crash-net play.

Taming (Catch)

Catch with a spirit chain during combat. Base chance by enemy HP %, then scaled by the equipped chain's capture multiplier (and gated by its max rarity):

Enemy HP Base catch chance
< 25% 70%
25–50% 40%
50–75% 20%
> 75% 5%

Modifiers, in order: +15% if the enemy has a status effect → × the chain's capture multiplier (0.50× tier 1 … 1.60× tier 5) → clamped to 95% (a guaranteed chain bypasses the clamp at ≤25% HP) → auto-fail if the enemy's rarity exceeds the chain's max rarity. The enemy still attacks during the attempt, except on a first-turn player-initiated catch (a thrown-chain engage), which skips the retaliation. On success the monster joins the team (or the vault if the team is full of 4), a chain charge is consumed, and the catch is stabilized to a usable amount of HP/energy (50% of max, GAME.CATCH_HEAL_FRACTION) so a caught-at-death monster isn't dead weight for the rest of the run (CB-9).

Clone this wiki locally