R16 changes a design rather than adding to it. The reward action no longer calls
GiveKit; it grants the permission that GATES a kit, and the player redeems it
themselves in game. Kits already has exactly this model built in - every kit
carries a RequiredPermission, GiveKit's own path checks it, the in-game menu
renders an ungated kit as locked rather than hiding it, and GetKitInfo returns
the permission so the module can read which kits are gated.
What that removes is most of the hard part.
The offline-grant problem disappears. GiveKit needed a connected BasePlayer, so
an event firing at 2am rewarded only whoever was online; an entitlement waits.
That CLOSES the open question section 3 carried - no pending-grant queue, no
second at-most-once store.
It is the same machinery as R2 rather than a second mechanism: a reward becomes
a permission grant authored by the site and mirrored into Oxide, which phase 7
already builds. One permission authority, one drift story, one audit trail.
reversible: 'ledger' becomes honest where a direct grant could only ever be
'none'. revert revokes the permission, removing one that is not there is a
success, and it is idempotent by construction. A player who redeemed before the
revert keeps the items, and that is correct: the ledgered resource is the GRANT,
so reverting withdraws the entitlement rather than the consumption.
cost() counts grants and is exactly knowable before dispatch, which removes the
whole declare-the-maximum-because-you-cannot-know class of problem from chapter
5 section 4. And the idempotency key largely stops mattering: chapter 5 draws
the line itself - a key is for a write whose repetition would be a second
EFFECT, and a permission grant is a SET.
The earlier section arguing a kit grant can only be reversible: none is
rewritten rather than deleted, with the correction stated: it was right about a
direct grant, and the reusable part is that the action had been declared around
the wrong noun. What the event makes is not loot, it is an entitlement.
One design note carried into the option source: a kit with an EMPTY
RequiredPermission is open to everybody, so granting a permission for it rewards
nobody. The dropdown must surface which kits are gated and refuse or warn on one
that is not.
R15 opens an optional-integration tier, with BetterChat (LaserHydra, 5.2.15,
MIT, Universal) as the first member, for leaderboard-earned chat titles. Its
integration point is a PULL - API_RegisterThirdPartyTitle registers a callback
BetterChat invokes per player - so a title is a pure function of state we
already hold, with nothing written into it and no drift to reconcile. The trap
is chapter 4's rule applied to somebody else's callback: that getter runs
synchronously on the chat path and must be a cheap in-memory lookup, never a
socket call. Its API_AddGroup and API_SetGroupField pair naturally with R2's
site-authored groups, but that direction is a push and would need R2's drift
posture, so it is a phase 17 decision rather than a given.
Section 3 is now empty. Clans-in-the-base-set was confirmed complementary, and
the offline-grant question was dissolved rather than answered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4