Skip to main content

Gamigin

The Eternal Sinner

Unit #031

HPPWRCPSpeedRangeTier
66 (+4)2 (+1)SlowLocalC

Abilities

Passive

After Gamigin dies, your opponent must Fuse and Exhaust Gamigin's demon card onto a Demon they control if they control any Fusible Demons. (Fused Demons have both names.)
Engine Implementation
def _gamigin_forced_fusion_trigger(
state: GameState, event: GameEvent, demon: DemonInstance, depth: int
) -> GameState | None:
"""#031 Gamigin — Passive trigger: After Gamigin dies, force-fuse onto opponent's demon.

Fires on FATALLY_WOUNDED when Gamigin IS the target (dying demon).
Gamigin itself IS fatally_wounded — this is intentional. The trigger's
effect is applied to the OPPONENT's board, not Gamigin.

Effect: Pick the first fusible enemy demon. Fuse Gamigin's card as the
bottom card and exhaust the resulting demon.

"Fusible Demons" = non-fused (or Dantalion) non-familiar regular unit demons.
Auto-selects first eligible target (deterministic; a full UI implementation
would let the opponent choose which demon to receive Gamigin).

CRITICAL: Gamigin's card data fuses onto top demon → top.fused_bottom = "031",
top.current_hp += Gamigin's fhp (0), top.is_fused = True, top.state = EXHAUSTED.
The fused demon retains the top card's speed, range, and lane.
"""
from engine.data_loader import UNITS
from engine.constants import DemonState

# Only fire if Gamigin (this demon) is the dying target
if event.target is None:
return None
if event.target.instance_id != demon.instance_id:
return None # A different demon is dying — not Gamigin's trigger

# Gamigin IS fatally_wounded here — this is expected and allowed for this trigger
# (the effect applies to the opponent, not Gamigin performing anything)

# Find the opponent's side
opponent = Side.PLAYER_2 if demon.owner == Side.PLAYER_1 else Side.PLAYER_1

# Find the first fusible opponent demon (non-fused OR Dantalion)
# "Fusible" = can accept a bottom card = not already fused (except Dantalion),
# not a familiar (cannot_fuse_familiars = true)
DANTALION_ID = "106"
fusible_target = None
for d in state.demons:
if d.owner != opponent:
continue # must be opponent's demon
if d.is_familiar:
continue # cannot fuse familiars
if d.is_fused and d.unit_id != DANTALION_ID:
continue # already fused (exception: Dantalion)
# This demon can receive Gamigin as bottom card
fusible_target = d
break

if fusible_target is None:
return None # No fusible demons — trigger does nothing

# Apply the forced fusion: Gamigin becomes bottom card of fusible_target
gamigin_data = UNITS["031"]
new_state = copy.deepcopy(state)

# Find the top demon in the new state
top_in_state = next(
(d for d in new_state.demons if d.instance_id == fusible_target.instance_id),
None,
)
if top_in_state is None:
return None

top_unit = UNITS[top_in_state.unit_id]
# Apply fusion bonus: top.current_hp = top base hp + Gamigin fhp (0)
top_in_state.current_hp = top_unit.hp + (gamigin_data.fhp or 0)
top_in_state.fused_bottom = "031"
top_in_state.is_fused = True
# "Fuse and Exhaust" — the resulting demon is EXHAUSTED
top_in_state.state = DemonState.EXHAUSTED
# Existing damage preserved (lane preserved — no change needed)

# Gamigin is NOT removed here — death resolution happens after triggers
# The fusible_target now embeds Gamigin as bottom card

return new_state

register_trigger("031", 0, _gamigin_forced_fusion_trigger)
Gamigin