Combat¶
Source: PROJECT_CONTEXT.md §5, §8.3;
config.jsplayer,combat,melee,weapons,move,enemyScale;game.jscomputeStats/setBonuses/doMelee/updatePlayerAttacks/perceive/setLean. Status: ✅ Implemented.
What it is¶
The fight model. The player has no attack button: a stance arbiter auto-picks melee or ranged based on the nearest threat's range. Mobs are armored from the front and must be circled to a rear weak-point; a backstab bypasses that armor entirely, so stealth beats brute force. Fights are tuned to be prolonged-but-survivable so progression is about kill speed, not surviving.
How it works¶
- Stats & Power. Base
{atk, hp, crit, speed}fromconfig.jsplayer+ gear. (PROJECT_CONTEXT §8.3 frames the design baseline as{atk 30, hp 280, crit 50, speed 100}; live config currently runsbaseHp 190/baseSpeed 60after a lethality pass.)shieldMax = hp × player.shieldFrac— a regenerating overshield buffer (§8.3 frames it as hp×0.5; liveshieldFrac0.18) that refills aftershieldRegenDelay(6s) of no damage atshieldRegen/s up toshieldRegenCap.power = atk×powAtk + hp×powHp + crit×powCrit + speed×powSpeed(weights6 / 0.9 / 4 / 2→ the §5 formulaatk×6 + hp×0.9 + crit×4 + speed×2), computed incomputeStats. - Stamina. A
move.stamMax(100) pool drives sprint (sprintMult×1.9, drainssprintDrain32/s) and the dodge-roll (dodgeCost30,dodgeTime0.25s,dodgeSpeed500px/s peak), which grants i-frames (stealth.invulnWindow0.34s — un-spottable and invulnerable). Regens atstamRegen/s afterstamRegenDelay. - Attack-stance arbiter (one stance at a time).
updatePlayerAttackspicks a single stance by the nearest threat's distance: melee when it's within strike reach +combat.meleeBand(ranged is suppressed so you never auto-shoot point-blank, auto-swinging onplayer.meleeCd); ranged otherwise. Hysteresis (combat.bandHysteresis) + astanceCommittimer keep the handoff from flickering. The master toggle iscombat.autoMelee(true); set false to restore the old all-range ranged + tap/backstab-only melee. - Front armor → rear weak-point. Mobs are armored from the front: shots inside the
combat.armorFrontcone ping off (armorFrontMult×0.25), flanks takearmorFlankMult(×0.65), and the glowing rear weak-point (armorRear) takesarmorRearMult(×1.6). Mobs juke side-to-side, so you must circle. - Backstab. A strike landing in the rear/side arc (
combat.backstabArc, ×PI from facing) is a crit (critBase×1.5) that bypasses armor and ignores the PvP Power check — stealth beats brute force (doMelee). The hands-off auto-backstab uses a stricter rear-only arc (autoBackstabArc, 0.72) than a committed tapped strike, so brushing a side won't auto-kill. A backstab kill triggersbackstabHitstop(0.13s freeze-frame). - Silent-opener grace. After a silent backstab, the player's auto-fire is muted for
combat.meleeToFireLock(0.5s) so a non-lethal opener isn't instantly blown by a loud auto-shot — a beat to re-stab or slip to cover. A loud frontal melee doesn't trigger it. - Ranged enemies: rare-but-hard + dodgeable telegraph. Ranged mobs fire on a slow
fireRatewith achargeDurwind-up that shows a red charge telegraph; the dodge-roll evades it. Melee mobs (tier ≥melee.lungeMinTier) gap-close with a telegraphed lunge (melee.windupthen a locked dash) that you side-step off the line. - Enemy cadence.
enemyScale.atkCdMul(1.5) multiplies every enemy attack cooldown — mob melee + lunge, mob ranged, rival fire — so enemies attack less often without losing per-hit damage (the prolonged-but-survivable dial). The stance arbiter is player-only; rivals keep their own kiting/burst AI but obey this cadence. - Sets (3 matching pieces to activate,
SET_THRESHOLD).setBonuses: Hunter +30% dmg vs mobs (mobDmg ×1.30); Stalker +45% backstab (backstab ×1.45) & −30% detectability (stealth ×0.7); Bulwark +22% max HP (hpMult ×1.22). - Attack body-language (render-only).
setLean/attackLeanOffsetlean the body token toward a melee target (ui.attackLean) and kick back from a ranged shot (ui.shootRecoil) forui.leanTime, while feet/shadow stay planted — a stand-in for sprite anim, applied to player and enemies so a fight reads as back-and-forth.
Tunables¶
player
| Key | Default | Meaning |
|---|---|---|
| player.baseAtk / baseHp / baseCrit / baseSpeed | 30 / 190 / 50 / 60 | base stats before gear |
| player.shieldFrac | 0.18 | overshield = maxHP × this |
| player.meleeCd | 0.9 | melee swing cooldown (s); backstab opener exempt |
| player.powAtk / powHp / powCrit / powSpeed | 6 / 0.9 / 4 / 2 | Power-score weights |
combat
| Key | Default | Meaning |
|---|---|---|
| combat.autoMelee | true | master toggle for auto-melee + stance handoff |
| combat.meleeBand / bandHysteresis / stanceCommit | 5 / 5 / 0.15 | stance switch lead / anti-flicker / min hold (s) |
| combat.backstabArc / autoBackstabArc | 0.55 / 0.72 | landed-crit arc / stricter passive-trigger arc (×PI) |
| combat.critBase | 1.5 | backstab crit × |
| combat.meleeToFireLock | 0.5 | silent-opener auto-fire mute (s) |
| combat.backstabHitstop | 0.13 | freeze-frame on a backstab kill (s) |
| combat.armorFront / armorRear | 0.95 / 2.1 | front armor cone / rear weak-point angle (rad) |
| combat.armorFrontMult / armorFlankMult / armorRearMult | 0.25 / 0.65 / 1.6 | hit damage × by angle |
| combat.mobHpMult / mobDmgMult | 2 / 0.5 | spongier mobs / softer hits (prolonged-but-survivable) |
| combat.mobMeleeCd | 0.7 | enemy melee cooldown (s) |
| combat.powCheckExp / powCheckMin / powCheckMax | 0.35 / 0.55 / 1.7 | PvP power-ratio damage exponent + clamps |
move (stamina)
| Key | Default | Meaning |
|---|---|---|
| move.stamMax / stamRegen / stamRegenDelay | 100 / 24 / 0.6 | stamina pool / regen rate / pause (s) |
| move.sprintMult / sprintDrain | 1.9 / 32 | sprint × / drain (stam/s) |
| move.dodgeCost / dodgeTime / dodgeSpeed / dodgeCd | 30 / 0.25 / 500 / 0.55 | roll stamina / duration / peak speed / cooldown |
melee / weapons / enemyScale
| Key | Default | Meaning |
|---|---|---|
| melee.lungeMinTier | 2 | only tier ≥ this gap-close with a lunge |
| melee.windup / lungeSpeed / dmgMult | 0.65 / 480 / 2.0 | lunge telegraph (s) / dash speed / damage × |
| weapons.<archetype>.chargeDur (enemies, per-mob) | e.g. 0.4–0.7 | ranged wind-up telegraph (s) |
| enemyScale.atkCdMul | 1.5 | × on every enemy attack cooldown |
Design intent¶
The stance arbiter + no-attack-button keep input minimal (Hunter Assassin's tap-to-move feel) so the skill is positioning, not aiming (pillar #2). Front-armor + rear weak-point makes a head-on fight a circling puzzle and risky; the backstab that bypasses both armor and the Power check is the mechanical embodiment of pillar #1 (hide-and-kill — stealth beats brute force). Prolonged-but-survivable knobs (mobHpMult up, mobDmgMult down, slow enemy cadence) make progression about kill speed while keeping the lengthened, deliberate melee cadence readable. Sets let players craft a style (aggressive Hunter, stealthy Stalker, tanky Bulwark).
Open questions / deltas¶
- "Crit Damage" vs
crit(§9.2): the concept lists Crit Damage as a core stat; code tracks a singlecritvalue — naming/semantics to reconcile for the Unity port. - Prolonged-but-survivable defaults: PROJECT_CONTEXT §8.7 notes
mobHpMult/mobDmgMultare pending a playtest pass; live config runs 2 / 0.5 (the Diablo direction) while §8.7 documents a1/1arcade fallback. - Stat-baseline drift: §8.3 cites
{hp 280, shieldMax = hp×0.5}; live config runsbaseHp 190/shieldFrac 0.18after a lethality pass — treatconfig.jsas canonical. - RTS ranged mode parked:
weapons.rtsMode(false) gates a no-magazine/no-reload auto-attack; the default is the arcade mag+reload model. - Body-language is render-only:
setLeanis a stand-in for sprite animation — the Unity port replaces it with real attack anims.