Skip to content
Open
30 changes: 15 additions & 15 deletions snapshots/EngineGasTest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"B1_Execute": "973988",
"B1_Setup": "812723",
"B2_Execute": "754167",
"B2_Setup": "278155",
"Battle1_Execute": "494124",
"Battle1_Setup": "789140",
"Battle2_Execute": "408814",
"Battle2_Setup": "234804",
"FirstBattle": "3495916",
"Intermediary stuff": "44162",
"SecondBattle": "3589364",
"Setup 1": "1668829",
"Setup 2": "295644",
"Setup 3": "335644",
"ThirdBattle": "2906543"
"B1_Execute": "1003703",
"B1_Setup": "817752",
"B2_Execute": "778056",
"B2_Setup": "283130",
"Battle1_Execute": "505822",
"Battle1_Setup": "794141",
"Battle2_Execute": "420512",
"Battle2_Setup": "237842",
"FirstBattle": "3583299",
"Intermediary stuff": "43924",
"SecondBattle": "3692660",
"Setup 1": "1674199",
"Setup 2": "299218",
"Setup 3": "338942",
"ThirdBattle": "2994000"
}
6 changes: 3 additions & 3 deletions snapshots/MatchmakerTest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Accept1": "305380",
"Accept2": "33991",
"Propose1": "197148"
"Accept1": "307847",
"Accept2": "34356",
"Propose1": "199515"
}
25 changes: 24 additions & 1 deletion src/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,27 @@ uint256 constant EFFECT_COUNT_MASK = 0x3F; // 6 bits = max count of 63

address constant TOMBSTONE_ADDRESS = address(0xdead);

uint256 constant MAX_BATTLE_DURATION = 1 hours;
uint256 constant MAX_BATTLE_DURATION = 1 hours;

// Active mon index packing (uint16):
// Singles: lower 8 bits = p0 active, upper 8 bits = p1 active (backwards compatible)
// Doubles: 4 bits per slot (supports up to 16 mons per team)
// Bits 0-3: p0 slot 0 active mon index
// Bits 4-7: p0 slot 1 active mon index
// Bits 8-11: p1 slot 0 active mon index
// Bits 12-15: p1 slot 1 active mon index
uint8 constant ACTIVE_MON_INDEX_BITS = 4;
uint8 constant ACTIVE_MON_INDEX_MASK = 0x0F; // 4 bits

// Slot switch flags + game mode packing (uint8):
// Bit 0: p0 slot 0 needs switch
// Bit 1: p0 slot 1 needs switch
// Bit 2: p1 slot 0 needs switch
// Bit 3: p1 slot 1 needs switch
// Bit 4: game mode (0 = singles, 1 = doubles)
uint8 constant SWITCH_FLAG_P0_SLOT0 = 0x01;
uint8 constant SWITCH_FLAG_P0_SLOT1 = 0x02;
uint8 constant SWITCH_FLAG_P1_SLOT0 = 0x04;
uint8 constant SWITCH_FLAG_P1_SLOT1 = 0x08;
uint8 constant SWITCH_FLAGS_MASK = 0x0F;
uint8 constant GAME_MODE_BIT = 0x10; // Bit 4: 0 = singles, 1 = doubles
119 changes: 119 additions & 0 deletions src/DefaultValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,125 @@ contract DefaultValidator is IValidator {
return true;
}

/**
* @notice Validates a move for a specific slot in doubles mode
* @dev Enforces:
* - If slot's mon is KO'd, must switch (unless no valid targets → NO_OP allowed)
* - Switch target can't be KO'd or already active in another slot
* - Standard move validation for non-switch moves
*/
function validatePlayerMoveForSlot(
bytes32 battleKey,
uint256 moveIndex,
uint256 playerIndex,
uint256 slotIndex,
uint240 extraData
) external view returns (bool) {
// Get the active mon index for this slot
uint256 activeMonIndex = ENGINE.getActiveMonIndexForSlot(battleKey, playerIndex, slotIndex);
uint256 otherSlotActiveMonIndex = ENGINE.getActiveMonIndexForSlot(battleKey, playerIndex, 1 - slotIndex);

BattleContext memory ctx = ENGINE.getBattleContext(battleKey);

// Check if this slot's mon is KO'd
bool isActiveMonKnockedOut = ENGINE.getMonStateForBattle(
battleKey, playerIndex, activeMonIndex, MonStateIndexName.IsKnockedOut
) == 1;

// Turn 0: must switch to set initial mon
// KO'd mon: must switch (unless no valid targets)
if (ctx.turnId == 0 || isActiveMonKnockedOut) {
if (moveIndex != SWITCH_MOVE_INDEX) {
// Check if NO_OP is allowed (no valid switch targets)
if (moveIndex == NO_OP_MOVE_INDEX && !_hasValidSwitchTargetForSlot(battleKey, playerIndex, otherSlotActiveMonIndex)) {
return true;
}
return false;
}
}

// Validate move index range
if (moveIndex != NO_OP_MOVE_INDEX && moveIndex != SWITCH_MOVE_INDEX) {
if (moveIndex >= MOVES_PER_MON) {
return false;
}
}
// NO_OP is always valid (if we got past the KO check)
else if (moveIndex == NO_OP_MOVE_INDEX) {
return true;
}
// Switch validation
else if (moveIndex == SWITCH_MOVE_INDEX) {
uint256 monToSwitchIndex = uint256(extraData);
return _validateSwitchForSlot(battleKey, playerIndex, monToSwitchIndex, activeMonIndex, otherSlotActiveMonIndex, ctx);
}

// Validate specific move selection
return _validateSpecificMoveSelectionInternal(battleKey, moveIndex, playerIndex, extraData, activeMonIndex);
}

/**
* @dev Checks if there's any valid switch target for a slot (excluding other slot's active mon)
*/
function _hasValidSwitchTargetForSlot(
bytes32 battleKey,
uint256 playerIndex,
uint256 otherSlotActiveMonIndex
) internal view returns (bool) {
for (uint256 i = 0; i < MONS_PER_TEAM; i++) {
// Skip if it's the other slot's active mon
if (i == otherSlotActiveMonIndex) {
continue;
}
// Check if mon is not KO'd
bool isKnockedOut = ENGINE.getMonStateForBattle(
battleKey, playerIndex, i, MonStateIndexName.IsKnockedOut
) == 1;
if (!isKnockedOut) {
return true;
}
}
return false;
}

/**
* @dev Validates switch for a specific slot in doubles (can't switch to other slot's active mon)
*/
function _validateSwitchForSlot(
bytes32 battleKey,
uint256 playerIndex,
uint256 monToSwitchIndex,
uint256 currentSlotActiveMonIndex,
uint256 otherSlotActiveMonIndex,
BattleContext memory ctx
) internal view returns (bool) {
if (monToSwitchIndex >= MONS_PER_TEAM) {
return false;
}

// Can't switch to a KO'd mon
bool isNewMonKnockedOut = ENGINE.getMonStateForBattle(
battleKey, playerIndex, monToSwitchIndex, MonStateIndexName.IsKnockedOut
) == 1;
if (isNewMonKnockedOut) {
return false;
}

// Can't switch to mon already active in the other slot
if (monToSwitchIndex == otherSlotActiveMonIndex) {
return false;
}

// Can't switch to same mon (except turn 0)
if (ctx.turnId != 0) {
if (monToSwitchIndex == currentSlotActiveMonIndex) {
return false;
}
}

return true;
}

/*
Check switch for turn flag:

Expand Down
Loading