Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
aa283ef
feat: add core data structures for double battles support
claude Jan 8, 2026
7863152
fix: add p0Move2, p1Move2 to BattleConfigView construction
claude Jan 8, 2026
6917c8d
feat: add doubles-specific getters to IEngine and Engine
claude Jan 8, 2026
072ca7e
feat: add DoublesCommitManager for 2-move commit/reveal
claude Jan 8, 2026
8958cb4
feat: add doubles execution logic in Engine
claude Jan 8, 2026
c144359
feat: add doubles boundary condition tests and fix getters
claude Jan 8, 2026
bbb2959
refactor: extract shared functions for singles/doubles code reuse
claude Jan 9, 2026
adcb602
feat: add slot-aware validation for doubles battles
claude Jan 9, 2026
8a7433c
test: add comprehensive doubles validation boundary tests
claude Jan 9, 2026
46d0960
feat: implement single-player switch turns for doubles battles
claude Jan 9, 2026
cc4cf45
test: add comprehensive doubles switch turn combo tests
claude Jan 10, 2026
1a8024c
Merge double battles implementation from claude/add-double-battles-3PJU2
claude Jan 10, 2026
0bb16ec
fix: prevent both slots from switching to same mon in doubles
claude Jan 10, 2026
de7a640
feat: add doubles-aware validation for forced switch moves
claude Jan 10, 2026
49dff01
test: add doubles/singles battle transition storage reuse tests
claude Jan 11, 2026
0fad179
feat: add switchActiveMonForSlot for doubles force-switch moves
claude Jan 11, 2026
9db7518
test: add switchActiveMonForSlot tests for doubles force-switch
claude Jan 11, 2026
ad65b7b
fix: handle cross-slot switch claiming in doubles validation
claude Jan 11, 2026
1d1c365
Merge branch 'main' into claude/double-battle-switch-combos-jvJGO
sudo-owen Jan 11, 2026
00ab05f
docs: add CHANGELOG documenting double battles implementation
claude Jan 11, 2026
156ef88
docs: remove incorrect timeout concern from CHANGELOG
claude Jan 11, 2026
0ce9f7d
docs: clarify mixed switch+attack turns status in CHANGELOG
claude Jan 11, 2026
625fdef
test: add mixed switch+attack during single-player switch turn test
claude Jan 11, 2026
b34b5ee
fix: return MOVE_MISS_EVENT_TYPE when attack misses
claude Jan 11, 2026
29741bc
chore: update gas snapshot
claude Jan 11, 2026
36f442a
refactor: extract shared CommitManager logic and unify validator func…
claude Jan 11, 2026
989d1fa
chore: add .gas-snapshot to gitignore
claude Jan 11, 2026
b25b1b3
fix: make _runEffects slot-aware for doubles battles
claude Jan 15, 2026
a59b308
refactor: unify active mon index packing for singles and doubles
claude Jan 16, 2026
d399c9e
refactor: remove _handleSwitch wrapper, use slot-based version directly
claude Jan 16, 2026
e249af5
fix: make effect execution and move validation slot-aware for doubles
claude Jan 16, 2026
0446a0c
docs: rewrite CHANGELOG to focus on features, tests, and future work
claude Jan 16, 2026
0b62da7
refactor: rename activeMonIndex fields to use 0-indexed notation
claude Jan 16, 2026
a741500
Merge branch 'main' into claude/double-battle-switch-combos-jvJGO
sudo-owen Jan 20, 2026
ef54e4d
fix: make AttackCalculator slot-aware for doubles damage calculations
claude Jan 21, 2026
9a06a20
docs: update CHANGELOG with slot-aware damage calculation changes
claude Jan 21, 2026
57eff3a
test: add failing tests for doubles slot bugs in StaminaRegen and Ove…
claude Jan 21, 2026
d7c05b9
fix: make StaminaRegen and Overclock effects doubles-aware
claude Jan 21, 2026
b187305
test: move doubles effect tests to existing test files
claude Jan 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
cache/
out/

# Default forge gas snapshot (we use snapshots/ directory instead)
.gas-snapshot

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
Expand Down
180 changes: 180 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Changelog

## Double Battles Implementation

This document summarizes all changes made to implement double battles support.

---

### Core Data Structure Changes

#### `src/Enums.sol`
- Added `GameMode` enum: `Singles`, `Doubles`

#### `src/Structs.sol`
- **`BattleArgs`** and **`Battle`**: Added `GameMode gameMode` field
- **`BattleData`**: Added `slotSwitchFlagsAndGameMode` (packed field: lower 4 bits = per-slot switch flags, bit 4 = game mode)
- **`BattleContext`** / **`BattleConfigView`**: Added `p0ActiveMonIndex0`, `p0ActiveMonIndex1`, `p1ActiveMonIndex0`, `p1ActiveMonIndex1`, `slotSwitchFlags`, `gameMode`

#### `src/Constants.sol`
- Added `GAME_MODE_BIT`, `SWITCH_FLAGS_MASK`, `ACTIVE_MON_INDEX_MASK` for packed storage

---

### New Files

#### `src/BaseCommitManager.sol`
Extracted shared commit/reveal logic from singles and doubles managers:
- Common errors, events, and storage
- Shared validation functions: `_validateCommit`, `_validateRevealPreconditions`, `_validateRevealTiming`, `_updateAfterReveal`, `_shouldAutoExecute`

#### `src/DoublesCommitManager.sol`
Commit/reveal manager for doubles handling 2 moves per player per turn:
- `commitMoves(battleKey, moveHash)` - Single hash for both moves
- `revealMoves(...)` - Reveal both slot moves with cross-slot switch validation

---

### Interface Changes

#### `src/IEngine.sol`
```solidity
function getActiveMonIndexForSlot(bytes32 battleKey, uint256 playerIndex, uint256 slotIndex) external view returns (uint256);
function getGameMode(bytes32 battleKey) external view returns (GameMode);
function switchActiveMonForSlot(uint256 playerIndex, uint256 slotIndex, uint256 monToSwitchIndex) external;
function setMoveForSlot(bytes32 battleKey, uint256 playerIndex, uint256 slotIndex, uint256 moveIndex, bytes32 salt, uint240 extraData) external;
```

#### `src/IValidator.sol`
```solidity
function validatePlayerMoveForSlot(bytes32 battleKey, uint256 moveIndex, uint256 playerIndex, uint256 slotIndex, uint240 extraData) external returns (bool);
function validatePlayerMoveForSlotWithClaimed(bytes32 battleKey, uint256 moveIndex, uint256 playerIndex, uint256 slotIndex, uint240 extraData, uint256 claimedByOtherSlot) external returns (bool);
function validateSpecificMoveSelection(bytes32 battleKey, uint256 moveIndex, uint256 playerIndex, uint256 slotIndex, uint240 extraData) external returns (bool);
```

---

### Engine Changes

#### Unified Active Mon Index Packing
- Singles and doubles now use the same 4-bit-per-slot packing format
- Singles uses slot 0 only; doubles uses slots 0 and 1
- Removed deprecated `_packActiveMonIndices`, `_unpackActiveMonIndex`, `_setActiveMonIndex`
- All code now uses `_unpackActiveMonIndexForSlot` and `_setActiveMonIndexForSlot`

#### Slot-Aware Effect Execution
- Added overloaded `_runEffects` accepting explicit `monIndex` parameter
- Switch effects (`OnMonSwitchIn`, `OnMonSwitchOut`) pass the switching mon's index
- `dealDamage` passes target mon index to `AfterDamage` effects
- `updateMonState` passes affected mon index to `OnUpdateMonState` effects

#### Slot-Aware Damage Calculations
- `getDamageCalcContext` now accepts `attackerSlotIndex` and `defenderSlotIndex` parameters
- `AttackCalculator._calculateDamage` and `_calculateDamageView` updated with slot parameters
- Ensures doubles damage calculations use correct attacker/defender stats based on slot
- All mon-specific attacks updated to pass explicit slot indices (singles use 0, 0)

#### Doubles Execution Flow
- `_executeDoubles` handles 4 moves per turn with priority/speed ordering
- `_checkForGameOverOrKO_Doubles` checks both slots for each player
- Per-slot switch flags track which slots need to switch after KOs

---

### Validator Changes

#### `src/DefaultValidator.sol`
- `validateSwitch` checks both slots in doubles mode
- `validateSpecificMoveSelection` accepts `slotIndex` for correct mon lookup
- `_getActiveMonIndexFromContext` helper for slot-aware active mon retrieval
- Unified `_hasValidSwitchTargetForSlot` with optional `claimedByOtherSlot` parameter

---

### Test Coverage

#### `test/DoublesValidationTest.sol` (36 tests)
- Turn 0 switch requirements
- KO'd slot handling (with/without valid switch targets)
- Both slots KO'd scenarios (0, 1, or 2 reserves)
- Single-player switch turns (one player switches, other attacks)
- Force-switch moves targeting specific slots
- Storage reuse between singles↔doubles transitions
- Effects running on correct mon for both slots
- Move validation using correct slot's mon stamina
- AfterDamage effects healing correct mon
- Slot 1 damage calculations (defender stats, attacker stats)

#### `test/DoublesCommitManagerTest.sol` (11 tests)
- Commit/reveal flow for doubles
- Move execution ordering by priority and speed
- Position tiebreaker for equal speed
- Game over detection when all mons KO'd

#### Test Mocks Added
- `DoublesTargetedAttack` - Attack targeting specific opponent slot
- `DoublesForceSwitchMove` - Force-switch specific opponent slot
- `DoublesEffectAttack` - Apply effect to specific slot
- `EffectApplyingAttack` - Generic effect applicator for testing
- `MonIndexTrackingEffect` - Tracks which mon effects run on
- `DoublesSlotAttack` - Attack using AttackCalculator with explicit slot parameters

---

### Client Usage

#### Starting a Doubles Battle
```solidity
Battle memory battle = Battle({
// ... other fields ...
moveManager: address(doublesCommitManager),
gameMode: GameMode.Doubles
});
```

#### Turn Flow
```solidity
// Commit hash of both moves
bytes32 moveHash = keccak256(abi.encodePacked(
moveIndex0, extraData0,
moveIndex1, extraData1,
salt
));
doublesCommitManager.commitMoves(battleKey, moveHash);

// Reveal both moves
doublesCommitManager.revealMoves(battleKey, moveIndex0, extraData0, moveIndex1, extraData1, salt, true);
```

#### KO'd Slot Handling
- KO'd slot with valid switch targets → must SWITCH
- KO'd slot with no valid switch targets → must NO_OP
- Both slots KO'd with one reserve → slot 0 switches, slot 1 NO_OPs

---

### Future Work

#### Target Redirection
When a target slot is KO'd mid-turn, moves targeting that slot should redirect or fail. Currently handled by individual move implementations.

#### Move Targeting System
- Standardize targeting semantics (self, ally, opponent slot 0/1, both opponents, all)
- Consider `TargetType` enum and standardized `extraData` encoding

#### Speed Tie Handling
Currently uses basic speed comparison with position tiebreaker. May need explicit rules (random, player advantage).

#### Ability/Effect Integration
- Abilities affecting both slots (e.g., Intimidate)
- Weather/terrain affecting 4 mons
- Spread moves hitting multiple targets

#### Execution Pattern Unification
- Singles: `revealMove` → `execute` directly
- Doubles: `revealMoves` → `setMoveForSlot` × 2 → `execute`
- Consider unifying if performance permits

#### Slot Information in Move Interface
- `IMoveSet.move()` doesn't receive attacker's slot index
- Limits slot-aware move logic in doubles
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": "973562",
"B1_Setup": "812723",
"B2_Execute": "753779",
"B2_Setup": "278155",
"Battle1_Execute": "494044",
"Battle1_Setup": "789140",
"Battle2_Execute": "408734",
"Battle2_Setup": "234804",
"FirstBattle": "3493668",
"Intermediary stuff": "44162",
"SecondBattle": "3586835",
"Setup 1": "1668829",
"Setup 2": "295644",
"Setup 3": "335644",
"ThirdBattle": "2904295"
"B1_Execute": "1020627",
"B1_Setup": "817509",
"B2_Execute": "795025",
"B2_Setup": "282847",
"Battle1_Execute": "512654",
"Battle1_Setup": "793925",
"Battle2_Execute": "427344",
"Battle2_Setup": "237589",
"FirstBattle": "3647997",
"Intermediary stuff": "43924",
"SecondBattle": "3765806",
"Setup 1": "1673640",
"Setup 2": "298473",
"Setup 3": "338195",
"ThirdBattle": "3058698"
}
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": "307493",
"Accept2": "34420",
"Propose1": "199579"
}
Loading