Skip to content

Claude/feat manual overrides - #2

Open
nickmatteo wants to merge 42 commits into
mainfrom
claude/feat-manual-overrides
Open

Claude/feat manual overrides#2
nickmatteo wants to merge 42 commits into
mainfrom
claude/feat-manual-overrides

Conversation

@nickmatteo

Copy link
Copy Markdown
Owner

No description provided.

Nicholas Mattteo and others added 30 commits April 22, 2026 00:01
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>
Nicholas Mattteo and others added 12 commits May 6, 2026 03:15
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant