The Journal panel is complete: contracts from the server, a per-character notebook, and its searchable index. The gate ledger is recorded rather than smoothed over, because the pattern is the point — four rounds, and every defect was one mistake wearing different clothes: an element bound as the wrong thing, or a binding never tested. One reported failure was not a defect at all; the character was indoors, where retail records nothing either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.1 KiB
Campaign QJ — the Journal and Page List tabs
Status: CLOSED USER-ACCEPTED 2026-08-21. All five slices landed and the connected gate passed.
The gate took four rounds, and every defect it found was the same mistake in a different place — an element bound as the wrong thing, or a binding never tested:
- Button property
0x0Dread as "starts disabled", which killed every button on the panel (register QJ-2). - The location readout is authored EDITABLE, so it is a
UiField; bound asUiTextit silently discarded every write — the value reached the model and the file and never the screen. - Handlers deferred their redraw to the next frame's
Tickwhere retail redraws at the click. - The timer's unit labels stayed visible behind the running readout,
because retail's
ShowEditableTimertoggles each box AND its label.
Round 3's "Record does nothing" turned out not to be a defect at all: the
character was indoors, where retail's own gid_to_lcoord fails and nothing is
recorded. Faithful, and now commented so it does not read as a gap.
The durable outcome is JournalPanelLiveBindTests — see
claude-memory/feedback_test_the_binding_seam.md. Completes the panel Campaign QT mounted: QT
shipped the Contracts tab and left the other two inert by design.
Scope: retail's gmJournalUI (element type 0x10000048, page
0x10000563) and gmPageListUI (type 0x10000049, page 0x10000564).
What this actually is
A per-character notebook, entirely client-side. No wire, no server involvement, no dat content — the player writes the pages. Each page carries a label, a title, free-form notes, a recorded LOCATION, and a countdown TIMER. The Page List tab is a searchable index over those pages.
Nothing about it depends on quests; it shares the panel with Contracts and nothing else. That it is called "Journal" while the panel is also called "Journal" is retail's own naming, not a mistake here.
Measured ground truth
The file format
gmJournalUI::SavePages @0x00497270 / LoadPages @0x00496AC0. A plain tagged
text file, fopen mode w+. Both call sites pass the literal prefix
"Journal"; the path template is %s%s-%s-%s.txt, i.e.
{dir}Journal-{server}-{character}.txt.
<NEWP> begins a page (a file that does not open with one is refused)
<PNUM> %d page number
<LABE> %s label (authored max length 16)
<TITL> %s title (32)
<NOTE> %s notes (2048)
<DAYS> %d timer days
<HOUR> %d timer hours
<MINU> %d timer minutes
<LOCX> %f recorded location
<LOCY> %f
<TIME> %f running-timer value
Retail's own load error, byte-decoded: "Problem loading journal: Your journal file does not create a new page!"
The Journal page (0x10000563)
| Element | Role |
|---|---|
0x10000567 |
"New" button |
0x10000569 |
label edit box (0x1E = 16) |
0x1000056A / 0x1000056B |
"Title:" / title edit box (32) |
0x1000056C / 0x1000056D |
"Notes:" / notes edit box (2048), scrollbar 0x1000056E |
0x1000056F / 0x10000570 / 0x10000571 |
"First" / "~ 1 ~" / "Last" |
0x10000572 / 0x10000573 / 0x10000574 |
"Location:" / "00.0S, 00.0W" / "Record" |
0x10000575 |
"Timer:" |
0x10000576 0x10000577 |
days field, "d" |
0x10000578 0x10000579 |
hours field, "h" |
0x1000057A 0x1000057B |
minutes field, "m" |
0x1000057C |
running-timer text — OVERLAPS the three fields at x=84 |
0x1000057D |
"Start" button |
0x10000566 |
bottom-right button (65x32) |
0x1000057C sharing x=84 with the day/hour/minute fields is the authored form
of ShowEditableTimer @0x00495770 vs ShowRunningTimer: the same strip is
either three editable numbers or one running readout, never both.
The Page List page (0x10000564)
| Element | Role |
|---|---|
0x1000057F 0x10000580 0x10000581 0x10000582 |
headers "#" / "Title" / "Timer" / "Label" |
0x10000583 |
the list, scrollbar 0x10000584 |
0x10000585 |
"Delete" |
0x10000586 / 0x10000587 / 0x10000588 |
"Search:" / search box / "Reset" |
gmPageListUI::PageContainsString @0x00493B60 is the search predicate;
CheckForDoubleClick @0x00493140 opens the page
(gmJournalUI::GotoPage @0x00496430).
Slices
- QJ1 — the page model and its file.
JournalPageplus a faithful reader/writer for the tagged format, including retail's refusal of a file that does not open with<NEWP>. Pure; no UI, no state ownership. - QJ2 — the owner.
RuntimeJournalState: the page collection, the current page, new/delete/goto, and the timer. Per-character. - QJ3 — the Journal page. Edit boxes, page navigation, Record, and the editable/running timer swap.
- QJ4 — the Page List page. The list, the search, delete, and double-click-to-open.
- QJ5 — persistence. Load on character enter, save on exit, under the client's own data directory.
Definition of done
- A page written in one session is there in the next.
- Every ported algorithm cites its retail address.
- Every slice has a test that would catch its regression.