|
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
|
This is the pre-per-day session narrative from the 2026 revival, preserved verbatim from the old single-file session-log.md. These sessions were tracked by number (s13, s13b … s13z11, plus s14) rather than by calendar date — the work was never committed (HEAD was the corrupted commit until the 2026-06-06 recovery), so it has no reliable per-day dates. Newest first.
Dated work from 2026-06-06 onward lives in the per-day files (2026-06-NN.md).
The recurring theme of sessions 13f–13j: QML garbage-collects parentless C++ QObjects. Two distinct fixes came out of it — DB::qmlProtect for DB entries (13f) and qmlCppOwned() for savefile Q_INVOKABLE returns (13g–13h). See ../reference/qt-patterns.md → "QML garbage-collects parentless C++ QObjects" for the full rule.
(Pre-corruption; the last pre-corruption work was s13z11.) agreed, live-iterated: (1) restored the centered grey box (the SwipeView had been changed to anchors.fill: parent in the refactor → back to centered 500×250); (2) compacted every field via one CardFront.fieldH (28) knob + tighter row margins, fixing the too-tall Qt 6 Material boxes; (3) fixed the playtime clock — narrowed each digit field to ~2 chars (PlaytimeEdit.digitPad) and stopped the row sizing to the Material implicit height (it now pins to fieldH and vcenters the :), which had made the digits ride high; (4) vcentered DefTextEdit's built-in label (it lacked verticalAlignment, so ID/Money/Coins labels rode high).
Hover name on the Pokémon box grid was still blank for all mons (nicknamed or not), even after the accent pill painted. Data was fine (the edit screen shows the name). Root cause: the Material Button's built-in icon+text label was not rendering its text at the button's small fixed size (height: 20, padding: 0, display: TextBesideIcon). A plain Text in the same delegate (the L## level badge) renders fine, so:
File: PokemonBoxView.qml. Pure QML (no rebuild).
First pass at the blank hover name. The label is a flat Material button; flat buttons don't paint Material.background, so there was no accent fill and (we thought) no contrast for the light text. Removed flat: true and added Material.elevation: 0 (keep the accent fill, no drop shadow). Result: the accent pill painted but the name STILL didn't show → ruled out contrast (the real fix was the contentItem in 13j). Also added a species-name fallback to getMonNickname() so un-nicknamed mons show their species (matches the in-game display). Pure QML.
Did the full systemic fix for the Q_INVOKABLE-GC bug (setObjectOwnership was chosen over parenting — parenting would have fought the existing manual deleteLater/cross-box-relocate lifecycle and risked double-frees; setObjectOwnership leaves the C++ lifecycle untouched).
Removes the whole class of "QML frees a savefile object I still hold" crashes/decay. Needs a rebuild. Standing rule added (CLAUDE.md + qt-patterns): wrap any new Q_INVOKABLE QObject return in qmlCppOwned().
Project-debugger stack trace: crash at pokemonstoragemodel.cpp:146 return !mon->isBoxMon();, read access violation at 0xffff…ffff (freed mon). Cause: getBoxMon/getPartyMon are Q_INVOKABLE and return a parentless PokemonBox; QML gives Q_INVOKABLE returns JavaScriptOwnership, so after the details editor closes QML's GC frees the mon, leaving a dangling pointer in the box's vector → next role read crashes. Fixed those two with QQmlEngine::setObjectOwnership(mon, CppOwnership); identified the systemic ~13-method version (fixed in 13h). The earlier "random terminated abnormally with no output" crashes were this same use-after-free.
Repro clues: all font rendering (trainer name, full keyboard, hover tooltips) goes blank at once after "clicking around", name stops saving at the same moment, and only an app reboot fixes it. Root cause: QML was garbage-collecting the shared FontDBEntry objects (parentless, in FontsDB's vector) → dangling pointers → all font rendering AND name saving break (saving runs the name through the same font store via FontsDB::convertToCode); reboot reloads the DB. The fix machinery DB::qmlProtect(engine) existed (cascades CppOwnership to every entry in all 26 sub-DBs) but was never called — wired it into MainWindow::injectIntoQML() (+#include <pse-db/db.h>). Needs a rebuild.
Pokémon click works (the earlier "crash" was interference). Fixes:
Session 13 had included the entire expanded tree into savefileexpanded.h (included almost everywhere), dragging the heavy area sub-tree (whose .cpps pull mapdbentry.h/db) into nearly every TU → ballooned compile time; full rebuilds surfaced every pre-existing dllimport warning at once. Fix: include only the branches QML traverses — area.h → just areageneral.h, world.h → just worldother.h, dropped daycare/hof/rival from savefileexpanded.h; untraversed types back to forward-decl + Q_DECLARE_OPAQUE_POINTER. Added -Wno-ignored-attributes to root CMake to silence the harmless dllimport warning. Chain still works (verified no traversed type is opaque).
Tooling note: the bash sandbox mount lags behind the editor's writes and shows false truncation / stale content. The Read/Edit/Write file tools (and PowerShell) are the source of truth.
Rebuild confirmed the chain fix — data flows into the UI. Then:
After the s12 rebuild the whole brg.file.data.dataExpanded.* chain was still undefined, disproving the s10–12 truncation/qRegisterMetaType theory (binary was current and registered).
Root cause: Q_DECLARE_OPAQUE_POINTER(T*) forces QtPrivate::IsPointerToTypeDerivedFromQObject<T*> = false, so Qt stores those QObject pointers as opaque non-QObject values and QML reads their sub-properties as undefined — and neither qRegisterMetaType nor qmlRegisterUncreatableType overrides it. Proof (natural experiment): brg.file worked (FileManagement fully #included in bridge.h, never opaque) while everything opaque-declared beneath it failed. This also produced the "Connections: no signal matches" flood (the targets were just undefined).
Fix: removed the opaque decls for the traversed QObject chain types and added full #includes down the chain so Qt sees them as real QObject pointers. See ../reference/qt-patterns.md → "`Q_DECLARE_OPAQUE_POINTER` breaks the QML property chain".