A record whose requirements are met reads Ready to Claim in the Triumphs screen. Clicking it sends opcode 1801. This build decodes that request and resolves the record it names; it does not yet change any state, so the entry stays claimable.
Three claims were made in play and traced end to end at debug level:
ev=ws stage=request opcode=1801 transaction=0 payload_bytes=3 payload_hex=80DD00
ev=ws1801 stage=claim result=ok reason=decoded record_index=221
ev=transport stage=frame conn=1 type=1 bytes=37
ev=bap svc=10 rsp=11 result=ok
The full opcode sequence around them:
39391 op104
48358 op1801 claim
52742 op1801 claim
53212 op1801 claim
67824 op701 14 s later, unrelated
The client accepts the reply and asks for nothing else. No retry, no state fetch, no follow-up opcode. A grep for push or queuez activity after the claims returns nothing, because the server sends nothing.
That isolates the gap precisely: the reply shape is already correct, and the client is waiting on a push that never arrives. It is not rejecting the response, so a richer response payload is not what is missing.
Three bytes, the same shape as the opcode-1820 Collections pull: a presence bit then a fifteen-bit record row index.
payload_hex=80DD00 -> 0x80DD, presence 1, record row 221
payload_hex=80E300 -> 0x80E3, presence 1, record row 227
payload_hex=80E000 -> 0x80E0, presence 1, record row 224
The row indexes the records and lore table 0x81319339. opcode1801::parse_request refuses an
absent record, non-zero padding, a wrong length and a wrong opcode; 13 cases are covered by a
standalone test run against payloads captured from real clicks.
web_service_runtime.cpp computes prepared from the outcome and answers any dispatched opcode
that prepared no mutation with kRefusedStatus. A claim prepares nothing yet, so adding 1801 to the
dispatch chain would convert today's silently accepted claim into an explicit refusal -- a
regression wearing the shape of progress. The hook therefore runs before the chain and leaves the
outcome untouched.
Move it into the chain in the same commit that gives it a mutation, not before.
state::build_data::records exists so a claim can go from a record row to the bank index it has to
set, without walking the mapping tables per request.
struct Definition {
uint16_t definitionIndex; // native record row, what the claim names
uint16_t completionFlagIndex; // account flag bank row, or 0xFFFF when unaddressable
};
package_record_build.cpp reads both tables at extraction time:
+100 holds the unlock slot of the record's completion flagA slot is not an array index. The byte that sets slot s lives at the row of the mapping table
whose destination is s, so the resolution is done once here rather than per claim.
Only the state transition and its push. Three pieces:
state::unlocks is an immutable policy by contract --
publish, get, clear and nothing else -- so claimed records need their own store.Pending* mutation pattern: add a PendingRecordClaim to the
Outcome::Mutation variant, prepare it in claim_record, and let the established publication
path carry the new Family-4 version to the client.It is not established that the completion flag is what marks a record claimed. Every record completion flag can be set while the client still offers the claim, which was observed directly over a long session. What the traced claims prove is the delivery mechanism -- a push rather than a response -- not the payload.
Steps 1 and 3 are needed for any per-record state change and are safe to build. Only step 2 depends
on +100 being the right field, and if it turns out to be a different one that is a small change at
the end rather than a redesign.