|
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
|
Status: PLAN / awaiting sign-off (written 2026-07-11). Nothing implemented yet.
Replaces the current full-screen name editor (screens/modal/FullKeyboard.qml → PagedPicker → SearchRoot/SearchContainer/SearchCriteria/ SearchResults + TilesetPicker) with a real keyboard outline: an ASDF/QWERTY deck whose letter and number keys each hold one game tile, with the physical key it's bound to shown as a superscript legend in the corner. Click the key, or just type it. Modifier combinations (Shift / Ctrl / Alt) switch pages; the pages are also clickable buttons above the deck.
projects/db/assets/data/font.json holds 255 tiles (ind 1–255; 0 is the string terminator and is not a tile). Classified by the app's existing colour precedence (normal → control → picture → singleChar → variable → multiChar, from SearchResults.determineColor):
| Category | Count | What it is |
|---|---|---|
| Normal | 70 | A–Z, a–z, space, ( ) : ; [ ] - ? ! . / ,, Pk Mn ♂ ♀ × |
| Single-Char | 49 | digits 0–9, bold A–I/V/S/L/M, é, ', contractions ('d 'l 'm 'r 's 't 'v), quotes, …, ·, $, kana |
| Multi-Char | 7 | <pkmn> <poke> <pc> <tm> <trainer> <rocket> <......> |
| Variable | 4 | <player> <rival> <targ> <user> |
| Picture | 114 | tile01–tile48, tile4D, box-frames ╔ ═ ╗ ║ ╚ ╝, tileC0–tileDF, arrows ⮚ ⯈ ⯆ |
| Control | 11 | <page> <line> <next> <para> <end> <cont> <cont_> <autocont> <done> <prompt> <dex> |
| Total | 255 |
Assignable keys per page: letters (26) + number row (10) = 36. Pages needed: ⌈255 / 36⌉ = 8. Modifier combinations available: Shift / Ctrl / Alt = 2³ = 8. They line up exactly — one page per combination, no leftovers, nothing arbitrary.
The Space tile ( , ind 127) is the 255th and rides on the deck's real spacebar, which is where every human already expects it. So: 254 tiles across 8 × 36 alphanumeric keys (34 slots left empty), + Space on the spacebar.
The category strip's order (minus "All") is the usage/priority order Twilight named: Normal → Single-Char → Multi-Char → Variable → Picture → Control. Pages are ordered by that, and cheap modifier combos get the high-usage pages.
But keys are not filled linearly by category. Three rules, in priority order:
A tile is promoted to a better key on a later page rather than being buried on a bad key of an earlier page (Twilight's explicit instruction) — e.g. the box-frame glyphs and cursor arrows are the only genuinely useful Pictures, so they take page 5's home row rather than being dumped in tile order.
| # | Modifier | Name | Contents |
|---|---|---|---|
| 1 | — | Letters | A–Z uppercase (Normal) + digits 0–9 |
| 2 | Shift | Lowercase | a–z (Normal) + the 10 Normal symbols |
| 3 | Ctrl | Symbols | bold letters + é ' “ ” ‘ ’ … · $ ﹕ . + : ; [ ] / Pk Mn |
| 4 | Alt | Codes | contractions + <player>/<rival>/<targ>/<user> + Multi-Char words + kana |
| 5 | Shift+Ctrl | Tiles I | box frames + arrows + tile01–tile1B |
| 6 | Shift+Alt | Tiles II | tile1C–tile3F |
| 7 | Ctrl+Alt | Tiles III | tile40–tile48, tile4D, tileC0–tileD9 |
| 8 | Shift+Ctrl+Alt | Controls | tileDA–tileDF + the 11 control codes |
Pages 1–4 (the four cheapest combos) hold every Normal, Single-Char, Multi-Char and Variable tile — i.e. everything you'd ever put in a name. Pictures and Controls (the two you shouldn't) sit behind two- and three-key combos.
Deck rows: A = number row 1 2 3 4 5 6 7 8 9 0, B = Q W E R T Y U I O P, C = A S D F G H J K L, D = Z X C V B N M.
| Key | Tile | Key | Tile | |
|---|---|---|---|---|
| 1–0 | 1 2 3 4 5 6 7 8 9 0 (Single-Char) | Q–P | q w e r t y u i o p | |
| A–L | a s d f g h j k l | Z–M | z x c v b n m | |
| Space | (Normal) |
Pure identity: the base layer of a real keyboard — press a letter key, get the lowercase letter. 36/36 used.
Corrected 2026-07-11 (Twilight). The first cut had uppercase here, reasoning that Gen 1 names are all-caps. That was wrong: it's the one place the deck would have contradicted every keyboard its user has ever touched. All-caps is what Caps Lock is for (§5).
Letters: Q W E R T Y U I O P / A S D F G H J K L / Z X C V B N M — identity, exactly like a real keyboard. Number row = the 10 Normal symbols, matching the real Shift-row where the game has the glyph:
| Key | Tile | Why |
|---|---|---|
| 1 | ! | Shift+1 = ! on a real keyboard ✔ |
| 2 | ? | the other sentence-ender |
| 3 | . | |
| 4 | , | |
| 5 | - | |
| 6 | ♂ (<m>) | |
| 7 | ♀ (<f>) | |
| 8 | × (<x>) | Shift+8 = * = multiply ✔ |
| 9 | ( | Shift+9 = ( ✔ |
| 0 | ) | Shift+0 = ) ✔ |
36/36. Page 2 is 100 % Normal — pages 1+2 together are the whole in-game-legal character set.
| Key | Tile | Key | Tile | Key | Tile |
|---|---|---|---|---|---|
| A | bold A | I | bold I | Q | “ (<o">) |
| B | bold B | L | bold L | W | ” (<c">) |
| C | bold C | M | bold M | R | ‘ (<o'>) |
| D | bold D | S | bold S | T | ’ (<c'>) |
| E | bold E | V | bold V | U | … (<...>) |
| F | bold F | J | ' (apostrophe) | Y | · (<mdot>) |
| G | bold G | K | é (<e>) | Z | ﹕ (<:>) |
| H | bold H | N | Mn (<mn>) | X | . (<.>) |
| P | Pk (<pk>) | O | $ (Pokédollar) | ||
| 1 | : | 2 | ; | 3 | [ |
| 4 | ] | 5 | / | 6–0 | (empty) |
Ctrl+B = bold B is the same thing it means in every app on earth. 31/36 used.
| Key | Tile | Mnemonic |
|---|---|---|
| D L M R S T V | 'd 'l 'm 'r 's 't 'v | the contraction sits on its own letter |
| P | <player> | **P**layer |
| O | <rival> | **O**pponent (and it sits right next to Player) |
| Y | <targ> (Player Usage) | **Y**our usage |
| U | <user> (Enemy Usage) | **U**ser code — next to Your |
| K | <pkmn> | Po**K**émon |
| E | <poke> | Pok**é** |
| C | <pc> | **C**omputer |
| J | <tm> | prime free key (T and M are taken by contractions) |
| H | <trainer> | |
| G | <rocket> | |
| F | <......> | prime key — …… is common in dialogue |
| 1–5 | あ い う え お | kana strip |
| 6–8 | ァ ゥ ェ | |
| A B I N Q W X Z, 9, 0 | (empty) |
26/36 used.
The six box-frame glyphs are drawn as a box on the keys — Q=╔ W=═ E=╗, A=║, Z=╚ X=╝ — and the three cursor arrows take the right home keys: J=⯈ K=⯆ L=⮚. Those nine are the only Pictures anyone actually uses, so they get the best keys on the page.
The remaining 27 keys carry tile01–tile1B in reading order (number row → R T Y U I O P → S D F G H → C V B N M). 36/36.
tile1C–tile3F, straight reading order: number row (1C–25), Q–P (26–2F), A–L (30–38), Z–M (39–3F). 36/36.
Number row: tile40–tile48 + tile4D. Q–P: tileC0–tileC9. A–L: tileCA–tileD2. Z–M: tileD3–tileD9. 36/36.
Number row 1–6: tileDA–tileDF. Control codes on mnemonic letters:
| Key | Code | Key | Code |
|---|---|---|---|
| E | <end> | C | <cont> |
| L | <line> | V | <cont_> (alt continue, next to Cont) |
| N | <next> | B | <autocont> |
| P | <page> | D | <done> |
| A | <para> | R | <prompt> |
| X | <dex> |
17/36 used. The most dangerous codes live behind the most deliberate key combination — you cannot hit <end> by accident.
254 tiles on alphanumeric keys + Space on the spacebar = 255 = every tile, each exactly once, no duplicates. This is pinned by a unit test (§6).
The current screen's problem is that it's a list of chips next to a filter form. The new one is a device: a keyboard deck you look at, not a form you read.
The six Settings::fontColor* values stay exactly as they are (Normal grey #616161, Single-Char lime #9E9D24, Multi-Char amber #FF6F00, Variable green #388E3C, Picture blue #303F9F, Control purple #7B1FA2). A safe→dangerous re-ramp was proposed and rejected — the categories already read fine and the redesign is about the screen's layout/chrome, not the category identity. The deck applies them as borders + a faint face wash, so they stay legible without shouting.
Caps Lock is not "latch the Shift page". It behaves the way it does on every keyboard:
The consequence is that the deck can show two pages at once — uppercase letters above a digit row — which is exactly what a keyboard does. The rules live in C++ (FontKeyboard::pageForKey), pinned by tst_font_keyboard, so the QML just asks. Qt exposes no portable way to read the caps light, so if it was already on before the screen opened, the deck corrects itself from the OS's own answer (event.text) on the first letter you press.
Page 3 is the Ctrl page, so Ctrl+C is bold C and Ctrl+V is bold V — they can't also be copy/paste. Rather than hide that behind a focus rule, the screen has two explicit modes and says which one you're in:
projects/app/src/mvc/fontkeyboardmodel.{h,cpp} — FontKeyboardModel : QAbstractListModel, exposed as brg.fontKeyboardModel.
Why C++ and not a QML table: it's testable (below), it keeps the delegate dumb, and the layout is data, not markup.
Each image://font/... request runs a full tileset build (TilesetEngine::buildTileset). Firing that once per key per animation frame (36 keys × 8 frames) would melt the UI — this is the same class of problem that already froze the old hover tooltip.
So: request the whole 16×16 tile sheet once per frame (image://tileset/..., exactly what TilesetDisplay already does) and have every key clip its 8×8 cell out of that one shared image — a classic sprite-sheet: Item { clip: true; Image { source: sheetUrl; x: -col*w; y: -row*w } }. One provider call per frame for the entire deck, and QML's pixmap cache hands the same pixmap to all 36 keys. Tile ind → row = ind / 16, col = ind % 16.
Exceptions (they aren't single tiles):
New, in fragments/controls/name-full/:
Rewritten: FullKeyboard.qml, NameFullHeader.qml (loses the Grid/Tileset toggle), DetailView.qml (restyled).
Deleted (dead once the deck lands): PagedPicker.qml, SearchRoot.qml, SearchContainer.qml, SearchCriteria.qml, SearchParam.qml, SearchResults.qml, TilesetPicker.qml — and their app.qrc entries. TilePreview.qml stays (the deck uses it). The C++ FontSearchModel / brg.fontSearch go with them in the same change (decided 2026-07-11) — nothing lingers.
Settings is untouched: the six fontColor* values stay as they are (§4).
projects/tests/tst_font_keyboard (new), pinning the thing that would be catastrophic to get wrong silently:
Plus tst_qml_screens must stay green (it loads every screen through the real engine).