Server response payloads and native formatters
Reward containers vary between systems. Normalize recognized keys such as bonus,
reward, and rewards, and accept either a dictionary or a list of
dictionaries only after validating the shape.
Pass a deep copy to native formatters because some mutate the payload while formatting. Keep a readable fallback for unknown reward types and verify the exact required fields of a system-message template; similarly named templates are not interchangeable.
import copy
def format_rewards(payload):
rewards = _normalize_reward_container(payload)
if rewards is None:
return _fallback_reward_text(payload)
try:
return _native_formatter(copy.deepcopy(rewards))
except Exception:
_LOGGER.exception('native reward formatter failed')
return _fallback_reward_text(rewards)
Actions near context teardown
Requests initiated while leaving battle need an explicit policy.
- For independent actions, submit all required requests synchronously before calling the original exit method, then allow normal teardown. A callback-chained sequence may lose its owner after the first response.
- Do not keep a battle session half-exited merely to wait for unrelated work.
- If the action is explicitly defined as part of confirmed early exit and requires the live avatar context, use a bounded sequence with a hard release path. Always call the original exit method on success, failure, timeout, and exception.
This area is highly version-sensitive because providers and player objects may be destroyed in a different order between client builds.
Account identity and preserved intent
Pending local intent may legitimately survive an account-to-avatar transition or a reconnect on the same account, but it should not leak to a different account.
- Choose the identity that remains stable across the exact transitions you support.
- Do not interpret an early empty cache after reconnect as proof that owned items vanished.
- Keep preserved intent during a bounded restore window.
- Clear it on confirmed account change and explain the cancellation to the user.
- Keep saved preferences separate from the current pending schedule; cancelling one should not silently change the other.
Diagnostics
| Symptom | Likely cause | Check |
|---|---|---|
| Nothing happens after lobby entry | The GUI event preceded cache or controller readiness. | Log player type, dependency resolution, sync completion, and bounded retry count. |
| The action runs twice | Repeated provider state, duplicated listeners, or an in-flight guard cleared too late. | Log a logical request key and the exact event that submitted it. |
| Success message appears but inventory is unchanged | Message was emitted from the response without cache confirmation. | Compare exact pre-request and post-request item counts. |
| Feature never recovers after one failure | A busy flag remained set after an exception. | Clear the flag before callback processing and log the traceback. |
| Unrelated mod fails to import | Optional feature imports or descriptors were evaluated at module scope. | Move the entire optional feature behind a local availability gate. |
Source-verification checklist
- Find the native UI action and trace which processor or factory it creates.
- Inspect validator inputs, request result fields, and native messages.
- Find the cache or controller event that proves state synchronization.
- Check which player context owns the provider in lobby and battle.
- Verify teardown and whether callbacks survive context replacement.
- Test success, server rejection, delayed cache updates, disconnect, account switch, and repeated clicks.
Request coalescing and cooldowns
RequestsController adds queues, type-keyed timeout waiters and optional delayed-path coalescing above a requester. A cooldown is not a single-flight lock. Overlapping same-type requests can overwrite the stored waiter handle, and an earlier response can cancel that later timer. The handler also runs before its waiter is installed, so synchronous callback delivery followed by a truthy return can leave a timeout after completion.
A singulizer key must distinguish every input changing the result. Parameterless native examples do not justify a type-only key for a parameterized query. Consumer callbacks run before all fanout/key-removal/queue advancement; escaping exceptions or reentrant same-key submissions can disrupt that shared completion path. Preserve diagnostics while keeping mod-owned callbacks safe for the caller.
Cooldown-manager scope need not match controller lifetime. The current
WgcgRequestsController initializer retains one default
WgcgCooldownManager() object; both inspected
factory variants omit that argument. Within the same loaded class,
their default-constructed controllers therefore share its common-cooldown timestamp. Controller stop/finalization
does not reset or replace that manager. Supplying a distinct manager separates that timestamp, while
per-type deadlines still use shared scope/request-type keys.
Selected-source identity and timing controls used an inert clock and requesters, without real scheduling,
requests or server-cooldown measurements.
A timeout's failure delegation does not prove the underlying transport stopped. Inspect the concrete requester stop/finalize path and invalidate late results independently.
Request context and waiting UI ownership
RequestCtx.isProcessing tests a stored callback, not transport state or waiting-widget existence. Base context completion clears the callback before invoking it; DataRequestCtx uses a different argument shape and clears afterward. Both hide waiting UI after the callback without a finally guard, so a consumer failure can skip cleanup. Avoid reusing a completing context for a new operation.
Waiting.show expects a generated waiting-message resource, not arbitrary text. Repeated shows normally create multiple tasks; hide removes one matching task rather than every occurrence. Single mode shares an active task and interrupt callbacks, not an independent per-request ownership token. Balance owned show/hide calls, and never use global close/rollback to clear unrelated native tasks or pretend to cancel transport.
See future and worker ownership and cache synchronization ordering.
Grouped actions preserve dispatch order, not completion order
Current public source; version-sensitive. PlayerAccount.doActions takes a group's original size, then repeatedly removes the first action tuple and dispatches it through the action factory. It does not await each result or collect successful completions before dispatching the next action. The supplied list is consumed in place; preserve an owned copy before dispatch if a local journal needs the proposed sequence.
A false factory return did not stop later dispatch in the isolated check. A synchronous exception stopped the loop after removing the failed entry, leaving later entries in the list. Neither outcome establishes what a remote failure means to the server's grouping protocol.
GroupedRequestProcessor adds group metadata and a response callback to the request. Those fields do not themselves provide client-side rollback or dependency sequencing. The integer-second fallback group ID can be shared by separate batches, so retain local operation and request/outcome identity rather than treating it as a unique journal key.
When one operation depends on another's authoritative result, use the appropriate completion and synchronized-state boundaries. Do not infer atomicity, server acceptance or automatic rollback from ordered submission. These are source and isolated-check findings; real transport and server transaction semantics remain outside that evidence.