Skip to content

Dev - #1

Open
nickmatteo wants to merge 35 commits into
mainfrom
dev
Open

Dev#1
nickmatteo wants to merge 35 commits into
mainfrom
dev

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 5 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>
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