Readiness gates
A robust request normally needs several gates, not one broad “lobby ready” flag.
- The current player is the expected
AccountorPlayerAvatarfor the verified client source. - The owning feature or mode is active.
- The dependency container can resolve the controller or service.
- The relevant cache has completed synchronization.
- The target item, request, or state still exists and is actionable.
- No request for the same logical action is already in flight.
- The account, event, arena, or view generation still matches the one that scheduled the work.
Optional modules should gate only their own feature. Keep imports, dependency descriptors, class definitions, and initialization for that feature behind the same boundary so a missing optional package cannot prevent unrelated code from loading.
Prefer native processors and action factories
When the client already wraps a server request in a processor or action object, use that layer. It usually owns current validation, request formatting, result messages, and synchronization behavior.
Directly calling a lower-level inventory command can skip validators, produce incomplete messages, or rely on a signature that is less stable than the client’s own action layer.
# Illustrative pattern. Verify the processor and callback contract
# in the current client build.
def request_action(target_id):
if not _can_request(target_id):
return
_busy[target_id] = True
processor = _create_native_processor(target_id)
processor.request(_on_action_result)
def _on_action_result(result):
target_id = result.targetID
_busy[target_id] = False
if not _context_is_current(result.contextToken):
return
if not result.success:
_show_native_failure(result)
return
_begin_confirmation(result)
With @adisp_process-style APIs, yield the native processor and keep all continuation
logic in the same owned flow. A timed delay can be part of that flow when it allows a
synchronized cache to catch up, but it should remain bounded and cancellable.
Bulk requests and per-item invariants
Native action APIs sometimes accept a total amount plus a list of targets rather than an explicit amount per target. If your feature must preserve a per-target reserve, cap, or safety invariant, do not assume the server will consume from the target list in the order your UI displayed.
- Recompute eligibility immediately before submission.
- Round requested values to the native action's valid step size before sending.
- Split the operation into sequential single-target native requests when the native bulk contract cannot express the per-target invariant.
- Stop the sequence on rejection, context change, cache mismatch, or exhausted retry budget.
- Show the user that multiple native confirmations or result messages may appear when the safe path uses several independent requests.
Busy flags should match independent operations
Use one in-flight guard per independent request domain. Two independent server-changing request domains can complete separately and should not share one global lock.
- Set the guard immediately before submitting.
- Clear it before processing success or failure so an exception in the callback cannot permanently disable the feature.
- Bind target IDs and item objects into callback closures; never rely on the loop variable still containing the intended value.
- Do not blindly retry a state-changing request after an ambiguous result.
for target in pending_targets:
if _busy_by_type.get(target.kind):
continue
_busy_by_type[target.kind] = True
_submit(target, lambda result, target=target: _finished(target, result))
React to synchronized state, not request bursts
For repeated claims, openings, or activations, submit one request and continue from its response or the system’s synchronized update event. Re-read the count and availability after a short delay before deciding whether another operation remains.
Stop the chain when the count reaches zero, the owning feature is disabled, the account context changes, the user leaves the relevant screen, the retry budget is exhausted, or a request fails.
Response success versus cache confirmation
A server response can report success before the client cache exposes the resulting inventory change. When the user-facing message claims that an exact item was received or consumed, verify the observable state separately.
- Snapshot the exact target item and quantity before submission.
- Preserve the server-reported item identifier and count.
- After success, perform bounded checks of the synchronized cache.
- Show the success confirmation only after the expected change is observed.
- If confirmation times out, log the mismatch and do not repeat the same request automatically. The operation may have succeeded and the cache may simply be late.