Claude/feat manual overrides - #2
Open
nickmatteo wants to merge 42 commits into
Open
Conversation
Folds in dev-branch work across tracking (BattleStateFacade,
StatTracker, PokemonRegistry, StateUpdater), reveal parsing (new
BattleMoveSupport, BoostTraceLog, DeltaClientBattleMessageQueueMixin),
calc support (CalcBattleAnalysisSupport, CalcPreviewSelectionState),
and calc panel UI polish (DamageCalcPanel, CalcPanelState).
Removes the Codex battle export feature (no longer needed):
- Deletes src/main/kotlin/com/cobblemonextendedbattleui/export/
- Drops battleExport* fields, setters, and ConfigData entries from
PanelConfig (plus the unused java.nio.file.Path import)
- Removes the Codex Export category + 5 toggles from
ModMenuIntegration, and the related lang keys
- Drops the BattleExportService.tick() client-tick registration in
CobblemonExtendedBattleUIClient
- Deletes the orphaned BattleExportIOTest
Adds move data + damage-calc improvements from the hungry-snyder Claude session: +143 lines on DamageEngine.kt plus supporting MoveDefinition / MoveDex / SimpleDamageCalculator tweaks.
- DB-first type resolution with mismatch log (CalcBattleSnapshotFactory) - Showdown-derived move flag DB (MoveFlagDatabase + build_move_flags.py) - Offensive abilities: Tough Claws, Strong Jaw, Iron Fist, Mega Launcher, Sharpness, Punk Rock, Reckless, Sheer Force, Technician, Tinted Lens, Water Bubble, Steelworker, Dragon's Maw, Transistor, Rocky Payload, pixelate family (Aerilate/Pixilate/Refrigerate/Galvanize) - Defensive abilities: Water Bubble, Dry Skin fire weakness, Fluffy, Ice Scales, Multiscale/Shadow Shield, Punk Rock sound, Purifying Salt - Mold Breaker / Turboblaze / Teravolt bypass; Wonder Guard - Items: Expert Belt, Muscle Band, Wise Glasses - Split-category moves: Photon Geyser / LTBTS / Tera Blast pick phys vs spec by effective stats; Sunsteel Strike / Moongeist Beam / LTBTS / Photon Geyser bypass defender ability - Foul Play uses defender's Atk/stages/item - Fixed-damage moves (Seismic Toss, Night Shade, Dragon Rage, Sonic Boom, Super Fang, Endeavor, Final Gambit, Psywave) - Variable-BP moves: Facade, Venoshock, Brine, Acrobatics, Stored Power, Punishment, Electro Ball, Gyro Ball, Eruption/Water Spout/Dragon Energy, Low Kick/Grass Knot/Heavy Slam/Heat Crash (+ Showdown weightkg pipeline) - Crit: 1.5x (2.25x Sniper) with screen bypass and stat-stage clamping - 16-roll distribution + multi-hit hit-count propagation - UI: Damage Calc Options tab (showCritDamage, showMultiHitCount, debugDumpEnabled), DebugDumper writes latest-calc.json - DB coverage: heuristic base-stat spreads + delta-curated-sets.json plumbing; ranked refresh pipeline (weightKg, stronger aliases, delta: namespace fix) - Screen-heuristic removal: CobblemonClient.battle drives battle detection
# Conflicts: # src/main/kotlin/com/cobblemonextendedbattleui/calc/DamageEngine.kt
# Conflicts: # src/main/kotlin/com/cobblemonextendedbattleui/calc/CalcComputationService.kt # src/main/kotlin/com/cobblemonextendedbattleui/calc/DamageEngine.kt
Adapt the V2 mockup styling onto DamageCalcPanel while preserving every existing feature (collapse toggles, team chip click-to-preview, drag, resize, font scale). DamageCalcPanel - Header gets a green live-status dot and neutral title color. - New matchup row at the top: YOU / OPP columns with mon name, HP bar, HP%, and a small speed indicator (>/</=) between the columns. Compact mode collapses to a single line. - Move rows show a damage-vs-HP forecast bar under the name: solid HP-colored block for guaranteed remaining HP, severity-colored block for HP at risk. Status moves and missing data skip the bar. - Move-section labels: YOU - OPPONENT (green) / OPPONENT - YOU (red). - Item / Ability rows render SEEN / LIKELY / ? pills in place of the [R] / [G] text tags. - Team chips moved to the bottom of the panel with a subtle divider. Chips pack into one row each side via a min-tab-width guard, show a 2px HP fill bar at the chip's bottom, and add a strikethrough on fainted mons. Names use full species labels truncated by chip width with an ellipsis instead of a hard 5-char cap. - Predicted-set summary block gets a thin 1px outline. - Section headers and sub-labels switch to muted label-gray for the V2 flat look. Default panel height bumps to 310 to fit new sections; width stays at 228. Data - Extend CalcMoveRow with minPercent / maxPercent / isStatus, populated by toRow() from the existing DamageEstimate fields. No other call sites construct CalcMoveRow. - tabLabel returns the full species label - the UI now decides how much fits. Bug fixes (Gson interaction with Kotlin) - Drop the unused MoveFlagEntry.accuracy: Int field. The bundled move-flags JSON encodes "always hit" moves as accuracy: true, which Gson cannot deserialize into Int. - Replace MoveFlagEntry.flagSet's `by lazy` delegate with a computed property. Gson bypasses the constructor on deserialization, leaving the Lazy backing field null and tripping an NPE on first hasFlag() call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Damage Calc Text Scale previously only shrank glyphs; row spacing, column offsets, and forecast/HP bars stayed at full size, so small screens couldn't fit the 4th opponent move even at minimum scale. Render now derives a per-frame uiScale = CalcPanelState.fontScale and applies it through a local s(n) helper to every textY increment, the move row height, header click bounds, and the right-edge state-pill offset. Helper functions (drawMatchupSection, drawMoveRow, drawTabs, tabBlockHeight, drawHpBar, drawDamageBar, drawStatePill) accept uiScale and scale their own bar heights, tab dimensions, label insets, and pill padding with sensible floors so elements stay clickable and readable at 0.35x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Old default y=18 overlapped Cobblemon Chat Plus's chat region. Only affects users with no saved CalcPanelState.y; existing positions are preserved. README now notes the panel is draggable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
confidenceFor previously returned MEDIUM for any inferred-value warning, which read as confident even on OHKO predictions where an unrevealed defensive item (Assault Vest, Eviolite, Heavy-Duty Boots, Leftovers, Multiscale, etc.) could swing the result. When koLabel is "OHKO" or "Likely OHKO" and warnings include any "Inferred" entry, drop to LOW. Non-OHKO labels keep MEDIUM behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
White Herb's contract is to reset negative stat stages and consume itself; previously the consume was tracked but stages persisted. StatTracker.clearNegativeStages zeros only negative entries (positives preserved), and StateUpdater.extractItemConsumed calls into it when the consumed item normalizes to "whiteherb". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the BattleStateTracker hasn't yet confirmed a species, the truth fell back to properties.species — which already encodes regional variants (e.g. "moltres-galar") and so flagged every Kantonian Moltres as Galarian until the tracker caught up. Split a closed list of regional suffixes (-galar, -alola, -hisui, -paldea) off the base id and route them into the form slot only if nothing else has named one. canonicalSpeciesKey re-attaches the suffix once the variant is real, so legitimate Galarian Moltres still resolves as moltres-galar. Pokemon.toTruth (party path) was already correct via species.resourceIdentifier.path and is left unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Embody Aspect grants a passive +1 to the stat tied to Ogerpon's mask (Hearthflame Atk, Wellspring SpD, Cornerstone Def, Teal Spe), but Cobblemon doesn't emit a separate boost message for it. Hook the ability-reveal branches (ABILITY_GENERIC_KEY and ABILITY_SINGLE_ARG_KEYS) and apply the boost via StatTracker, gated strictly on species starting with "ogerpon" so Trace/Receiver copying doesn't trigger it. Dedupe against re-emit by checking the slot is below +1 before applying; clearPokemonState zeros stages on switch-out so a fresh switch-in always sees 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regenerator triggers on the OUTGOING Pokemon at switch-out, but Cobblemon does not emit a heal message for it; the calc treated regen-switchers as still being at low HP. Inspect packet.getPnx() at HEAD inject to resolve the slot's active Pokemon (still the outgoing one before the switch is applied), and bump DamageTracker by min(100, current + 33.33) when the revealed ability is Regenerator. Gated on >0% so KO-forced switches (which fire FAINT before SENDOUT) do not pre-bump. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cobblemon's PokemonProperties.species is String? in this version, so the helper has to handle the null case rather than push the burden to the single caller. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ontext + debug-dump phase signals BattleStateFacade - Ogerpon mask-driven form detection: derive form from held mask item when no form-change message fires. - resolveCurrentForm: when FormTracker has a recent form change (Stance Change / Mega / Ogerpon mask), the actor's `form` reference often still points at the original; resolve via aspect lookup so baseStats and types reflect the post-change form. - Form-name precedence: tracker > Ogerpon mask > properties.form > regional aspect (regional last so any stronger signal wins). DamageEngine - DamageContext now carries attackerTeamFainted to scale moves like Last Respects. - moveTypeChartMultiplier replaces the raw type-chart lookup so move type tags can override effectiveness where needed. - Multi-hit damage roll handling tightened. BattleMessageInterceptor + DebugDumper + MessageParser - End-of-move-phase signals (faint / switch / drag / sendout / replace / turn) reset DebugDumper's hit aggregator and call MessageParser.markMoveResolved so post-move passive damage (Toxic, Leech Seed, hazards) doesn't get attributed to the prior move. - hasMegaForm guards cobblemon.battle.formechange.mega / cobblemon.battle.mega messages so spurious mega messages on non-mega species (e.g. Iron Valiant) don't corrupt form tracking. Plus tooling, generated DB refresh, and other small adjustments. Conflicts vs. the 6 v1.1 fixes (B2 in BattleStateFacade.kt and B4 in BattleMessageInterceptor.kt) were resolved by combining intents. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…read Adds inline manual override controls to the calc panel summary block. Left-click any of the three rows to cycle through the next alternative from usage stats (top 5 deduped); right-click resets the row to the inferred default. A `>` glyph on the row signals it's cycleable; a `*` suffix on the value signals an active override. State pill turns green because overrides are tagged REVEALED so DamageEngine.confidenceFor treats them as ground truth. Implementation: - EffectiveBattleSet gains itemAlternatives / abilityAlternatives / spreadAlternatives lists, populated from BattleSetEntry usage data in OpponentInferenceService (top 5 distinct). - CalcComputationService holds an ephemeral MutableMap<UUID, OpponentOverride> keyed per opponent UUID; cleared when no battle is active. - applyOverride merges overrides on top of the inferred set right after inference returns. Real reveals (battle-log itemName / abilityName) always beat overrides. - DamageCalcPanel captures per-row click bounds during render, polls GLFW left + right mouse buttons, and uses the existing deferred arm-on-mousedown / fire-on-mouseup pattern for cycle clicks. Right- click is a rising-edge poll for reset. - Cycle wraps no-override → 1 → 2 → ... → (size-1) → no-override, so the "*" indicator disappears when the user lands back on the inferred default. Out of scope (deferred): full 6-EV editor with sum cap and nature dropdown; persistence across battles or sessions; popup-menu UX as an alternative to cycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ompact User-feedback follow-up to the cycle-through overrides: - Add a `<` glyph next to the existing `>` glyph on each cycleable row; click `<` cycles backward through alternatives, click anywhere else on the row (including `>`) cycles forward as before. cycle() in CalcComputationService now takes a direction (+1/-1) and floor-mods the index, so wrap-around works in both directions. - Add a distinct "MANUAL" pill (blue) shown when a row has an active override, replacing the SEEN/LIKELY/UNKNOWN pill. Removes the visual abruptness of LIKELY (yellow) flipping to SEEN (green) on every cycle and makes the override status legible at a glance. - Surface the Spread row even in compact mode so it can still be overridden; only the source label below stays hidden when compact. Click-target geometry: back zone is a small ~8px rect around the `<` glyph just left of the pill; forward stays "the rest of the row" so the original UX is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups from in-game testing:
1. Mega-stone item -> mega form: when the effective opponent item is a
mega stone (Charizardite X/Y, Mewtwonite X/Y, Garchompite, ~50 total),
swap the opponent snapshot to that mega form for damage / speed /
types. New helper `applyMegaFormSwap` looks up the form via Cobblemon's
PokemonSpecies API (same pattern as BattleMessageInterceptor.hasMegaForm)
and produces a transformed CalcPokemonSnapshot with the mega form's
base stats and types. The mega form's intrinsic ability is slotted
into EffectiveBattleSet.ability when the user hasn't overridden ability.
Falls back to the original snapshot when the species has no matching
mega form (e.g. delta or non-mega-able species).
2. Speed line in compact mode: model.speedText was previously gated
behind `if (!compact)` and only rendered in full layout. Removed the
gate so the labeled speed comparison ("Speed: You likely move first
(X vs Y)") is visible in compact mode too. Speed updates correctly
when overrides change spread / item / ability since buildSpeedText
already reads from effectiveSet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mmon items User-feedback follow-up: on mons with thin usage data (Ferrothorn was the example), the cycle had effectively one option per row. Two changes: - Abilities: merge usage-stat ranking with the species' full legal ability set from Cobblemon's PokemonSpecies API. Mons that almost always run one ability now still expose their alternates (Ferrothorn -> Iron Barbs / Anticipation; mons with hidden abilities expose them too). Cap raised from 5 to 6 to make room for HA slots. - Items: append a curated 12-item list of common competitive items (Leftovers, Life Orb, Heavy-Duty Boots, Choice trio, Assault Vest, Focus Sash, Rocky Helmet, Eviolite, Air Balloon, Black Sludge) to the usage alternatives, deduped. Cap raised from 5 to 8 so usage defaults still lead but every mon gets several cycle options. Both expansions are species-aware via PokemonSpecies.getByIdentifier with the same defensive runCatching pattern used by the mega-form lookup, so delta / custom species fall back to usage-only when species data isn't available. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-feedback follow-up: - Surface usage percentages on Item / Ability / Spread rows when the current value comes from usage data. Renders as " (45%)" suffix before the override "*" tag. Curated common-items and species-derived ability fallbacks have no usage data and render without the suffix. - Add a MANUAL pill to the Spread row (matches Item / Ability rows) so override state is consistent across all three. Pill uses InferenceSpread.state when not overridden (LIKELY for usage-derived, UNKNOWN when no data). Plumbing: - InferenceSpread gains usagePercent field; OpponentInferenceService populates it from usage.spreads. - EffectiveBattleSet gains itemUsagePercent and abilityUsagePercent maps keyed by normalized name; OpponentInferenceService populates them from usage.items / usage.abilities. - DamageCalcPanel renderer looks up the current value's percent via the new maps and formats with a small helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: when font scale grows or moves+team content gets tall, content runs past the panel bottom and gets cut off. Wraps the move predictions + team region in a scissor + matrix-translate viewport with the existing reusable ScrollbarRenderer (4px right-edge track, hover thumb). Implementation: - Adds contentScrollOffset / lastContentHeight / viewport top+bottom fields. Each render frame computes the viewport (panel bottom minus the summary block top), clamps the scroll offset against the cached content height, then pushes a translate(-scrollOffset) matrix. - Click bounds inside the viewport (moves header, team header, player / opponent tab buttons) are captured in screen coords by subtracting the current scrollOffset, so the existing handleInput tests work against bare mouseY without scroll-aware adjustments. - onScroll: Ctrl+wheel still adjusts font scale; bare wheel scrolls the viewport at 12px/tick when content overflows, otherwise yields to other widgets. - Scrollbar early-returns when content fits; no visual change for panels that already fit their content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rides Repro: Ferrothorn with Iron Barbs revealed via contact hit. The cycle arrows still rendered and the MANUAL pill stayed up, but applyOverride silently dropped the override (real reveals beat overrides), so clicking the row never moved the displayed value. User saw "only Iron Barbs and Leftovers" with apparently broken cycling. Two fixes: - applyOverride now purges override.itemIndex / abilityIndex whenever the opponent snapshot reports a real-revealed value. The purge runs on every render, so an override stored before a reveal lands gets cleaned automatically and hasOverride flips back to false. The MANUAL pill clears, the SEEN/LIKELY pill takes over, and the row reads honestly. - Panel render now derives `itemRealRevealed` / `abilityRealRevealed` (state == REVEALED && !overridden) and gates `itemHasAlts` / `abilityHasAlts` on it. Real-revealed rows: arrows hidden, both the back-arrow and full-row click bounds zeroed so click-cycle is a no-op. Spread is never battle-revealed, so unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two reasons the scroll didn't work:
1. onScroll bounds-check was using raw window-pixel coords against
GUI-scale-resolved lastBounds, so the contains() guard never
matched at any GUI scale != 1.0 and the wheel handler returned
false before scrolling. Convert mouseX/mouseY to scaled coords
the same way BattleLogWidget / BattleInfoPanel do.
2. The scrollbar thumb was rendered but had no drag handling. Add a
scrollbar-drag state and three new arms in handleInput:
- Mouse down on thumb: begin drag, save start mouseY + offset.
- Mouse down on empty track: jump-scroll to that position, then
keep dragging so the user can fine-tune without releasing.
- While dragging: ScrollbarRenderer.dragToScrollOffset() converts
mouse-Y delta to scroll offset.
- Mouse up: end drag.
Both arms gate on `lastContentHeight > viewportHeight` so the thumb
isn't draggable when content fits. Also reset isScrollbarDragging in
the render bail-out (battle ended / panel disabled).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The data-pipeline scripts that produce learnsets.generated.json,
delta-auto-sets.generated.json, and delta-movesets.generated.json
were sitting untracked, leaving build_battle_database.py with
imports nobody else could resolve. Track all five.
Also ignore .claude/, .codex_tmp{,_delta}/, battleinfo/, and *.bak
so future commits aren't poisoned by agent/debug-dump artifacts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New species (sourced from the team-builder forum + xlsx scrape): - Iron Sentinel (Water/Flying) - Girashadow (Ghost/Dragon) - Silent Sting (Bug/Dragon) - Apex Tyrant (Fighting/Dragon) - Munchlax-Delta (Normal/Grass) - Terapagos-ATOM (Normal) - Slakoth-ATOM (Normal/Psychic) Iron Sentinel, Girashadow, Silent Sting, and Apex Tyrant ship with xlsx-derived movesets and auto-curated default sets. The other three have stats/types/abilities populated and will pick up movesets when their sheets get posted. The previous battle-database.generated.json shipped with an empty smogonFallback array; this rebuild repopulates 1269 fallback defaults (171 Smogon-scraped, 1098 heuristic baseStats) so non-curated species fall back to a real spread instead of nothing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Discord launch feedback was that new users get blasted by overlapping panes the first time they enter a battle. Default both CalcPanelState.enabled and PanelConfig.enableBattleLog to false; existing users keep their saved visibility because PanelConfig.load() / CalcPanelState.load() read the value from disk before defaults apply, and any panel interaction (drag, resize, toggle) writes the field back. New installs land on a quieter UI and discover the panes via the mod menu / hotkey. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v3 shipped with CalcPanelState.enabled defaulting to false to avoid overwhelming new users with overlapping panes — but the Mod Menu had no toggle for it, leaving fresh installs with the headline feature silently hidden and no UI path to turn it on. Flip the default back to true so new installs see the calc on first battle, and add a Damage Calc Panel toggle to the Mod Menu so users who want a quieter UI can disable it without editing JSON. Existing saved configs keep whatever value users had set. Bump mod_version 0.9.1 -> 0.9.1.1 for the Modrinth hotfix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(v0.9.1.2) vxctor reported "attacker: need actual" / "defender: need actual" on every move row in custom level-cap battles (lvl 50 cap rule on Cobblemon Delta). Server-enforced level rules clone the player's team into temporary battle-only Pokemon at the forced level — those clones have different UUIDs than the originals in CobblemonClient.storage.party, so findByUUID returns null, actualStats stays null, and DamageEngine emits "Need actual stats" as a blocking issue. When partyPokemon is null but baseStats is available, derive a stat block from baseStats + the in-battle level using the same offensive / defensive heuristic spread shape that build_default_delta_sets.py applies on the data-pipeline side (Adamant/Modest 4/252/0/0/0/252, Bold/Calm 252/0/252/0/0/4). 31 IVs across the board, standard Cobblemon stat math. Approximate but within damage-roll variance, and revealed item / ability / move info tightens the rest of the calc as the battle progresses. Also fixes any other custom-rule scenario that re-instantiates the team at battle start (random battle, custom banlists that rebuild the squad, etc.). Bump mod_version 0.9.1.1 -> 0.9.1.2 for the Modrinth hotfix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ade burn (v0.9.1.3) - Parasol Prayer / Delta Stream: clamp SE-vs-Flying to neutral on Flying defenders when either active Pokemon has the ability. - Mega evolution sticky: FormTracker preserves isMega across switch-out so Cobblemon's base-form visual glitch on switch-in no longer drops the calc back to base stats. Player actualStats are re-derived from mega baseStats when isMegaTracked is true to defend against partyPokemon reporting base values in the same glitch state. - Return / Frustration: always 102 BP since the calc has no friendship access; matches max-friendship competitive convention. - BattleDatabase two-pass indexer (primary identifiers first, aliases via putIfAbsent) prevents 303 variant entries from overwriting their base species' lookup key. Fixes Ursaluna defaulting to Bloodmoon, Zapdos / Moltres defaulting to Galar, Decidueye to Hisui, Camerupt to Mega, Basculegion to F, Aegislash to Blade, every typed Arceus, every regional / Gigantamax form. - Facade now bypasses burn's 0.5x physical Atk drop in addition to doubling its BP (previously netted to 1x, wiping the boost). - Guts trigger hardened: requires a recognized status name instead of any non-null status string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- BattleMessageInterceptor: drop GIGANTAMAX_KEY branch. - TranslationKeys: remove GIGANTAMAX_KEY constant. - DamageEngine: drop gmaxdrumsolo / gmaxfireball / gmaxhydrosnipe from the move-bypasses-ability set. Dynamax handling is kept intact — only Gigantamax is removed per project scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a hand-authored Cresselia default to delta-curated-sets.json and bakes
it into battle-database.generated.json. Cresselia is almost exclusively
played on Trick Room teams in Delta — the previous Smogon fallback
(Bold / Lunar Dance / Ice Beam / Thunder Wave / Moonlight) was a defensive
OU set that doesn't match Delta play patterns.
Set:
Ability: Levitate
Nature: Relaxed (Spe-lowering for slow TR)
EVs: 252 HP / 252 Def / 4 SpA
Item: Mental Herb > Leftovers > Colbur Berry
Moves: Trick Room (100%), Moonblast (90%), Psychic (70%),
Lunar Dance (60%), Ice Beam (30%), Moonlight (30%)
The curated entry overrides the Smogon fallback for the same species
(priority: manual curated > auto-curated > ranked > Smogon > heuristic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…a and Sevygarde
Re-scraped the Cobblemon Delta #custom-pokemon forum via the external
scraper (Z:/Cb delta scraper/scrape-new-mons.js + enrich-stubs.js) and
baked the results into the calc database. The previous team-builder
snapshot was from 2026-03-31; this catches everything released since.
New species (types, base stats, weight, movepool, auto-curated default set):
- Trapinch-ATOM Water/Bug, BST 528, Hyper Cutter / Strong Jaw
- Sevygarde Poison/Ground, BST 600, Shed Skin
(Seviper x Zygarde fusion, Season 7 ranked reward)
- Hydrapple-Ultra Poison/Dragon, BST 540, Spoiled Goods / Regenerator
- Dipplin-Ultra Poison/Dragon, BST 485
- Appletun-Ultra Poison/Dragon, BST 485, Thick Fat
- Flapple-Ultra Poison/Dragon, BST 485, Hustle
- Applin-Ultra Poison/Dragon, BST 260, Bulletproof
Curated overrides (delta-curated-sets.json):
- Hydrapple-Ultra: AV Regenerator (244 HP / 252 SpA / 12 Spe, Modest,
Fickle Beam / Sludge Bomb / Earth Power / Giga Drain) per beeg_lad
+ Cob channel consensus. Nasty Plot Regen as 25% alt; Spoiled Goods
20% alt ability.
- Sevygarde: defensive utility (248 HP / 252 SpD / 8 Spe, Careful,
Thousand Arrows / Toxic / Haze / Rest) per Cob's read; banded
Thousand Arrows + ESpeed as 40% alt spread.
Pipeline run:
- scrape-new-mons.js (7 new forum threads discovered + xlsx)
- enrich-stubs.js (parsed stats/types/abilities from
thread starter content into both
team-builder JSONs)
- import_delta_movesets.py (parsed new xlsx -> delta-movesets,
then merged with team-building
workspace's 296-mon snapshot)
- build_learnsets.py (1750 species, 301 xlsx-covered)
- build_default_delta_sets.py (192 auto sets, all 7 new mons covered)
- build_battle_database.py (skipped Smogon scrape; restored
pre-existing fallback data and
injected the 7 new mons + 2 curated
overrides manually to avoid wiping
the 1268-entry Smogon fallback)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the held item is a mega stone that matches the species, force the
calc into its mega form (base stats, types, intrinsic ability) regardless
of what Cobblemon's live form data says. Applies symmetrically to both
the player and the opponent.
Why: previously only the opponent got the swap. The player snapshot
respected `partyPokemon` stats, which silently downgraded the calc in
two real scenarios:
1. Pre-mega: player is holding their mega stone but hasn't pressed
mega-evolve yet. `partyPokemon.attack` etc. report base-form
values, so the calc reads base stats even though we know the
pokemon is committed to mega for the battle.
2. Post-mega + switch-out + switch-in: Cobblemon's known visual
glitch reverts the sprite (and partyPokemon stats) to base form
on re-entry. The previous FormTracker preservation (v0.9.1.3)
covered this for opponents but not for the player's actualStats.
Implementation:
- Extracted `computeMegaSwap` shared between opponent (existing
`applyMegaFormSwap`) and the new player-side `applyPlayerMegaSwap`.
- Player swap re-derives actualStats heuristically from the new mega
baseStats via `CalcBattleSnapshotFactory.derivedHeuristicStats`
(made `internal`). Loses precise IV/EV/nature investment but stays
inside damage-roll variance; accepted per user direction.
- `effectivePlayer` threads through the rest of `currentModel()`:
damageEngine.compute, switchPreview, render model fields,
matchupLabel, speedText. UUIDs and tab indices use the original
snapshot — only the calc-facing data changes.
- Player ability is NOT overwritten: Cobblemon swaps the ability on
mega activation, and pre-mega the player still has their base
ability which the engine should respect.
- Only triggers on items in MEGA_STONE_FORMS — non-mega items never
override live state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v0.9.1.4 mega-stone trump rule was built on `species.getForm(setOf("mega-x"))`
via Cobblemon's PokemonSpecies API. In practice that aspect lookup
silently returns the standard form for Charizard X/Y (and likely other
megas) because Cobblemon's actual form-aspect convention doesn't match
the "mega-x" / "megax" candidates we were trying. The check
`it != standard` then filtered the result out and the swap bailed,
leaving the calc on base-form stats. User reproduced by cycling
Charizardite X / Y on an opponent Charizard with no change in special-
attack damage numbers.
Fix: read mega base stats from the bundled battle database first —
it has hand-verified `<species>-mega`, `<species>-mega-x`,
`<species>-mega-y` entries for every Gen 6-7 mega (Charizard-Mega-X:
78/130/111/130/85/100; Charizard-Mega-Y: 78/104/78/159/115/100; etc.).
DB hit short-circuits the Cobblemon aspect path entirely. Fallback
aspect candidates widened (`mega_x`, `x`, `mega-form`, `megaform`)
for species missing from the DB.
Also surface mega stones in the opponent item-cycle UI:
- New MEGA_STONES_BY_SPECIES reverse map (45 Gen 6-7 species).
- `expandAlternatives` prepends the species' mega stones to its item
alternatives so Charizard always shows Charizardite X / Y in the
cycle, Gardevoir gets Gardevoirite, etc. Cycle limit raised 8 -> 10
to make room.
DB ability column is empty, so opponent mega-evolved abilities now
fall through to whatever was inferred for the base form — user can
still cycle the ability manually. (Previous code path tried to read
the mega's intrinsic from Cobblemon, but that path was the broken
one in the first place.) If the cycle picks the wrong ability for a
mega'd opponent, fix it via the existing ability override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v0.9.1.5 trump fix landed the right base stats and types from the
DB, but ability stayed on the base form (Charizard Y kept Blaze
instead of Drought; Mega Gardevoir kept Synchronize instead of
Pixilate). Solar Power / Tough Claws / Pixilate / Huge Power and the
rest of the mega-conditional engine paths never fired.
Add a hand-verified MEGA_ABILITY_BY_FORM_KEY map (47 entries, every
Gen 6-7 mega) keyed by the same `<species>-mega(-x|-y)?` form key
used in the DB lookup. computeMegaSwap fills MegaSwapData.megaAbility
from this map. Both swap call sites apply it:
- Opponent (applyMegaFormSwap): unchanged — already respected
swap.megaAbility when not user-overridden. Now actually gets a
value.
- Player (applyPlayerMegaSwap): previously kept player.abilityName
intact ("the player knows their own ability"). That was wrong for
the pre-mega case — pre-mega the player has the base form's
ability, so trump-as-mega means we have to overwrite. Player has no
manual-cycle mechanism so the override is unconditional.
Engine-side: snapshot.abilityName drives Pixilate / Aerilate type
remap, Drought-conditional Solar Power, Tough Claws contact boost,
Mega Launcher pulse boost, Huge Power doubling — all fire correctly
now via the existing offensiveAbilityModifier / applyTypeChangingAbility
paths.
Out of scope (separate enhancement): weather-setting on switch-in
(Drought/Drizzle/Sand Stream/Snow Warning). The calc reads weather
from snapshot.weather, which only updates when BattleStateTracker
sees a real weather message. Pre-mega Charizard Y will show Drought
as its ability but won't auto-set sun for damage purposes. Set the
weather manually or wait for the actual mega-evolve message if you
need Solar Power / sun-boosted Fire damage in the calc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.