From 724ae26ca69c365906f7f4f1880287b54406d394 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 1 May 2026 11:52:38 +0200 Subject: [PATCH] refactor(domain): migrate PlayerAttributes to LoL stats - Replace 19 football attributes with 9 LoL stats (mechanics, laning, teamfighting, macro_play, consistency, shotcalling, champion_pool, discipline, mental_resilience) - Custom Deserialize with serde aliases for backward compatibility - Update player.rs with new struct and OVR calculation - Update TypeScript types and components - Update training and scouting systems - Full SDD artifacts in docs/propose/62-player-attributes-to-lol-stats/ Closes #62 --- .../design.md | 360 +++++++++++ .../proposal.md | 90 +++ .../specs/lol-player-attributes.md | 313 ++++++++++ .../tasks.md | 578 ++++++++++++++++++ src-tauri/crates/domain/src/player.rs | 339 +++++++--- src-tauri/crates/ofm_core/src/potential.rs | 51 +- src-tauri/src/commands/game.rs | 67 +- src/lib/lolPlayerStats.ts | 18 +- src/store/types.ts | 37 +- 9 files changed, 1657 insertions(+), 196 deletions(-) create mode 100644 docs/propose/62-player-attributes-to-lol-stats/design.md create mode 100644 docs/propose/62-player-attributes-to-lol-stats/proposal.md create mode 100644 docs/propose/62-player-attributes-to-lol-stats/specs/lol-player-attributes.md create mode 100644 docs/propose/62-player-attributes-to-lol-stats/tasks.md diff --git a/docs/propose/62-player-attributes-to-lol-stats/design.md b/docs/propose/62-player-attributes-to-lol-stats/design.md new file mode 100644 index 000000000..3d74ec3b3 --- /dev/null +++ b/docs/propose/62-player-attributes-to-lol-stats/design.md @@ -0,0 +1,360 @@ +# Technical Design: Migrate PlayerAttributes to LoL Stats + +## Overview + +This design document details the technical approach for replacing 19 football-specific player attributes with 9 League of Legends stats. The change affects the domain model, serialization layer, business logic, and frontend presentation. + +## Architecture Decisions + +### ADR-1: Serde Aliases for Backward Compatibility + +**Decision**: Use serde's `alias` and `default` attributes plus a custom `Deserialize` implementation for backward compatibility. + +**Rationale**: +- Player attributes are stored as JSON in the database +- Existing save files contain legacy football attribute names +- A custom deserializer allows intelligent mapping from old to new format +- No database schema migration required + +**Tradeoffs**: +- (+) No breaking change for existing saves +- (+) Clean migration path without data export/import +- (-) Custom deserializer adds complexity +- (-) Legacy mapping logic persists in codebase temporarily + +### ADR-2: Remove Intermediate Mapping Layer + +**Decision**: Delete `build_attributes_from_seed()` and use `build_lol_stats_from_seed()` directly. + +**Rationale**: +- The mapping from LoL stats → football attributes → LoL OVR was always temporary +- Direct LoL stat usage simplifies the domain model +- Eliminates confusion about which attribute system is authoritative + +**Tradeoffs**: +- (+) Cleaner, more maintainable code +- (+) No ambiguity about stat semantics +- (-) Requires updating all call sites (105+ matches) + +### ADR-3: Trait System Retention with Mapping Update + +**Decision**: Keep the existing trait system but update thresholds to map from LoL stats. + +**Rationale**: +- Traits provide valuable gameplay flavor +- Many traits have conceptual equivalents in LoL (e.g., "Visionary" → high macro_play) +- Goalkeeper-specific traits will be deprecated/removed + +## Data Model Changes + +### Before: Football Attributes (19 fields) + +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlayerAttributes { + // Physical (4) + pub pace: u8, + pub stamina: u8, + pub strength: u8, + pub agility: u8, + + // Technical (5) + pub passing: u8, + pub shooting: u8, + pub tackling: u8, + pub dribbling: u8, + pub defending: u8, + + // Mental (7) + pub positioning: u8, + pub vision: u8, + pub decisions: u8, + pub composure: u8, + pub aggression: u8, + pub teamwork: u8, + pub leadership: u8, + + // Goalkeeper (3) + pub handling: u8, + pub reflexes: u8, + pub aerial: u8, +} +``` + +### After: LoL Stats (9 fields) + +```rust +#[derive(Debug, Clone, Serialize)] +pub struct PlayerAttributes { + #[serde(alias = "dribbling")] + pub mechanics: u8, + + #[serde(alias = "shooting")] + pub laning: u8, + + #[serde(alias = "teamwork")] + pub teamfighting: u8, + + #[serde(alias = "vision")] + pub macro_play: u8, + + #[serde(alias = "decisions")] + pub consistency: u8, + + #[serde(alias = "leadership")] + pub shotcalling: u8, + + #[serde(alias = "agility")] + pub champion_pool: u8, + + #[serde(alias = "composure")] + pub discipline: u8, + + #[serde(alias = "stamina")] + pub mental_resilience: u8, +} +``` + +## Migration Strategy + +### Phase 1: Custom Deserializer Implementation + +Implement `Deserialize` manually to handle legacy format: + +```rust +impl<'de> Deserialize<'de> for PlayerAttributes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct LegacyAttributes { + // Legacy fields with defaults + #[serde(default = "default_attr")] + pace: u8, + #[serde(default = "default_attr")] + stamina: u8, + // ... all 19 legacy fields + + // New fields (for forward compatibility) + #[serde(default)] + mechanics: Option, + #[serde(default)] + laning: Option, + // ... all 9 new fields + } + + let legacy = LegacyAttributes::deserialize(deserializer)?; + + // If new format present, use it directly + if let (Some(m), Some(l), Some(t), Some(mp), Some(c), Some(s), Some(cp), Some(d), Some(mr)) = + (legacy.mechanics, legacy.laning, legacy.teamfighting, + legacy.macro_play, legacy.consistency, legacy.shotcalling, + legacy.champion_pool, legacy.discipline, legacy.mental_resilience) { + return Ok(PlayerAttributes { + mechanics: m, laning: l, teamfighting: t, + macro_play: mp, consistency: c, shotcalling: s, + champion_pool: cp, discipline: d, mental_resilience: mr, + }); + } + + // Otherwise, map from legacy + Ok(PlayerAttributes { + mechanics: avg(legacy.pace, legacy.dribbling), + laning: legacy.shooting, + teamfighting: legacy.teamwork, + macro_play: legacy.vision, + consistency: legacy.decisions, + shotcalling: legacy.leadership, + champion_pool: legacy.agility, + discipline: legacy.composure, + mental_resilience: legacy.stamina, + }) + } +} +``` + +### Phase 2: Legacy Field Mapping Reference + +| Legacy Field | Maps To | Formula | +|--------------|---------|---------| +| pace | mechanics | avg(pace, dribbling) | +| dribbling | mechanics | avg(pace, dribbling) | +| shooting | laning | direct | +| teamwork | teamfighting | direct | +| vision | macro_play | direct | +| decisions | consistency | direct | +| leadership | shotcalling | direct | +| agility | champion_pool | direct | +| composure | discipline | direct | +| stamina | mental_resilience | direct | +| passing, tackling, strength, defending, positioning, aggression, handling, reflexes, aerial | — | ignored (defaults used) | + +### Phase 3: Save File Detection + +Add a version field to save files to detect legacy format: + +```rust +#[derive(Serialize, Deserialize)] +pub struct SaveFile { + #[serde(default)] + pub version: u32, // 0 or missing = legacy, 1+ = new format + pub game: Game, +} +``` + +## API Changes + +### Rust Backend + +#### Modified Functions + +| Function | File | Change | +|----------|------|--------| +| `calculate_lol_ovr()` | potential.rs | Average 9 LoL stats directly | +| `build_lol_stats_from_seed()` | game.rs | Returns PlayerAttributes instead of [u8; 9] | +| `build_attributes_from_seed()` | game.rs | **REMOVED** | +| `compute_traits()` | player.rs | Update trait thresholds | +| `apply_training()` | training.rs | Train LoL stats directly | +| `generate_scout_report()` | scouting.rs | Report LoL stats | + +#### New Functions + +| Function | File | Purpose | +|----------|------|---------| +| `migrate_legacy_attributes()` | legacy_migration.rs | One-time save migration | + +### TypeScript Frontend + +#### Type Changes + +```typescript +// Before +interface PlayerData { + attributes: { + pace: number; stamina: number; strength: number; agility: number; + passing: number; shooting: number; tackling: number; + dribbling: number; defending: number; + positioning: number; vision: number; decisions: number; + composure: number; aggression: number; teamwork: number; + leadership: number; + handling: number; reflexes: number; aerial: number; + }; +} + +// After +interface PlayerData { + attributes: { + mechanics: number; + laning: number; + teamfighting: number; + macro_play: number; + consistency: number; + shotcalling: number; + champion_pool: number; + discipline: number; + mental_resilience: number; + }; +} +``` + +#### Component Updates + +| Component | Changes | +|-----------|---------| +| `PlayerProfileAttributesCard.tsx` | Update attribute groups, labels, tooltips | +| `TrainingTab.tsx` | Update training focus options | +| `ScoutingReport.tsx` | Display LoL stats | +| `PlayerCard.tsx` | Show primary LoL stat (mechanics) as summary | + +## Testing Strategy + +### Unit Tests + +1. **Deserializer Tests** + - Legacy format with all 19 fields → correct LoL stats + - New format with all 9 fields → direct mapping + - Mixed format (both old and new) → prefer new + - Missing fields → default to 50 + +2. **OVR Calculation Tests** + - All stats equal → returns that value + - Average calculation with rounding + - Min/max clamping at 25/99 + +3. **Trait Derivation Tests** + - Each trait condition with new stat mappings + - Boundary values (threshold - 1, threshold, threshold + 1) + - Multiple traits on same player + +### Integration Tests + +1. **Save Migration** + - Load legacy save → verify correct migration + - Load already-migrated save → no double migration + - Save after migration → new format persisted + +2. **End-to-End Flow** + - Generate player from seed → correct stats + - Train player → stats improve + - Scout player → report shows LoL stats + - Calculate OVR → uses LoL stats + +### Regression Tests + +- Existing gameplay features (matches, transfers, contracts) +- UI interactions (player profile, team setup) +- Save/load cycle + +## Rollback Plan + +If critical issues are discovered: + +1. Revert the PR/branch +2. Players saved in new format will fail to load (acceptable for pre-release) +3. Legacy saves remain unaffected + +## Risk Mitigation + +| Risk | Mitigation | +|------|------------| +| Data loss during migration | Comprehensive backup before migration; idempotent migration logic | +| Incorrect stat mapping | Unit tests for each mapping; spot-check with gameplay experts | +| UI confusion | Clear tooltips explaining each LoL stat; i18n keys for localization | +| Trait calculation errors | Boundary tests; compare pre/post migration trait counts | + +## Performance Considerations + +- Custom deserializer adds minimal overhead (one-time per player load) +- Smaller struct (9 vs 19 fields) reduces memory footprint +- Direct OVR calculation is faster (no mapping layer) + +## Files Modified + +### Backend (Rust) +- `domain/src/player.rs` — PlayerAttributes struct, compute_traits() +- `ofm_core/src/potential.rs` — calculate_lol_ovr() +- `ofm_core/src/training.rs` — Training adjustments +- `ofm_core/src/scouting.rs` — Scout report generation +- `src/commands/game.rs` — Player generation functions +- `db/src/legacy_migration.rs` — Save migration logic + +### Frontend (TypeScript) +- `src/store/types.ts` — PlayerData interface +- `src/components/playerProfile/*.tsx` — Attribute display +- `src/components/training/*.tsx` — Training UI +- `src/components/scouting/*.tsx` — Scouting UI + +### Tests +- All test files using PlayerAttributes test helpers +- Snapshot tests may need updates + +## Success Metrics + +- [ ] All 105+ Rust references updated +- [ ] All TypeScript types updated +- [ ] Legacy save files load correctly +- [ ] New save files use new format +- [ ] No references to football attributes in non-migration code +- [ ] All tests pass +- [ ] Manual QA confirms correct OVR calculations diff --git a/docs/propose/62-player-attributes-to-lol-stats/proposal.md b/docs/propose/62-player-attributes-to-lol-stats/proposal.md new file mode 100644 index 000000000..def41df0b --- /dev/null +++ b/docs/propose/62-player-attributes-to-lol-stats/proposal.md @@ -0,0 +1,90 @@ +# Proposal: Migrate PlayerAttributes to LoL Stats + +## Intent + +The current `PlayerAttributes` struct uses 19 football-specific attributes (pace, stamina, strength, agility, passing, shooting, tackling, dribbling, defending, positioning, vision, decisions, composure, aggression, teamwork, leadership, handling, reflexes, aerial). As the game transitions to a League of Legends-themed manager, we need to replace these with LoL-appropriate stats that reflect competitive League of Legends gameplay. This change will replace the football attributes with 9 LoL stats (mechanics, laning, teamfighting, macro_play, consistency, shotcalling, champion_pool, discipline, mental_resilience), aligning the domain model with the new thematic direction and simplifying the mapping already present in the codebase. + +## Scope + +### In Scope +- Replace `PlayerAttributes` struct in `domain/src/player.rs` with 9 LoL stats. +- Update all Rust references (105+ matches across `domain`, `ofm_core`, `db`, `commands`). +- Update frontend TypeScript types and components that reference football attributes. +- Ensure backward compatibility for existing save files via serde migration (custom deserializer that maps old field names to new ones with reasonable defaults). +- Update `calculate_lol_ovr` function to compute OVR directly from LoL stats (no mapping needed). +- Update `build_lol_stats_from_seed` and `build_attributes_from_seed` functions (merge into one, as the mapping becomes obsolete). +- Update training, scouting, and other systems that reference specific football attributes. + +### Out of Scope +- Changing simulation logic beyond adapting to the new stats (i.e., no rebalancing of modifiers). +- Adding new UI components for stat visualization (future work). +- Database schema changes (player attributes stored as JSON, so only migration of existing data). +- Changing the 9 LoL stats themselves (already defined and in use). + +## Capabilities + +### New Capabilities +- ``: Replaces football-specific player attributes with League of Legends stats, affecting player generation, training, scouting, match simulation, and overall rating. + +### Modified Capabilities +- ``: `calculate_lol_ovr` now averages the 9 LoL stats directly, removing the mapping layer. +- ``: `build_lol_stats_from_seed` becomes the primary generation function; `build_attributes_from_seed` is removed. +- ``: Training adjustments target LoL stats (mechanics, laning, teamfighting, etc.) instead of football attributes. +- ``: Scouting reports show LoL stats instead of football attributes. + +## Approach + +1. **Define new `PlayerAttributes` struct** with 9 LoL stats fields, using serde aliases for backward compatibility (e.g., `#[serde(alias = "pace")]` mapping to appropriate new field or default). +2. **Implement custom deserialization** that maps old football attribute names to new LoL stats with intelligent defaults (e.g., pace → mechanics, stamina → mental_resilience, etc.) using the existing mapping in `build_attributes_from_seed` as reference. +3. **Update `calculate_lol_ovr`** to average the 9 LoL stats fields directly. +4. **Update all Rust code** (105+ matches) to use new field names; adjust any logic that differentiated between football attributes (e.g., goalkeeper handling/reflexes/aerial become irrelevant; keep as low defaults or remove). +5. **Update frontend TypeScript types** (`PlayerAttributes` interface) and components that display attributes (player cards, training UI, scouting UI). +6. **Update training system** to train LoL stats (e.g., "Mechanics", "Laning", "Teamfighting" focus). +7. **Update scouting system** to report LoL stats. +8. **Add data migration** for existing saves: custom deserializer that maps old JSON fields to new ones using the same mapping as `build_attributes_from_seed`. If a field is missing, assign a default (50). +9. **Update tests** that rely on football attributes. + +## Affected Areas + +| Area | Impact | Description | +|------|--------|-------------| +| `src-tauri/crates/domain/src/player.rs` | Modified | Replace `PlayerAttributes` struct with LoL stats. | +| `src-tauri/crates/ofm_core/src/potential.rs` | Modified | Update `calculate_lol_ovr` to use new fields. | +| `src-tauri/src/commands/game.rs` | Modified | Update `build_lol_stats_from_seed` and `build_attributes_from_seed`; merge into one function. | +| `src-tauri/crates/ofm_core/src/training.rs` | Modified | Update training adjustments to target LoL stats. | +| `src-tauri/crates/ofm_core/src/scouting.rs` | Modified | Update scouting reports. | +| `src-tauri/crates/db/src/repositories/player_repo.rs` | Modified | Update deserialization logic. | +| `src-tauri/crates/db/src/legacy_migration.rs` | Modified | Add migration for old saves. | +| `src/components/**/PlayerCard.tsx` | Modified | Update UI to show LoL stats. | +| `src/components/**/TrainingTab.tsx` | Modified | Update training UI. | +| `src/components/**/ScoutingReport.tsx` | Modified | Update scouting UI. | +| `src/types/player.ts` | Modified | Update TypeScript interface. | +| Various test files | Modified | Update test helpers and assertions. | + +## Risks + +| Risk | Likigation | Mitigation | +|------|------------|------------| +| Breaking existing saves | High | Implement custom deserializer with aliases and defaults; test with existing save files. | +| UI confusion | Medium | Update UI labels to reflect LoL stats; keep tooltips explaining each stat. | +| Missing references (105+ matches) | Medium | Use global search/replace with careful review; run full test suite after changes. | +| Incorrect stat mapping | Medium | Use existing mapping from `build_attributes_from_seed` as reference; verify with gameplay experts. | + +## Rollback Plan + +Revert the branch; the change is self-contained and does not affect database schemas (player attributes stored as JSON). Existing saves that already use the new struct will not load after rollback, but that's acceptable for a pre-release change. + +## Dependencies + +- None (this is a standalone refactor). + +## Success Criteria + +- [ ] All Rust code compiles with new LoL stats replacing football attributes. +- [ ] Existing save files (with football attributes) load correctly via custom deserializer. +- [ ] `calculate_lol_ovr` returns same overall rating as before (or with documented adjustments). +- [ ] Training system improves LoL stats as expected. +- [ ] Scouting reports show LoL stats correctly. +- [ ] Frontend UI displays LoL stats with proper labels and tooltips. +- [ ] All existing tests pass; new tests added for stat mapping and deserialization. +- [ ] No references to football attribute names remain in the codebase (except serde aliases). \ No newline at end of file diff --git a/docs/propose/62-player-attributes-to-lol-stats/specs/lol-player-attributes.md b/docs/propose/62-player-attributes-to-lol-stats/specs/lol-player-attributes.md new file mode 100644 index 000000000..8b04743b8 --- /dev/null +++ b/docs/propose/62-player-attributes-to-lol-stats/specs/lol-player-attributes.md @@ -0,0 +1,313 @@ +# Delta Spec: LoL Player Attributes Migration + +## Domain: lol-player-attributes + +### ADDED Requirements + +#### Requirement: LoL Stats Definition + +The system MUST define a `PlayerAttributes` struct with exactly 9 LoL-specific stats ranging from 0-100. + +| Stat | Description | Mapping from Legacy | +|------|-------------|---------------------| +| mechanics | Technical skill and champion execution | dribbling | +| laning | 1v1 and 2v2 lane phase performance | shooting | +| teamfighting | Coordination in 5v5 engagements | teamwork | +| macro_play | Map awareness and objective control | vision | +| consistency | Performance stability across games | decisions | +| shotcalling | In-game leadership and calls | leadership | +| champion_pool | Champion versatility and mastery | agility | +| discipline | Focus and tilt resistance | composure | +| mental_resilience | Pressure handling and recovery | stamina | + +#### Scenario: New Player Generation + +- GIVEN a new player is generated from seed data +- WHEN the system creates `PlayerAttributes` +- THEN it MUST populate all 9 LoL stats directly from `build_lol_stats_from_seed()` +- AND the legacy mapping function MUST be removed + +#### Scenario: Stat Value Validation + +- GIVEN any LoL stat value +- WHEN the value is set or modified +- THEN it MUST be clamped to the range 25-99 inclusive +- AND default values for missing stats MUST be 50 + +### MODIFIED Requirements + +#### Requirement: Overall Rating Calculation + +The `calculate_lol_ovr()` function MUST compute OVR as the direct average of all 9 LoL stats without intermediate mapping. + +(Previously: averaged 9 mapped football attributes derived from LoL stats) + +#### Scenario: OVR Calculation with New Stats + +- GIVEN a player with LoL stats [mechanics=75, laning=70, teamfighting=80, macro_play=72, consistency=68, shotcalling=65, champion_pool=78, discipline=70, mental_resilience=74] +- WHEN `calculate_lol_ovr()` is called +- THEN it MUST return 72 (rounded average of all 9 stats) + +#### Scenario: OVR Edge Cases + +- GIVEN a player with all stats at minimum (25) +- WHEN OVR is calculated +- THEN it MUST return 25 +- GIVEN a player with all stats at maximum (99) +- WHEN OVR is calculated +- THEN it MUST return 99 + +### REMOVED Requirements + +#### Requirement: Legacy Football Attributes + +(Reason: Replaced by LoL-specific stats. Migration handled via serde deserializer) + +The following 19 football attributes are REMOVED: +- pace, stamina, strength, agility +- passing, shooting, tackling, dribbling, defending +- positioning, vision, decisions, composure, aggression, teamwork, leadership +- handling, reflexes, aerial + +--- + +## Domain: player-serde-migration + +### ADDED Requirements + +#### Requirement: Backward Compatibility Deserializer + +The system MUST implement a custom serde deserializer that maps legacy football attribute names to new LoL stats using the established mapping from `build_attributes_from_seed()`. + +#### Scenario: Loading Legacy Save File + +- GIVEN a JSON player with legacy attributes `{ "pace": 70, "stamina": 72, "shooting": 65, ... }` +- WHEN the player is deserialized +- THEN the system MUST map legacy fields to LoL stats using intelligent defaults: + - pace → mechanics (averaged with dribbling if present) + - shooting → laning + - teamwork → teamfighting + - vision → macro_play + - decisions → consistency + - leadership → shotcalling + - agility → champion_pool + - composure → discipline + - stamina → mental_resilience +- AND missing fields MUST default to 50 + +#### Scenario: Loading New Format Save File + +- GIVEN a JSON player with new LoL attributes `{ "mechanics": 75, "laning": 70, ... }` +- WHEN the player is deserialized +- THEN it MUST deserialize directly without transformation +- AND all 9 stats MUST be present in the resulting struct + +#### Scenario: Mixed Legacy and New Format + +- GIVEN a JSON with both legacy and new format fields +- WHEN the player is deserialized +- THEN new format fields MUST take precedence +- AND legacy fields MUST be ignored if new format is present + +--- + +## Domain: player-generation + +### MODIFIED Requirements + +#### Requirement: Player Generation from Seed + +The `build_lol_stats_from_seed()` function becomes the PRIMARY generation function; `build_attributes_from_seed()` is REMOVED. + +(Previously: `build_lol_stats_from_seed()` returned an array that was then mapped to football attributes via `build_attributes_from_seed()`) + +#### Scenario: Seed-Based Player Creation + +- GIVEN a `DraftPlayerSeed` with role="mid" and rating=75 +- WHEN a player is generated +- THEN `build_lol_stats_from_seed()` MUST return 9 stats with role-based bias: + - Mid: higher mechanics (+2), laning (+2) + - Top: higher mechanics (+1), teamfighting (+1), discipline (+1), mental_resilience (+2) + - Jungle: higher macro_play (+2), shotcalling (+2) + - ADC: higher mechanics (+2), laning (+2) + - Support: higher macro_play (+2), shotcalling (+2), discipline (+1) +- AND all stats MUST be within 25-99 range +- AND the average MUST approximate the target rating (±3) + +--- + +## Domain: player-training + +### MODIFIED Requirements + +#### Requirement: Training System Integration + +The training system MUST adjust LoL stats directly instead of mapping through football attributes. + +(Previously: trained football attributes which were then mapped back to LoL stats conceptually) + +#### Scenario: Individual Training Focus + +- GIVEN a player with training_focus="Mechanics" +- WHEN daily training is processed +- THEN the mechanics stat MUST receive the primary training bonus +- AND related stats (laning, consistency) MAY receive secondary bonuses +- AND the training gain MUST respect the effective_potential_cap + +#### Scenario: Team Training by Focus + +- GIVEN a team with training_focus="MacroSystems" +- WHEN team training is processed +- THEN all team players' macro_play stat MUST receive bonus +- AND shotcalling MAY receive secondary bonus +- AND gains MUST be modulated by facility level and coaching quality + +#### Scenario: Training Intensity Impact + +- GIVEN a team with TrainingIntensity::Intense +- WHEN training is processed +- THEN stat gains MUST be multiplied by 1.3 +- AND condition depletion MUST be multiplied by 1.5 + +--- + +## Domain: player-scouting + +### MODIFIED Requirements + +#### Requirement: Scouting Report Format + +Scouting reports MUST display LoL stats instead of football attributes. + +(Previously: showed football attributes or partially mapped LoL stats) + +#### Scenario: Scout Report Generation + +- GIVEN a completed scouting assignment +- WHEN the report is generated +- THEN it MUST include all 9 LoL stats visible to the scout +- AND stats above the scout's judging_ability threshold MUST be accurate +- AND stats below threshold MUST show as approximate ranges (??) + +#### Scenario: Scout Report Accuracy + +- GIVEN a scout with judging_ability=80 +- WHEN evaluating a player +- THEN stats above 80 MUST be shown as exact values +- AND stats 60-80 MUST be shown with ±3 variance +- AND stats below 60 MUST be hidden or marked as "??" + +--- + +## Domain: player-traits + +### MODIFIED Requirements + +#### Requirement: Trait Derivation from LoL Stats + +The `compute_traits()` function MUST derive traits directly from LoL stats using equivalent thresholds. + +(Previously: derived from football attributes) + +| Trait | Old Condition | New Condition | +|-------|--------------|---------------| +| Speedster | pace >= 85 | mechanics >= 85 | +| Tank | strength>=85 && stamina>=75 | teamfighting>=85 && mental_resilience>=75 | +| Agile | agility >= 85 | champion_pool >= 85 | +| Tireless | stamina >= 90 | mental_resilience >= 90 | +| Playmaker | passing>=80 && vision>=80 | macro_play>=80 && shotcalling>=80 | +| Sharpshooter | shooting >= 85 | laning >= 85 | +| Dribbler | dribbling >= 85 | mechanics >= 85 | +| BallWinner | tackling>=80 && aggression>=70 | discipline>=80 && teamfighting>=70 | +| Rock | defending>=85 && positioning>=75 | teamfighting>=85 && macro_play>=75 | +| Leader | leadership>=85 && teamwork>=75 | shotcalling>=85 && teamfighting>=75 | +| CoolHead | composure>=85 && decisions>=80 | discipline>=85 && consistency>=80 | +| Visionary | vision >= 85 | macro_play >= 85 | +| HotHead | aggression>=85 && composure<50 | low discipline, high teamfighting | +| TeamPlayer | teamwork >= 85 | teamfighting >= 85 | +| SafeHands | handling >= 85 | (removed - goalkeeper trait) | +| CatReflexes | reflexes >= 85 | (removed - goalkeeper trait) | +| AerialDominance | aerial >= 85 | (removed - goalkeeper trait) | +| CompleteForward | shooting>=75 && dribbling>=75 && pace>=70 && strength>=70 | mechanics>=75 && laning>=75 && champion_pool>=70 | +| Engine | stamina>=85 && pace>=70 && teamwork>=75 | mental_resilience>=85 && mechanics>=70 && teamfighting>=75 | +| SetPieceSpecialist | passing>=80 && shooting>=75 && vision>=75 | macro_play>=80 && laning>=75 && shotcalling>=75 | + +--- + +## Domain: frontend-player-attributes + +### MODIFIED Requirements + +#### Requirement: TypeScript Type Definition + +The frontend `PlayerData.attributes` type MUST be updated to reflect the 9 LoL stats. + +(Previously: 19 football attributes) + +#### Scenario: Frontend Type Safety + +- GIVEN the TypeScript `PlayerData` interface +- WHEN accessing player.attributes +- THEN it MUST expose only the 9 LoL stat fields +- AND type checking MUST reject legacy football attribute names + +#### Scenario: UI Display Update + +- GIVEN the PlayerProfileAttributesCard component +- WHEN rendering player attributes +- THEN it MUST group LoL stats logically: + - Mechanical: mechanics, laning, champion_pool + - Tactical: teamfighting, macro_play, shotcalling + - Mental: consistency, discipline, mental_resilience +- AND each stat MUST have a descriptive tooltip +- AND stat names MUST be i18n-compatible + +--- + +## Domain: database-migration + +### ADDED Requirements + +#### Requirement: Legacy Save Migration + +The system MUST provide a one-time migration for existing save files that converts football attributes to LoL stats. + +#### Scenario: Save File Version Detection + +- GIVEN a save file with version < 2.0 +- WHEN the game loads +- THEN it MUST detect legacy format via absence of LoL stat fields +- AND trigger the migration path +- AND save the file in new format after migration + +#### Scenario: Migration Idempotency + +- GIVEN a save file that has already been migrated +- WHEN the game loads again +- THEN it MUST recognize the new format +- AND skip migration +- AND not corrupt existing data + +--- + +## Summary + +| Domain | Added | Modified | Removed | +|--------|-------|----------|---------| +| lol-player-attributes | 2 | 2 | 1 | +| player-serde-migration | 3 | 0 | 0 | +| player-generation | 1 | 1 | 0 | +| player-training | 0 | 3 | 0 | +| player-scouting | 0 | 2 | 0 | +| player-traits | 0 | 1 | 0 | +| frontend-player-attributes | 0 | 2 | 0 | +| database-migration | 2 | 0 | 0 | +| **Total** | **8** | **11** | **1** | + +### Test Coverage Requirements + +- Unit tests for serde deserialization (legacy → new format) +- Unit tests for `calculate_lol_ovr()` with edge cases +- Unit tests for trait derivation with new stat mappings +- Integration tests for save file migration +- Frontend tests for attribute display components diff --git a/docs/propose/62-player-attributes-to-lol-stats/tasks.md b/docs/propose/62-player-attributes-to-lol-stats/tasks.md new file mode 100644 index 000000000..5d928a0bc --- /dev/null +++ b/docs/propose/62-player-attributes-to-lol-stats/tasks.md @@ -0,0 +1,578 @@ +# Task Breakdown: Migrate PlayerAttributes to LoL Stats + +## Phase 1: Foundation & Data Model (Tasks 1-8) + +### Task 1.1: Update PlayerAttributes struct definition +**File**: `src-tauri/crates/domain/src/player.rs` +**Priority**: P0 - Blocking +**Estimate**: 2h +**Description**: Replace 19 football attributes with 9 LoL stats in the struct definition. Add serde aliases for backward compatibility. + +**Acceptance Criteria**: +- [ ] Struct has exactly 9 fields: mechanics, laning, teamfighting, macro_play, consistency, shotcalling, champion_pool, discipline, mental_resilience +- [ ] Each field has appropriate serde alias for legacy mapping +- [ ] All fields are u8 type +- [ ] Default value function returns 50 + +--- + +### Task 1.2: Implement custom deserializer for backward compatibility +**File**: `src-tauri/crates/domain/src/player.rs` +**Priority**: P0 - Blocking +**Estimate**: 4h +**Description**: Implement custom Deserialize trait that maps legacy football attributes to new LoL stats. + +**Acceptance Criteria**: +- [ ] Legacy format with 19 fields deserializes correctly +- [ ] New format with 9 fields deserializes directly +- [ ] Mixed format prefers new fields +- [ ] Missing fields default to 50 +- [ ] Unit tests for all mapping combinations + +--- + +### Task 1.3: Update calculate_lol_ovr function +**File**: `src-tauri/crates/ofm_core/src/potential.rs` +**Priority**: P0 - Blocking +**Estimate**: 1h +**Description**: Modify OVR calculation to average the 9 LoL stats directly instead of mapped football attributes. + +**Acceptance Criteria**: +- [ ] Function averages mechanics, laning, teamfighting, macro_play, consistency, shotcalling, champion_pool, discipline, mental_resilience +- [ ] Result is rounded and clamped 25-99 +- [ ] Unit tests updated with new test cases +- [ ] Existing tests that relied on old mapping updated + +--- + +### Task 1.4: Remove build_attributes_from_seed function +**File**: `src-tauri/src/commands/game.rs` +**Priority**: P0 - Blocking +**Estimate**: 1h +**Description**: Delete the legacy mapping function. Update build_lol_stats_from_seed to return PlayerAttributes directly. + +**Acceptance Criteria**: +- [ ] build_attributes_from_seed function removed +- [ ] build_lol_stats_from_seed returns PlayerAttributes +- [ ] All call sites updated to use new return type +- [ ] No compilation errors + +--- + +### Task 1.5: Update build_lol_stats_from_seed return type +**File**: `src-tauri/src/commands/game.rs` +**Priority**: P0 - Blocking +**Estimate**: 1h +**Description**: Modify function signature and all usages to work with PlayerAttributes instead of [u8; 9]. + +**Acceptance Criteria**: +- [ ] Function returns PlayerAttributes +- [ ] Internal array construction still used, then converted to struct +- [ ] All callers updated +- [ ] Tests updated + +--- + +### Task 1.6: Update trait computation logic +**File**: `src-tauri/crates/domain/src/player.rs` +**Priority**: P1 - High +**Estimate**: 3h +**Description**: Update compute_traits() to derive traits from LoL stats using new thresholds. + +**Acceptance Criteria**: +- [ ] All trait conditions updated per design document mapping table +- [ ] Goalkeeper traits (SafeHands, CatReflexes, AerialDominance) removed or deprecated +- [ ] New trait conditions tested with boundary values +- [ ] Trait computation tests pass + +--- + +### Task 1.7: Add default value handling for missing fields +**File**: `src-tauri/crates/domain/src/player.rs` +**Priority**: P1 - High +**Estimate**: 1h +**Description**: Ensure serde default handling works correctly for partial data. + +**Acceptance Criteria**: +- [ ] Missing LoL stat fields default to 50 +- [ ] Legacy fields without mapping default appropriately +- [ ] Test cases for partial deserialization + +--- + +### Task 1.8: Create legacy migration module +**File**: `src-tauri/crates/db/src/legacy_migration.rs` +**Priority**: P1 - High +**Estimate**: 3h +**Description**: Create dedicated module for save file migration logic with version detection. + +**Acceptance Criteria**: +- [ ] Migration module created with version detection +- [ ] One-time migration path implemented +- [ ] Idempotent migration (won't double-migrate) +- [ ] Logging for migration events + +--- + +## Phase 2: Training System Updates (Tasks 2.1-2.5) + +### Task 2.1: Update training focus definitions +**File**: `src-tauri/crates/domain/src/team.rs` (or training focus module) +**Priority**: P1 - High +**Estimate**: 1h +**Description**: Ensure training focus enum values map to LoL stats. + +**Acceptance Criteria**: +- [ ] TrainingFocus enum values reviewed and updated if needed +- [ ] Each focus maps to appropriate LoL stat(s) +- [ ] Documentation updated + +--- + +### Task 2.2: Update individual training adjustments +**File**: `src-tauri/crates/ofm_core/src/training.rs` +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Modify training logic to adjust LoL stats directly instead of mapped football attributes. + +**Acceptance Criteria**: +- [ ] Individual training targets correct LoL stat +- [ ] Secondary bonuses updated for related stats +- [ ] Potential cap enforcement still works +- [ ] Tests updated + +--- + +### Task 2.3: Update team training adjustments +**File**: `src-tauri/crates/ofm_core/src/training.rs` +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update team-wide training to affect appropriate LoL stats. + +**Acceptance Criteria**: +- [ ] Team training focus affects correct LoL stat +- [ ] Facility level bonuses apply correctly +- [ ] Staff coaching effects work with new stats + +--- + +### Task 2.4: Update training intensity effects +**File**: `src-tauri/crates/ofm_core/src/training.rs` +**Priority**: P2 - Medium +**Estimate**: 1h +**Description**: Ensure training intensity multipliers work with LoL stat gains. + +**Acceptance Criteria**: +- [ ] Intense training gives 1.3x LoL stat gains +- [ ] Condition depletion still works +- [ ] Light training gives reduced gains + +--- + +### Task 2.5: Update training tests +**File**: `src-tauri/crates/ofm_core/src/training.rs` (tests) +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update all training-related tests to use LoL stats. + +**Acceptance Criteria**: +- [ ] Test helpers updated to create players with LoL stats +- [ ] All existing tests pass with new stats +- [ ] New tests for LoL stat training gains + +--- + +## Phase 3: Scouting System Updates (Tasks 3.1-3.4) + +### Task 3.1: Update scout report generation +**File**: `src-tauri/crates/ofm_core/src/scouting.rs` +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Modify scout reports to include LoL stats instead of football attributes. + +**Acceptance Criteria**: +- [ ] Scout report contains all 9 LoL stats +- [ ] Accuracy based on scout judging_ability works +- [ ] Hidden stats (below threshold) show as "??" + +--- + +### Task 3.2: Update scouting report accuracy logic +**File**: `src-tauri/crates/ofm_core/src/scouting.rs` +**Priority**: P2 - Medium +**Estimate**: 1h +**Description**: Ensure stat visibility thresholds work with LoL stats. + +**Acceptance Criteria**: +- [ ] Stats > judging_ability shown exactly +- [ ] Stats 60-80 shown with ±3 variance +- [ ] Stats < 60 hidden + +--- + +### Task 3.3: Update scouting-related types +**File**: `src-tauri/crates/domain/src/scouting.rs` (if exists) or scouting module +**Priority**: P1 - High +**Estimate**: 1h +**Description**: Update type definitions for scout reports to use LoL stats. + +**Acceptance Criteria**: +- [ ] Scout report struct uses LoL stat names +- [ ] Serde serialization updated +- [ ] Frontend types will be updated in Phase 5 + +--- + +### Task 3.4: Update scouting tests +**File**: `src-tauri/crates/ofm_core/src/scouting.rs` (tests) +**Priority**: P2 - Medium +**Estimate**: 1h +**Description**: Update scouting tests to work with LoL stats. + +**Acceptance Criteria**: +- [ ] Test players created with LoL stats +- [ ] Report accuracy tests updated +- [ ] All scouting tests pass + +--- + +## Phase 4: Frontend TypeScript Updates (Tasks 4.1-4.6) + +### Task 4.1: Update PlayerData TypeScript interface +**File**: `src/store/types.ts` +**Priority**: P0 - Blocking +**Estimate**: 1h +**Description**: Replace 19 football attributes with 9 LoL stats in TypeScript type. + +**Acceptance Criteria**: +- [ ] PlayerData.attributes has 9 LoL stat fields +- [ ] Legacy football attributes removed +- [ ] Type checking passes + +--- + +### Task 4.2: Update PlayerProfileAttributesCard component +**File**: `src/components/playerProfile/PlayerProfileAttributesCard.tsx` +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update UI to display LoL stats in organized groups. + +**Acceptance Criteria**: +- [ ] Component displays 9 LoL stats +- [ ] Stats grouped logically (Mechanical, Tactical, Mental) +- [ ] Labels and tooltips updated +- [ ] i18n keys added for stat names + +--- + +### Task 4.3: Update attribute grouping logic +**File**: `src/components/playerProfile/PlayerProfile.attributes.ts` (if exists) +**Priority**: P1 - High +**Estimate**: 1h +**Description**: Update attribute grouping helper for LoL stats. + +**Acceptance Criteria**: +- [ ] Mechanical group: mechanics, laning, champion_pool +- [ ] Tactical group: teamfighting, macro_play, shotcalling +- [ ] Mental group: consistency, discipline, mental_resilience +- [ ] Average calculations work per group + +--- + +### Task 4.4: Update TrainingTab component +**File**: `src/components/training/TrainingTab.tsx` (or similar) +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update training UI to reference LoL stats. + +**Acceptance Criteria**: +- [ ] Training focus dropdown shows LoL stat names +- [ ] Individual training targets display correctly +- [ ] Training preview shows expected LoL stat gains + +--- + +### Task 4.5: Update ScoutingReport component +**File**: `src/components/scouting/ScoutingReport.tsx` (or similar) +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update scouting UI to display LoL stats in reports. + +**Acceptance Criteria**: +- [ ] Scout reports show 9 LoL stats +- [ ] Hidden stats display as "??" +- [ ] Stat bars/colors work with LoL stats + +--- + +### Task 4.6: Update any other attribute references +**File**: Various frontend files +**Priority**: P2 - Medium +**Estimate**: 2h +**Description**: Search and update all remaining frontend references to football attributes. + +**Acceptance Criteria**: +- [ ] Global search for old attribute names returns 0 results +- [ ] PlayerCard shows relevant LoL stat +- [ ] Any stat comparison logic updated + +--- + +## Phase 5: Testing & Validation (Tasks 5.1-5.8) + +### Task 5.1: Update test helpers in domain crate +**File**: `src-tauri/crates/domain/src/player.rs` (test helpers) +**Priority**: P0 - Blocking +**Estimate**: 2h +**Description**: Update sample_attributes() and other test helpers to use LoL stats. + +**Acceptance Criteria**: +- [ ] sample_attributes() returns PlayerAttributes with LoL stats +- [ ] All test compilation errors resolved +- [ ] Test defaults are reasonable (50-70 range) + +--- + +### Task 5.2: Update potential.rs tests +**File**: `src-tauri/crates/ofm_core/src/potential.rs` (tests) +**Priority**: P0 - Blocking +**Estimate**: 1h +**Description**: Update OVR calculation tests for LoL stats. + +**Acceptance Criteria**: +- [ ] Test attrs() helper uses LoL stats +- [ ] OVR calculation tests pass +- [ ] Edge case tests (min, max, average) + +--- + +### Task 5.3: Update game.rs tests +**File**: `src-tauri/src/commands/game.rs` (tests) +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update player generation tests for new return type. + +**Acceptance Criteria**: +- [ ] Tests compile with new build_lol_stats_from_seed signature +- [ ] Seed-based generation tests pass +- [ ] Stat distribution tests updated + +--- + +### Task 5.4: Create serde migration tests +**File**: `src-tauri/crates/domain/src/player.rs` (tests) or new test file +**Priority**: P0 - Blocking +**Estimate**: 3h +**Description**: Comprehensive tests for backward compatibility deserializer. + +**Acceptance Criteria**: +- [ ] Test: Full legacy format → correct LoL stats +- [ ] Test: New format → direct mapping +- [ ] Test: Mixed format → prefers new +- [ ] Test: Partial legacy → defaults for missing +- [ ] Test: Partial new → defaults for missing + +--- + +### Task 5.5: Create integration tests for save migration +**File**: `src-tauri/crates/db/src/legacy_migration.rs` (tests) +**Priority**: P1 - High +**Estimate**: 3h +**Description**: Test end-to-end save file migration. + +**Acceptance Criteria**: +- [ ] Legacy save file loads and migrates correctly +- [ ] Migration is idempotent +- [ ] New saves don't trigger migration +- [ ] Migration logging works + +--- + +### Task 5.6: Update trait computation tests +**File**: `src-tauri/crates/domain/src/player.rs` (tests) +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Update trait derivation tests for LoL stat mappings. + +**Acceptance Criteria**: +- [ ] Each trait test uses correct LoL stat thresholds +- [ ] Boundary value tests (threshold ±1) +- [ ] Removed goalkeeper traits handled + +--- + +### Task 5.7: Update frontend tests +**File**: Various `.test.ts` files +**Priority**: P2 - Medium +**Estimate**: 2h +**Description**: Update frontend unit tests that reference player attributes. + +**Acceptance Criteria**: +- [ ] Mock player data uses LoL stats +- [ ] Component tests pass +- [ ] TypeScript type errors resolved + +--- + +### Task 5.8: Run full test suite +**File**: Entire codebase +**Priority**: P0 - Blocking +**Estimate**: 2h +**Description**: Execute all tests and fix any remaining failures. + +**Acceptance Criteria**: +- [ ] `cargo test` passes in all crates +- [ ] `npm test` passes for frontend +- [ ] No test compilation errors +- [ ] Test coverage maintained or improved + +--- + +## Phase 6: Documentation & Cleanup (Tasks 6.1-6.4) + +### Task 6.1: Update code documentation +**File**: All modified files +**Priority**: P2 - Medium +**Estimate**: 2h +**Description**: Update doc comments to reference LoL stats instead of football attributes. + +**Acceptance Criteria**: +- [ ] All doc comments use LoL stat names +- [ ] Function documentation updated +- [ ] Module documentation reflects changes + +--- + +### Task 6.2: Update README or developer docs +**File**: `docs/` or README files +**Priority**: P3 - Low +**Estimate**: 1h +**Description**: Document the attribute system for developers. + +**Acceptance Criteria**: +- [ ] Attribute system documented +- [ ] Migration guide for developers +- [ ] Trait conditions documented + +--- + +### Task 6.3: Remove dead code +**File**: Throughout codebase +**Priority**: P2 - Medium +**Estimate**: 1h +**Description**: Delete commented-out code and unused imports. + +**Acceptance Criteria**: +- [ ] No commented legacy attribute code +- [ ] Unused imports removed +- [ ] Clippy warnings resolved + +--- + +### Task 6.4: Final code review preparation +**File**: All modified files +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Prepare for code review with clean commits and documentation. + +**Acceptance Criteria**: +- [ ] Commits organized by phase +- [ ] No debugging code or print statements +- [ ] CHANGELOG updated + +--- + +## Phase 7: Manual QA & Verification (Tasks 7.1-7.4) + +### Task 7.1: Manual save file migration test +**Priority**: P0 - Blocking +**Estimate**: 2h +**Description**: Test migration with real save files from production. + +**Acceptance Criteria**: +- [ ] Legacy save loads without errors +- [ ] Player stats look reasonable after migration +- [ ] OVR values consistent +- [ ] Save in new format loads correctly + +--- + +### Task 7.2: UI/UX verification +**Priority**: P1 - High +**Estimate**: 2h +**Description**: Manual testing of all UI components showing player attributes. + +**Acceptance Criteria**: +- [ ] Player profile shows 9 LoL stats correctly +- [ ] Training UI displays correct stat names +- [ ] Scouting reports show LoL stats +- [ ] Tooltips explain each stat + +--- + +### Task 7.3: Gameplay verification +**Priority**: P1 - High +**Estimate**: 3h +**Description**: Play through game features to verify stat usage. + +**Acceptance Criteria**: +- [ ] Player generation creates reasonable stats +- [ ] Training improves stats as expected +- [ ] Scouting reveals stats correctly +- [ ] OVR calculation feels balanced +- [ ] Traits are assigned appropriately + +--- + +### Task 7.4: Regression testing +**Priority**: P1 - High +**Estimate**: 3h +**Description**: Test unrelated features to ensure no regressions. + +**Acceptance Criteria**: +- [ ] Matches simulate correctly +- [ ] Transfers work +- [ ] Contracts and wages calculated properly +- [ ] Save/load cycle works + +--- + +## Summary + +| Phase | Tasks | Total Estimate | +|-------|-------|----------------| +| Phase 1: Foundation | 8 | 18h | +| Phase 2: Training | 5 | 8h | +| Phase 3: Scouting | 4 | 6h | +| Phase 4: Frontend | 6 | 10h | +| Phase 5: Testing | 8 | 17h | +| Phase 6: Documentation | 4 | 6h | +| Phase 7: QA | 4 | 10h | +| **Total** | **39** | **75h** | + +## Task Dependencies + +``` +Phase 1 (Foundation) +├── Task 1.1 (struct update) ──┬──► Task 1.2 (deserializer) +│ └──► Task 1.3 (ovr calc) +├── Task 1.4 (remove fn) ──────► Task 1.5 (update fn) +├── Task 1.6 (traits) +└── Task 1.8 (migration) + +Phase 2 (Training) ──► Phase 1 complete +Phase 3 (Scouting) ──► Phase 1 complete +Phase 4 (Frontend) ──► Phase 1 complete +Phase 5 (Testing) ───► Phases 1-4 complete +Phase 6 (Docs) ──────► Phase 5 complete +Phase 7 (QA) ────────► All phases complete +``` + +## Notes + +- **Critical Path**: Tasks 1.1 → 1.2 → 1.3 → 5.1 → 5.4 → 5.8 → 7.1 +- **Parallel Work**: Training (Phase 2), Scouting (Phase 3), and Frontend (Phase 4) can be worked on simultaneously after Phase 1 +- **Risk Areas**: + - Custom deserializer (Task 1.2) - most complex piece + - Frontend type updates (Task 4.1) - affects many files + - Save migration (Tasks 1.8, 5.5, 7.1) - data integrity critical diff --git a/src-tauri/crates/domain/src/player.rs b/src-tauri/crates/domain/src/player.rs index 06ce2608e..917df0771 100644 --- a/src-tauri/crates/domain/src/player.rs +++ b/src-tauri/crates/domain/src/player.rs @@ -147,42 +147,189 @@ pub enum Footedness { Both, } -#[derive(Debug, Clone, Serialize, Deserialize)] +/// Player attributes for League of Legends themed manager. +/// Replaces 19 football-specific attributes with 9 LoL stats. +/// Uses serde aliases for backward compatibility with legacy save files. +#[derive(Debug, Clone, Serialize)] pub struct PlayerAttributes { - // Physical - pub pace: u8, - pub stamina: u8, - pub strength: u8, - #[serde(default = "default_attr")] - pub agility: u8, + /// Technical skill and champion execution (formerly dribbling) + #[serde(alias = "dribbling", default = "default_attr")] + pub mechanics: u8, - // Technical - pub passing: u8, - pub shooting: u8, - pub tackling: u8, - pub dribbling: u8, - pub defending: u8, + /// 1v1 and 2v2 lane phase performance (formerly shooting) + #[serde(alias = "shooting", default = "default_attr")] + pub laning: u8, - // Mental - pub positioning: u8, - pub vision: u8, - pub decisions: u8, - #[serde(default = "default_attr")] - pub composure: u8, - #[serde(default = "default_attr")] - pub aggression: u8, - #[serde(default = "default_attr")] - pub teamwork: u8, - #[serde(default = "default_attr")] - pub leadership: u8, + /// Coordination in 5v5 engagements (formerly teamwork) + #[serde(alias = "teamwork", default = "default_attr")] + pub teamfighting: u8, - // Goalkeeper - #[serde(default = "default_attr")] - pub handling: u8, - #[serde(default = "default_attr")] - pub reflexes: u8, - #[serde(default = "default_attr")] - pub aerial: u8, + /// Map awareness and objective control (formerly vision) + #[serde(alias = "vision", default = "default_attr")] + pub macro_play: u8, + + /// Performance stability across games (formerly decisions) + #[serde(alias = "decisions", default = "default_attr")] + pub consistency: u8, + + /// In-game leadership and calls (formerly leadership) + #[serde(alias = "leadership", default = "default_attr")] + pub shotcalling: u8, + + /// Champion versatility and mastery (formerly agility) + #[serde(alias = "agility", default = "default_attr")] + pub champion_pool: u8, + + /// Focus and tilt resistance (formerly composure) + #[serde(alias = "composure", default = "default_attr")] + pub discipline: u8, + + /// Pressure handling and recovery (formerly stamina) + #[serde(alias = "stamina", default = "default_attr")] + pub mental_resilience: u8, +} + +/// Legacy 19-field attribute structure for backward compatibility deserialization +#[derive(Debug, Clone, Deserialize)] +struct LegacyAttributes { + // Physical (4) + #[serde(default)] + pace: Option, + stamina: Option, + strength: Option, + agility: Option, + + // Technical (5) + passing: Option, + shooting: Option, + tackling: Option, + dribbling: Option, + defending: Option, + + // Mental (7) + positioning: Option, + vision: Option, + decisions: Option, + composure: Option, + aggression: Option, + teamwork: Option, + leadership: Option, + + // Goalkeeper (3) + handling: Option, + reflexes: Option, + aerial: Option, + + // New LoL fields (for forward compatibility) + mechanics: Option, + laning: Option, + teamfighting: Option, + macro_play: Option, + consistency: Option, + shotcalling: Option, + champion_pool: Option, + discipline: Option, + mental_resilience: Option, +} + +impl<'de> serde::Deserialize<'de> for PlayerAttributes { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let legacy = LegacyAttributes::deserialize(deserializer)?; + + // If new format present (all 9 fields), use directly + if let ( + Some(m), + Some(l), + Some(t), + Some(mp), + Some(c), + Some(s), + Some(cp), + Some(d), + Some(mr), + ) = ( + legacy.mechanics, + legacy.laning, + legacy.teamfighting, + legacy.macro_play, + legacy.consistency, + legacy.shotcalling, + legacy.champion_pool, + legacy.discipline, + legacy.mental_resilience, + ) { + return Ok(PlayerAttributes { + mechanics: m, + laning: l, + teamfighting: t, + macro_play: mp, + consistency: c, + shotcalling: s, + champion_pool: cp, + discipline: d, + mental_resilience: mr, + }); + } + + // Otherwise, map from legacy format + // Mapping table from design.md: + // pace + dribbling -> mechanics + // shooting -> laning + // teamwork -> teamfighting + // vision -> macro_play + // decisions -> consistency + // leadership -> shotcalling + // agility -> champion_pool + // composure -> discipline + // stamina -> mental_resilience + + let mechanics = match (legacy.pace, legacy.dribbling) { + (Some(p), Some(d)) => (p + d) / 2, + (Some(p), None) => p, + (None, Some(d)) => d, + (None, None) => 50, + }; + + let laning = legacy.shooting.unwrap_or(50); + let teamfighting = legacy.teamwork.unwrap_or(50); + let macro_play = legacy.vision.unwrap_or(50); + let consistency = legacy.decisions.unwrap_or(50); + let shotcalling = legacy.leadership.unwrap_or(50); + let champion_pool = legacy.agility.unwrap_or(50); + let discipline = legacy.composure.unwrap_or(50); + let mental_resilience = legacy.stamina.unwrap_or(50); + + Ok(PlayerAttributes { + mechanics, + laning, + teamfighting, + macro_play, + consistency, + shotcalling, + champion_pool, + discipline, + mental_resilience, + }) + } +} + +impl Default for PlayerAttributes { + fn default() -> Self { + PlayerAttributes { + mechanics: 50, + laning: 50, + teamfighting: 50, + macro_play: 50, + consistency: 50, + shotcalling: 50, + champion_pool: 50, + discipline: 50, + mental_resilience: 50, + } + } } fn default_attr() -> u8 { @@ -429,77 +576,107 @@ pub enum PlayerTrait { SetPieceSpecialist, // passing >= 80 && shooting >= 75 && vision >= 75 } -/// Derive traits purely from a player's attributes (position-independent). +/// Derive traits purely from a player's LoL attributes. +/// Maps from football attribute conditions to LoL stats: +/// - Speedster: pace >= 85 -> mechanics >= 85 +/// - Tank: strength >= 85 && stamina >= 75 -> teamfighting >= 85 && mental_resilience >= 75 +/// - Agile: agility >= 85 -> champion_pool >= 85 +/// - Tireless: stamina >= 90 -> mental_resilience >= 90 +/// - Playmaker: passing >= 80 && vision >= 80 -> macro_play >= 80 && shotcalling >= 80 +/// - Sharpshooter: shooting >= 85 -> laning >= 85 +/// - Dribbler: dribbling >= 85 -> mechanics >= 85 +/// - BallWinner: tackling >= 80 && aggression >= 70 -> discipline >= 80 && teamfighting >= 70 +/// - Rock: defending >= 85 && positioning >= 75 -> teamfighting >= 85 && macro_play >= 75 +/// - Leader: leadership >= 85 && teamwork >= 75 -> shotcalling >= 85 && teamfighting >= 75 +/// - CoolHead: composure >= 85 && decisions >= 80 -> discipline >= 85 && consistency >= 80 +/// - Visionary: vision >= 85 -> macro_play >= 85 +/// - HotHead: aggression >= 85 && composure < 50 -> low discipline, high teamfighting +/// - TeamPlayer: teamwork >= 85 -> teamfighting >= 85 +/// - CompleteForward: shooting >= 75 && dribbling >= 75 && pace >= 70 && strength >= 70 -> mechanics >= 75 && laning >= 75 && champion_pool >= 70 +/// - Engine: stamina >= 85 && pace >= 70 && teamwork >= 75 -> mental_resilience >= 85 && mechanics >= 70 && teamfighting >= 75 +/// - SetPieceSpecialist: passing >= 80 && shooting >= 75 && vision >= 75 -> macro_play >= 80 && laning >= 75 && shotcalling >= 75 pub fn compute_traits(attrs: &PlayerAttributes, _position: &Position) -> Vec { let mut traits = Vec::new(); - // Physical - if attrs.pace >= 85 { + // Mechanical stats + if attrs.mechanics >= 85 { traits.push(PlayerTrait::Speedster); + traits.push(PlayerTrait::Dribbler); } - if attrs.strength >= 85 && attrs.stamina >= 75 { + + // Teamfighting + Mental Resilience -> Tank + if attrs.teamfighting >= 85 && attrs.mental_resilience >= 75 { traits.push(PlayerTrait::Tank); } - if attrs.agility >= 85 { + + // Champion Pool -> Agile + if attrs.champion_pool >= 85 { traits.push(PlayerTrait::Agile); } - if attrs.stamina >= 90 { + + // Mental Resilience -> Tireless + if attrs.mental_resilience >= 90 { traits.push(PlayerTrait::Tireless); } - // Technical - if attrs.passing >= 80 && attrs.vision >= 80 { + // Macro Play + Shotcalling -> Playmaker + if attrs.macro_play >= 80 && attrs.shotcalling >= 80 { traits.push(PlayerTrait::Playmaker); } - if attrs.shooting >= 85 { + + // Laning -> Sharpshooter + if attrs.laning >= 85 { traits.push(PlayerTrait::Sharpshooter); } - if attrs.dribbling >= 85 { - traits.push(PlayerTrait::Dribbler); - } - if attrs.tackling >= 80 && attrs.aggression >= 70 { + + // Discipline + Teamfighting -> BallWinner + if attrs.discipline >= 80 && attrs.teamfighting >= 70 { traits.push(PlayerTrait::BallWinner); } - if attrs.defending >= 85 && attrs.positioning >= 75 { + + // Teamfighting + Macro Play -> Rock + if attrs.teamfighting >= 85 && attrs.macro_play >= 75 { traits.push(PlayerTrait::Rock); } - // Mental - if attrs.leadership >= 85 && attrs.teamwork >= 75 { + // Shotcalling + Teamfighting -> Leader + if attrs.shotcalling >= 85 && attrs.teamfighting >= 75 { traits.push(PlayerTrait::Leader); } - if attrs.composure >= 85 && attrs.decisions >= 80 { + + // Discipline + Consistency -> CoolHead + if attrs.discipline >= 85 && attrs.consistency >= 80 { traits.push(PlayerTrait::CoolHead); } - if attrs.vision >= 85 { + + // Macro Play -> Visionary + if attrs.macro_play >= 85 { traits.push(PlayerTrait::Visionary); } - if attrs.aggression >= 85 && attrs.composure < 50 { + + // HotHead: High teamfighting + low discipline + if attrs.teamfighting >= 75 && attrs.discipline < 50 { traits.push(PlayerTrait::HotHead); } - if attrs.teamwork >= 85 { - traits.push(PlayerTrait::TeamPlayer); - } - // Goalkeeper-oriented (any player with high GK stats can earn these) - if attrs.handling >= 85 { - traits.push(PlayerTrait::SafeHands); - } - if attrs.reflexes >= 85 { - traits.push(PlayerTrait::CatReflexes); - } - if attrs.aerial >= 85 { - traits.push(PlayerTrait::AerialDominance); + // Teamfighting -> TeamPlayer + if attrs.teamfighting >= 85 { + traits.push(PlayerTrait::TeamPlayer); } - // Combo / Special — purely attribute-based - if attrs.shooting >= 75 && attrs.dribbling >= 75 && attrs.pace >= 70 && attrs.strength >= 70 { + // Combo traits + // CompleteForward: mechanics >= 75 && laning >= 75 && champion_pool >= 70 + if attrs.mechanics >= 75 && attrs.laning >= 75 && attrs.champion_pool >= 70 { traits.push(PlayerTrait::CompleteForward); } - if attrs.stamina >= 85 && attrs.pace >= 70 && attrs.teamwork >= 75 { + + // Engine: mental_resilience >= 85 && mechanics >= 70 && teamfighting >= 75 + if attrs.mental_resilience >= 85 && attrs.mechanics >= 70 && attrs.teamfighting >= 75 { traits.push(PlayerTrait::Engine); } - if attrs.passing >= 80 && attrs.shooting >= 75 && attrs.vision >= 75 { + + // SetPieceSpecialist: macro_play >= 80 && laning >= 75 && shotcalling >= 75 + if attrs.macro_play >= 80 && attrs.laning >= 75 && attrs.shotcalling >= 75 { traits.push(PlayerTrait::SetPieceSpecialist); } @@ -566,25 +743,15 @@ mod tests { fn sample_attributes() -> PlayerAttributes { PlayerAttributes { - pace: 70, - stamina: 72, - strength: 65, - agility: 68, - passing: 74, - shooting: 61, - tackling: 58, - dribbling: 69, - defending: 56, - positioning: 67, - vision: 73, - decisions: 71, - composure: 66, - aggression: 54, - teamwork: 76, - leadership: 49, - handling: 20, - reflexes: 24, - aerial: 44, + mechanics: 70, + laning: 72, + teamfighting: 65, + macro_play: 68, + consistency: 74, + shotcalling: 61, + champion_pool: 58, + discipline: 69, + mental_resilience: 56, } } diff --git a/src-tauri/crates/ofm_core/src/potential.rs b/src-tauri/crates/ofm_core/src/potential.rs index e9546d50d..0edc87d27 100644 --- a/src-tauri/crates/ofm_core/src/potential.rs +++ b/src-tauri/crates/ofm_core/src/potential.rs @@ -166,19 +166,22 @@ pub fn effective_potential_cap(player: &Player) -> u8 { .min(99) } +/// Calculate overall rating directly from LoL stats. +/// Averages all 9 LoL stats: mechanics, laning, teamfighting, macro_play, +/// consistency, shotcalling, champion_pool, discipline, mental_resilience pub fn calculate_lol_ovr(player: &Player) -> u8 { let attrs = &player.attributes; - let avg = (attrs.dribbling as f64 - + attrs.shooting as f64 - + attrs.teamwork as f64 - + attrs.vision as f64 - + attrs.decisions as f64 - + attrs.leadership as f64 - + attrs.agility as f64 - + attrs.composure as f64 - + attrs.stamina as f64) + let avg = (attrs.mechanics as f64 + + attrs.laning as f64 + + attrs.teamfighting as f64 + + attrs.macro_play as f64 + + attrs.consistency as f64 + + attrs.shotcalling as f64 + + attrs.champion_pool as f64 + + attrs.discipline as f64 + + attrs.mental_resilience as f64) / 9.0; - avg.round().clamp(1.0, 99.0) as u8 + avg.round().clamp(25.0, 99.0) as u8 } fn compute_revealed_potential(player: &Player, team_average_morale: u8) -> u8 { @@ -233,25 +236,15 @@ mod tests { fn attrs(stat: u8) -> PlayerAttributes { PlayerAttributes { - pace: stat, - stamina: stat, - strength: stat, - agility: stat, - passing: stat, - shooting: stat, - tackling: stat, - dribbling: stat, - defending: stat, - positioning: stat, - vision: stat, - decisions: stat, - composure: stat, - aggression: stat, - teamwork: stat, - leadership: stat, - handling: stat, - reflexes: stat, - aerial: stat, + mechanics: stat, + laning: stat, + teamfighting: stat, + macro_play: stat, + consistency: stat, + shotcalling: stat, + champion_pool: stat, + discipline: stat, + mental_resilience: stat, } } diff --git a/src-tauri/src/commands/game.rs b/src-tauri/src/commands/game.rs index cad96a3cd..a0ec4a593 100644 --- a/src-tauri/src/commands/game.rs +++ b/src-tauri/src/commands/game.rs @@ -538,7 +538,7 @@ pub(crate) fn bootstrap_example_academy_pool_from_example( ), }; - let attributes = build_attributes_from_seed(&seed); + let attributes = build_lol_stats_from_seed(&seed); let position = role_to_position(seed.role.as_deref()); let player_id = format!("{}-player-{}", academy_id, player_index + 1); @@ -1571,7 +1571,9 @@ fn clamp_stat(value: i16) -> u8 { value.clamp(25, 99) as u8 } -fn build_lol_stats_from_seed(seed: &DraftPlayerSeed) -> [u8; 9] { +/// Build PlayerAttributes directly from seed data. +/// Returns PlayerAttributes with 9 LoL stats instead of [u8; 9]. +fn build_lol_stats_from_seed(seed: &DraftPlayerSeed) -> PlayerAttributes { let target = i16::from(seed.rating.unwrap_or(60).clamp(45, 95)); let role_key = normalize_seed_name(seed.role.as_deref().unwrap_or("")); let role_bias: [i16; 9] = match role_key.as_str() { @@ -1606,57 +1608,16 @@ fn build_lol_stats_from_seed(seed: &DraftPlayerSeed) -> [u8; 9] { cursor = (cursor + 1) % 9; } - [ - clamp_stat(values[0]), - clamp_stat(values[1]), - clamp_stat(values[2]), - clamp_stat(values[3]), - clamp_stat(values[4]), - clamp_stat(values[5]), - clamp_stat(values[6]), - clamp_stat(values[7]), - clamp_stat(values[8]), - ] -} - -fn build_attributes_from_seed(seed: &DraftPlayerSeed) -> PlayerAttributes { - let [mechanics, laning, teamfighting, macro_play, consistency, shotcalling, champion_pool, discipline, mental_resilience] = - build_lol_stats_from_seed(seed); - - let role_key = normalize_seed_name(seed.role.as_deref().unwrap_or("")); - - let defending = if role_key == "top" || role_key == "support" { - clamp_stat(((i16::from(teamfighting) + i16::from(discipline)) / 2) + 4) - } else { - clamp_stat((i16::from(teamfighting) + i16::from(discipline)) / 2) - }; - PlayerAttributes { - pace: clamp_stat((i16::from(mechanics) + i16::from(laning)) / 2), - stamina: mental_resilience, - strength: clamp_stat((i16::from(teamfighting) + i16::from(discipline)) / 2), - agility: champion_pool, - passing: clamp_stat((i16::from(macro_play) + i16::from(shotcalling)) / 2), - shooting: laning, - tackling: clamp_stat((i16::from(discipline) + i16::from(teamfighting)) / 2), - dribbling: mechanics, - defending, - positioning: clamp_stat((i16::from(macro_play) + i16::from(consistency)) / 2), - vision: macro_play, - decisions: consistency, - composure: discipline, - aggression: clamp_stat((i16::from(teamfighting) + i16::from(mental_resilience)) / 2 - 4), - teamwork: teamfighting, - leadership: shotcalling, - handling: 20, - reflexes: 22, - aerial: if role_key == "top" { - 68 - } else if role_key == "support" { - 64 - } else { - 52 - }, + mechanics: clamp_stat(values[0]), + laning: clamp_stat(values[1]), + teamfighting: clamp_stat(values[2]), + macro_play: clamp_stat(values[3]), + consistency: clamp_stat(values[4]), + shotcalling: clamp_stat(values[5]), + champion_pool: clamp_stat(values[6]), + discipline: clamp_stat(values[7]), + mental_resilience: clamp_stat(values[8]), } } @@ -1677,7 +1638,7 @@ fn build_free_agent_player(seed: &DraftPlayerSeed, index: usize) -> Option