Fix suitbuilder early termination and add armor level scoring

Bug fixes:
- Remove "TEMPORARY FIX" that stopped search after finding first suit
- Add armor level as lowest-priority tiebreaker in scoring

Scoring formula now:
- Set completion: +1000 per complete set
- Missing pieces: -200 penalty each
- Crit damage: +10/20 per item
- Damage rating (clothes): +10/20/30
- Spell coverage: +100 per fulfilled spell
- Base item score: +5 per item
- Armor level: +1 per 100 AL (tiebreaker)

Updated design doc with audit findings - most features were already
working correctly. The magsuitalgo.md analysis was outdated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
erik 2026-01-30 19:04:22 +00:00
parent 45987cfc39
commit 0fd539bedf
2 changed files with 90 additions and 81 deletions

View file

@ -64,118 +64,126 @@ Instead of checking all item combinations, we organize items into "buckets" by e
## Section 3: Current Implementation State ## Section 3: Current Implementation State
### What's Working **Updated after Task 1 Audit (2026-01-30)**
- **Spell Bitmap System** - Correctly implements O(1) spell overlap detection ### What's Working ✅
- **Armor Set Constraints** - 5+4 logic matches MagSuitBuilder exactly
- **Coverage Mask** - Body area tracking with reduction logic for multi-slot items
- **API Endpoints** - `/search`, `/characters`, `/sets` all functional
- **Streaming Results** - SSE (Server-Sent Events) for real-time feedback
- **Frontend** - `suitbuilder.html` and `suitbuilder.js` exist and connect to API
### What's Broken | Feature | Location | Status |
|---------|----------|--------|
| Bucket creation (all 17 slots) | Lines 1006-1135 | ✅ Working |
| Bucket sorting (armor first, smallest first) | Lines 1127-1130 | ✅ Working |
| ItemPreFilter (surpassing logic) | Lines 503-583, used at 969 | ✅ Working |
| Item sorting (by spell count/ratings) | Lines 971-991 | ✅ Working |
| Spell bitmap system | SpellBitmapIndex class | ✅ Working |
| Set constraints (5+4 logic) | can_add_item() | ✅ Working |
| Coverage mask with reductions | Lines 1137-1240 | ✅ Working |
| API Endpoints | `/search`, `/characters`, `/sets` | ✅ Working |
| Streaming Results (SSE) | recursive_search() | ✅ Working |
| Frontend | `suitbuilder.html`, `suitbuilder.js` | ✅ Working |
- **Incomplete Bucket Creation** - Only creates 2 buckets (Chest + Head) instead of 11+ slots ### What's Broken ❌
- **Search Exits Early** - Returns first result instead of completing full search
- **No Item Sorting** - Items processed in database order, not optimal order (best items first)
- **Goes in Circles** - Previous development sessions made inconsistent changes
### What's Missing | Bug | Location | Issue |
|-----|----------|-------|
| **Early termination** | Lines 1326-1329 | "TEMPORARY FIX" stops after finding 1 suit |
| **Armor level not scored** | `_calculate_score()` | Not included as tiebreaker |
- **AccessorySearcher Phase** - Jewelry optimization not implemented ### Current Scoring Formula
- **Item Pre-Filtering** - No "surpassing logic" to remove inferior items before search
- **Progressive Result Tracking** - Should track top-N suits per piece count ```
- **Proper Scoring** - Needs to score by: set completion > spell coverage > armor level score = set_completion_bonus (1000 per complete set)
+ set_missing_penalty (-200 per missing piece)
+ crit_damage (10-20 per item)
+ damage_rating on clothes (10-30)
+ spell_coverage (100 per fulfilled spell)
+ base_item_score (5 per item)
MISSING: + armor_level as lowest-priority tiebreaker
```
### Code Structure ### Code Structure
- `suitbuilder.py`: 1,847 lines - `suitbuilder.py`: 1,847 lines
- Main solver class (`ConstraintSatisfactionSolver`): ~1,130 lines - Main solver class (`ConstraintSatisfactionSolver`): ~1,130 lines
### Note: magsuitalgo.md Analysis Was Outdated
The analysis document incorrectly stated:
- ❌ "Only creates 2 buckets" → Actually creates all 17
- ❌ "No item pre-filtering" → ItemPreFilter exists and is used
- ❌ "No item sorting" → Items are sorted by spell count/ratings
The code is in much better shape than documented. Only 2 bugs need fixing.
--- ---
## Section 4: Implementation Plan ## Section 4: Implementation Plan
**Approach:** Fix incrementally, verify after each step. **Updated after audit - most tasks already done!**
Each task is small, self-contained, and testable. Complete one fully before starting the next.
--- ---
### Task 1: Audit & Document Current Search Flow ### Task 1: Audit & Document Current Search Flow ✅ COMPLETE
**Goal:** Understand exactly what the current code does before changing it **Findings:**
- Bucket creation: ✅ All 17 slots created correctly
- Pre-filtering: ✅ ItemPreFilter.remove_surpassed_items() used at line 969
- Sorting: ✅ Items sorted by spell count (armor/jewelry) and damage rating (clothes)
- Set constraints: ✅ 5+4 logic in can_add_item()
**Steps:** **Bugs Found:**
- Trace through `ConstraintSatisfactionSolver.search()` and document the flow 1. Early termination at lines 1326-1329 ("TEMPORARY FIX")
- Identify where buckets are created (find the 2-bucket limitation) 2. Armor level missing from scoring
- Identify where search exits early
- Write findings as comments or separate doc
**Verify:** Can explain the current flow without looking at code
--- ---
### Task 2: Fix Bucket Creation ### Task 2: Fix Bucket Creation ✅ ALREADY WORKING
**Goal:** Create buckets for all 11 armor/clothing slots No changes needed - all 17 slots are created at lines 1006-1135.
**Steps:**
- Find bucket creation code
- Add missing slots: Head, Chest, Abdomen, Upper Arms, Lower Arms, Hands, Upper Legs, Lower Legs, Feet, plus clothing slots
- Ensure items are placed in correct buckets based on `EquipMask`
**Verify:** Log bucket counts - should see items distributed across 11+ buckets, not just 2
--- ---
### Task 3: Fix Search Completion ### Task 3: Fix Search Completion ✅ COMPLETE
**Goal:** Search processes ALL buckets before returning results **Goal:** Remove early termination so search finds multiple suits
**Steps:** **The Bug (was at lines 1326-1329):**
- Find early exit condition ```python
- Remove or fix it so recursion continues through all buckets # TEMPORARY FIX: Stop search after finding first suit to test completion
- Ensure backtracking works correctly if len(self.best_suits) >= 1:
logger.info(f"[DEBUG] EARLY TERMINATION...")
return
```
**Verify:** Search logs show "processing bucket 1... bucket 2... bucket 11..." not stopping at 2 **Fix Applied:** Removed this block entirely.
--- ---
### Task 4: Add Item Sorting ### Task 4: Add Item Sorting ✅ ALREADY WORKING
**Goal:** Process best items first for better pruning No changes needed - items sorted at lines 971-991.
**Steps:**
- Sort armor items by: set membership → armor level (descending)
- Sort within each bucket before search begins
**Verify:** Log first 5 items per bucket - should be highest armor level items
--- ---
### Task 5: Implement Proper Scoring ### Task 5: Add Armor Level to Scoring ✅ COMPLETE
**Goal:** Score suits by correct priority order **Goal:** Add armor_level as lowest-priority tiebreaker
**Steps:** **Location:** `_calculate_score()` method
- Score = (set_completion × 10000) + (spell_coverage × 100) + armor_level
- Set completion = count of pieces matching primary/secondary sets
- Update `CompletedSuit` scoring logic
**Verify:** Given two suits, the one with better set completion always scores higher regardless of armor **Fix Applied:** Added armor level scoring after base item score:
```python
# 6. Armor level as tiebreaker (LOWEST PRIORITY)
# Scale down significantly so it only matters when other scores are equal
armor_score = state.total_armor // 100 # ~5 points per 500 AL
score += armor_score
```
--- ---
### Task 6: Add Item Pre-Filtering ### Task 6: Add Item Pre-Filtering ✅ ALREADY WORKING
**Goal:** Remove inferior items before search to reduce search space No changes needed - ItemPreFilter used at line 969.
**Steps:**
- Implement "surpassing logic": item A surpasses item B if A has same slot, same set, better stats, and equal/better spells
- Filter out surpassed items before bucketing
**Verify:** Log shows "Filtered X items down to Y items" with significant reduction
--- ---
@ -183,7 +191,7 @@ Each task is small, self-contained, and testable. Complete one fully before star
**Goal:** After armor search, optimize jewelry slots **Goal:** After armor search, optimize jewelry slots
**Scope:** Separate task, do after Tasks 1-6 are solid **Scope:** Separate task, do after core search is verified working
--- ---
@ -207,10 +215,10 @@ Each task is small, self-contained, and testable. Complete one fully before star
| Task | Status | Notes | | Task | Status | Notes |
|------|--------|-------| |------|--------|-------|
| Task 1: Audit Current Flow | Not Started | | | Task 1: Audit Current Flow | ✅ Complete | Found 2 bugs, most features working |
| Task 2: Fix Bucket Creation | Not Started | | | Task 2: Fix Bucket Creation | ✅ Already Working | No changes needed |
| Task 3: Fix Search Completion | Not Started | | | Task 3: Fix Search Completion | ✅ Complete | Removed early termination at lines 1326-1329 |
| Task 4: Add Item Sorting | Not Started | | | Task 4: Add Item Sorting | ✅ Already Working | No changes needed |
| Task 5: Implement Proper Scoring | Not Started | | | Task 5: Add Armor Level Scoring | ✅ Complete | Added armor_score = total_armor // 100 |
| Task 6: Add Item Pre-Filtering | Not Started | | | Task 6: Add Item Pre-Filtering | ✅ Already Working | No changes needed |
| Task 7: AccessorySearcher | Not Started | Future | | Task 7: AccessorySearcher | Not Started | Future |

View file

@ -1322,11 +1322,6 @@ class ConstraintSatisfactionSolver:
suit_data['stats']['secondary_set'] = secondary_set_name suit_data['stats']['secondary_set'] = secondary_set_name
yield SearchResult(type="suit", data=suit_data) yield SearchResult(type="suit", data=suit_data)
# TEMPORARY FIX: Stop search after finding first suit to test completion
if len(self.best_suits) >= 1:
logger.info(f"[DEBUG] EARLY TERMINATION: Found {len(self.best_suits)} suits, stopping search for testing")
return
else: else:
logger.info(f"[DEBUG] Suit REJECTED: score {suit.score} not better than existing") logger.info(f"[DEBUG] Suit REJECTED: score {suit.score} not better than existing")
else: else:
@ -1616,6 +1611,12 @@ class ConstraintSatisfactionSolver:
score += base_item_score score += base_item_score
logger.debug(f"[SCORING] Base item score: {len(state.items)} items = +{base_item_score} points") logger.debug(f"[SCORING] Base item score: {len(state.items)} items = +{base_item_score} points")
# 6. Armor level as tiebreaker (LOWEST PRIORITY)
# Scale down significantly so it only matters when other scores are equal
armor_score = state.total_armor // 100 # ~5 points per 500 AL
score += armor_score
logger.debug(f"[SCORING] Armor level tiebreaker: {state.total_armor} AL = +{armor_score} points")
logger.debug(f"[SCORING] Final score: {score}") logger.debug(f"[SCORING] Final score: {score}")
return max(0, score) # Never negative return max(0, score) # Never negative