.claude/skills/wallet-module/SKILL.md
WalletModule reference — currency management with BigDouble support, reactive properties, caps, lifetime stats, and persistence. Use when working with currencies, wallets, or financial systems.
npx skillsauth add punkfuncgames/tetris-clone wallet-moduleInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Package: com.punkfuncgames.wallet | Define: PUNKFUNC_WALLET
Location: Packages/com.punkfuncgames.wallet/Runtime/PunkFuncGames.Wallet/
Namespace: PunkFuncGames.Wallet
Registration/Lifecycle:
RegisterCurrency(CurrencyDefinition, BigDouble initialAmount = default) — registers + applies pending save dataRegisterCurrencies(IEnumerable<CurrencyDefinition>) — batch registerUnregisterCurrency(CurrencyDefinition) — disposes container, removes lookupUniTask InitializeAsync(CancellationToken ct) — loads save data into pending bufferbool IsInitialized, int RegisteredCurrencyCountCurrency Operations:
Add(CurrencyDefinition/string, BigDouble amount, CurrencyChangeSource source) — publishes CurrencyChangedEvent if amount changed; string overload silently no-ops on unknown IDbool Consume(CurrencyDefinition/string, BigDouble amount, CurrencyChangeSource source, string context) — returns false + publishes CurrencyInsufficientFundsEvent on failure; string overload returns false on unknown IDbool TryConsumeAll(IEnumerable<(CurrencyDefinition, BigDouble)>, string context) — atomic: verifies all first, then consumes all; materializes enumerable to prevent double-enumerationSet(CurrencyDefinition/string, BigDouble amount, CurrencyChangeSource source) — publishes event only if value changedQueries:
BigDouble GetAmount(CurrencyDefinition/string) — returns BigDouble.Zero for null/unregisteredCurrencyContainer GetContainer(CurrencyDefinition/string) — returns null for unregisteredCurrencyDefinition GetCurrencyDef(string) — returns null for null/empty/unregisteredbool HasEnough(CurrencyDefinition/string, BigDouble amount)IEnumerable<CurrencyDefinition> GetAllCurrencies()IEnumerable<CurrencyContainer> GetAllContainers()Cap Management:
SetCap(CurrencyDefinition, BigDouble newCap) — saves immediately; publishes CurrencyCapChangedEvent; also publishes CurrencyChangedEvent if amount was clamped downRemoveCap(CurrencyDefinition) — saves immediately; publishes CurrencyCapChangedEventReset:
ResetCurrency(CurrencyDefinition) — publishes CurrencyChangedEvent (source=Prestige), saves immediatelyResetAllCurrencies() — iterates all, calls ResetCurrency per currency (publishes individual events)Error Behavior:
Add/Consume/Set/SetCap/RemoveCap/ResetCurrency with null definition → ArgumentNullExceptionAdd/Consume/Set/SetCap/RemoveCap/ResetCurrency with unregistered definition → InvalidOperationExceptionsealed class WalletService : IWalletService, IAsyncStartable, ITickable, IDisposable
Constructor dependencies:
ILogService (nullable — used for warnings/errors)ISaveService (required — ArgumentNullException)IPublisher<CurrencyChangedEvent> (required)IPublisher<CurrencyCapChangedEvent> (required)IPublisher<CurrencyInsufficientFundsEvent> (required)Persistence strategy:
"WalletData", version 1ITickable.Tick() checks _isDirty flag, saves after 5-second interval via Time.deltaTimeSetCap, RemoveCap, ResetCurrency, DisposeCurrencyEntryData for precisionInitializeAsync() loads all entries into _pendingLoadData dict; actual restore happens in RegisterCurrency() via ApplyLoadedData()Dual initialization:
IAsyncStartable.StartAsync() (VContainer auto-calls)IWalletService.InitializeAsync() (manual calls)InitializeInternalAsync() which is idempotentReactive runtime holder for a single currency. Bind UI directly to these properties.
CurrencyDefinition Definition — identity referenceReadOnlyReactiveProperty<BigDouble> Amount — subscribe for UI updatesReadOnlyReactiveProperty<BigDouble> Cap — infinity when no capBigDouble LifetimeEarned — cumulative earned, persistedBigDouble LifetimeSpent — cumulative spent, persistedOperations:
BigDouble Add(BigDouble, CurrencyChangeSource) — returns actual added after cap; no-ops for ≤0bool TryConsume(BigDouble, CurrencyChangeSource) — returns false if insufficient; no-ops for ≤0void Set(BigDouble, CurrencyChangeSource) — applies cap, tracks lifetime deltabool HasEnough(BigDouble)Cap Management:
bool SetCap(BigDouble) — returns true if amount was clamped downvoid RemoveCap() — sets cap to BigDouble.PositiveInfinityBigDouble GetPercentage() — 0-100 scale; returns 0 if no cap or zero capBigDouble GetRemainingSpace() — PositiveInfinity if no capbool IsAtCap() — false if cap is infinityLifetime Setters (for save restore):
void SetLifetimeEarned(BigDouble) — direct setter for deserializationvoid SetLifetimeSpent(BigDouble) — direct setter for deserializationReset:
void Reset() — zeroes amount, lifetimeEarned, lifetimeSpentCreate via Assets menu: "GameTemplate/Currency Definition"
CurrencyDefinition (ScriptableObject): Thin wrapper with public CurrencyDefinitionData data;
Access fields via definition.data.fieldName.
CurrencyDefinitionData ([Serializable] public struct, camelCase fields):
string displayNameKey — unique ID and lookup key (auto-filled from asset name in OnValidate)Sprite icon, Color colorbool hasCap, SerializableBigDouble initialCap (default 1e308)bool showInUI, int uiSortOrderWalletData:
int Version (currently 1)List<CurrencyEntryData> CurrenciesCurrencyEntryData:
string CurrencyId — maps to DisplayNameKeystring RawAmount, string RawCap — BigDouble as stringstring RawLifetimeEarned, string RawLifetimeSpent — BigDouble as stringAll events are readonly struct published via MessagePipe.
| Event | Fields | Published When |
|-------|--------|----------------|
| CurrencyChangedEvent | Currency, NewAmount, PreviousAmount, Delta (computed: New-Prev), Source | Any amount change (Add/Consume/Set/Reset/SetCap clamp) |
| CurrencyCapChangedEvent | Currency, NewCap, PreviousCap | SetCap or RemoveCap (only if cap actually changed) |
| CurrencyInsufficientFundsEvent | Currency, AttemptedAmount, AvailableAmount, Context | Consume fails (insufficient funds) |
CurrencyChangeSource enum (12 values): Unknown, Manual, Gameplay, Purchase, Reward, Prestige, Cheat, SaveLoad, Conversion, OfflineProgress, Production, Upgrade
WalletInstaller.Install(builder, options);
// Registers: WalletService as EntryPoint (IAsyncStartable + ITickable) with .As<IWalletService>()
// Brokers: CurrencyChangedEvent, CurrencyCapChangedEvent, CurrencyInsufficientFundsEvent
Required by WalletModule:
ISaveService (SaveModule) — persistenceILogService (LogModule) — optional loggingIPublisher<T> (MessagePipe) — event publishingBigDouble (Math) — large number arithmeticReactiveProperty<T> (R3) — reactive UI bindingModules that depend on WalletModule:
public sealed class ShopController : IStartable, IDisposable
{
private readonly IWalletService _wallet;
private readonly CompositeDisposable _disposables = new();
public ShopController(IWalletService wallet) => _wallet = wallet;
public void Start()
{
CurrencyContainer gold = _wallet.GetContainer("Gold");
gold.Amount
.Subscribe(amount => UpdateGoldUI(amount))
.AddTo(_disposables);
}
public bool TryBuyItem(BigDouble cost)
{
return _wallet.Consume("Gold", cost, CurrencyChangeSource.Purchase, "ShopItem");
}
public void Dispose() => _disposables.Dispose();
}
Tests in Packages/com.punkfuncgames.wallet/Tests/EditMode/:
CurrencyContainerTests — constructor, add, consume, set, cap, reset, lifetime, disposeWalletServiceTests — constructor guards, registration, CRUD, events, persistence, validationCurrencyEventsTests — struct field population, delta computationTest helpers:
MockFactory.CreateWalletService(currencies) — pre-configured mock with working Add/Consume/HasEnoughMockFactory.CreateSaveService(data) — mock with working HasKey/SetTestFixtures.CreateCurrencyDefinition(displayNameKey, hasCap, initialCap) — SO factory with data struct initializeddevelopment
UnlockConditionModule reference — composable unlock conditions using ScriptableObjects with AND/OR/NOT logic, stat/currency/upgrade/prestige/gamestate/boolean checks, reactive service layer with progress tracking. Use when implementing unlock systems, gating, or progression requirements.
development
UndoModule reference — command pattern with undo/redo stacks, command merging, and reactive state. Use when implementing undo/redo, undoable actions, or command patterns.
tools
Unity UI Toolkit reference — UXML documents, USS styling, MVVM pattern (ViewModel + Presenter), custom VisualElements, responsive layout, animations, performance guidelines, and complete Figma-to-UI-Toolkit property mapping. Use when building or modifying UI with UI Toolkit, creating UXML/USS files, writing ViewModels or Presenters, designing screens/panels/components, or converting Figma designs to UI Toolkit.
content-media
UIModule reference — panel management, dialog service, notifications, loading screen, and localized UI components. Use when working with UI panels, popups, dialogs, or localized text/images.