Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
2026-06-08 — Session Log

Coverage-gap-fill day: pushed all three library layers to/above 90% line coverage (common 100%, db 90.2%, savefile 90.2%) — pokemonbox.cpp 72→94.6%, spritedata.cpp 46→100%, the area family 61–70→90–100%, mapsearch.cpp 47→100%, fontsearch/fontsdb + the db-entry getters. The push surfaced several real bugs (below). Full per-file gap list: ../../plans/testing.md.

Bug: PokemonBox::update(resetType=false) clobbered a dual-type mon's type2 (+ 2 related)

Found while coverage-testing pokemonbox.cpp. approved, brought to her before fixing. (1) update()'s bare else type2 = toType1->ind ran on every call with resetType=false, overwriting a dual-type mon's type2 with type1 (silently dropping the second type; reachable via maxLevel/maxEVs/resetEVs/reRollEVs/manualLevelChanged; emitted no type2Changed). Fixed: type2 (re)derivation wrapped in if(resetType). (2) isCorrected() vs update() disagreed for a species whose DB toType2==toType1; isCorrected now treats a record as dual-type only when toType2 genuinely differs from toType1. (3) the empty-slot bug in isMaxPP()/isMaxPpUps() (an empty moveID-0 slot counted as "not maxed") propagated into isHealed(), so any mon with <4 moves could never read as healed (user-facing — the heal indicator). Fixed at source; isPokemonReset simplified to iterate the real initial moves. Regression-guarded in tst_pokemonbox (incl. box_healedWithFewerThanFourMoves). See ../../reference/fix-patterns.md.

Bug: isMinEvs() used || (true if ANY one stat-exp is 0)

Changed to && (all five stat-exp must be 0), matching the header and symmetric with isMaxEVs(). The || had real UX impact: StatsTab.qml disables the "Reset EVs" menu on isMinEvs, so the action greyed out whenever a single stat-exp happened to be 0, even on a heavily-EV'd mon. Regression-guarded in tst_pokemonbox (box_reRollEvsAndMaxPpUps). confirmed.

Bug: db map-search / connect (2, found coverage-testing db)

approved. (1) MapSearch::hasDynamicSpriteSet/noDynamicSpriteSet (and hasSpriteSet/ noSpriteSet) dereferenced a null toSpriteSet on maps with no sprite set — !spriteSet only catches index 0, but -1 is the "none" sentinel. Now use spriteSet < 0 + guard toSpriteSet == nullptr. (2) MapDBEntryConnect::xAlign() guard was missing <= 0 (if(toMap->getWidth())), so it returned 0 for every real map and its connection-math body was dead — fixed to getWidth() <= 0, confirmed against the connection-data formulas in ../../reference/gen1-knowledge.md. Both gated behind the disabled Maps feature; regression-guarded in tst_mapsearch_predicates + tst_db_entry_getters2. See ../../reference/fix-patterns.md.

Bug: area-family (3, found coverage-testing area/*)

approved, brought before fixing. (1) AreaTileset::loadFromData inverted ternary — (map==nullptr) ? map->getToTileset() : nullptr crashed on a null map and discarded the real tileset on a non-null map; fixed to ? nullptr : map->getToTileset() (masked today by the disabled Maps path). (2) PokemonBox::newPokemon(Random_Pokedex) rolled rangeExclusive(1,151) so could never randomize to Bulbasaur — dex keys are 0-based; fixed to rangeExclusive(0,151). (3) considered an i < wildMonsCount bound on AreaPokemon::setTo's array writes, but declined — gen-1 wild tables are fixed at exactly 10, so the bound implies an impossible case; left unbounded (trust the fixed data). Bugs (1)+(2) regression-guarded in tst_area_logic (+ tst_pokemonbox). See ../../reference/fix-patterns.md.

Latent landmine confirmed: map DB getToMap()/getToSprite() never resolved

MapsDB::deepLink() is not called anywhere (DB::deepLinkAll() omits it), so every map's warp/connect/sprite getToMap()/getToSprite() pointer stays null. Not a crash today — every consumer is part of the not-yet-wired Maps feature; normal save load reads Area straight from save bytes. Landmine for when Maps is enabled. Confirmed via tst_sprite_data: with deepLink() called, all 918 map sprites resolve getToSprite() (0 nulls) and SpriteData::setToAll/ randomizeAll run clean over all 249 maps — so the documented sprite-link "crash" is purely this deepLink-not-called landmine, not a SpriteData defect. Standing fix: call MapsDB::inst()->deepLink() (e.g. in DB::deepLinkAll()) before any map feature dereferences these. Tracked in ../../status.md → Open Issues.

Item RNG: no duplicates within a box + Re-Roll sorts

New-item ("+") and Re-Roll RNG can no longer produce a duplicate within the same list (bag and PC box are independent — uniqueness is per-list). ItemStorageBox::itemNew() rolls via randomUniqueInd() (pool = all non-glitch/non-once items minus those already in the box); if the box somehow holds every item it adds nothing rather than a dup. randomize() (Re-Roll) now calls sort() at the end. Regression-guarded: box_itemNewNeverDuplicates, box_randomizeIsUniqueAndSorted in tst_items_logic. Compiled clean + full ctest green on the kit by request; rebuild the app binary to pick it up at runtime.

Bag + Pokémon storage screen layout cleanup (QML-only)

Removed the dirty hacks on both screens (review pending). Two panes now split 50/50 via a RowLayout (was Math.trunc(width*0.5) + chained anchors); the header's magic width:265 wrapper box is gone (title/check-all/count anchored cleanly); every header/footer IconButtonSquare dropped its repeated leftPadding/rightPadding/leftInset/rightInset:0 overrides. On the Pokémon grid, names are now always visible below each icon as dark text on no background (was a hover-only accent pill); the redundant edit pill + pen icon was removed (the cell-wide MouseArea already opens the editor). Conventions in ../../reference/ui-patterns.md → "Bag / Items screen layout" and "Pokémon storage screen layout".