@voltro/plugin-progression 0.0.0-stage → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24329 -0
- package/LICENSE +61 -0
- package/README.md +478 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +9189 -0
- package/dist/achievementComponents.js +92 -0
- package/dist/achievementStore.js +603 -0
- package/dist/achievementTables.entity.js +98 -0
- package/dist/achievements.d.ts +188 -0
- package/dist/achievements.js +205 -0
- package/dist/capture.js +64 -0
- package/dist/cmsQuests.d.ts +91 -0
- package/dist/cmsQuests.js +83 -0
- package/dist/components.d.ts +172 -0
- package/dist/components.js +4 -0
- package/dist/curve.d.ts +27 -0
- package/dist/curve.js +38 -0
- package/dist/daily.d.ts +152 -0
- package/dist/daily.js +126 -0
- package/dist/dailyStore.js +421 -0
- package/dist/dailySystem.fixture.js +138 -0
- package/dist/dailyTables.entity.js +90 -0
- package/dist/declarationFixtures.js +143 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +1833 -0
- package/dist/index.js +19 -0
- package/dist/pass.d.ts +186 -0
- package/dist/pass.js +122 -0
- package/dist/passRules.js +96 -0
- package/dist/passStore.js +485 -0
- package/dist/passTables.entity.js +96 -0
- package/dist/plugin.js +1086 -0
- package/dist/port.js +5 -0
- package/dist/progressionStore.js +706 -0
- package/dist/questComponents.js +163 -0
- package/dist/questPort.d.ts +126 -0
- package/dist/questPort.js +5 -0
- package/dist/questRefresh.js +56 -0
- package/dist/questRefresh.server.js +126 -0
- package/dist/questRerollJournal.js +77 -0
- package/dist/questStore.js +592 -0
- package/dist/questTables.entity.js +117 -0
- package/dist/quests.d.ts +142 -0
- package/dist/quests.js +153 -0
- package/dist/rank-app/schedules/progression.dispatchRankClosures.cron.tsx +19 -0
- package/dist/rank-app/workflows/progression.closeRankSeason.workflow.server.tsx +10 -0
- package/dist/rank-app/workflows/progression.closeRankSeason.workflow.tsx +1 -0
- package/dist/rankClosure.d.ts +69 -0
- package/dist/rankClosure.js +35 -0
- package/dist/rankClosure.server.js +139 -0
- package/dist/rankClosureWorkflow.d.ts +34 -0
- package/dist/rankClosureWorkflow.js +16 -0
- package/dist/rankReader.server.js +57 -0
- package/dist/rankRewardParent.server.js +65 -0
- package/dist/rankSeasons.d.ts +144 -0
- package/dist/rankSeasons.js +91 -0
- package/dist/rankStore.js +521 -0
- package/dist/rankSystem.fixture.js +170 -0
- package/dist/rankTables.entity.js +101 -0
- package/dist/ranks.d.ts +167 -0
- package/dist/ranks.js +219 -0
- package/dist/resourceStore.js +464 -0
- package/dist/resourceTables.entity.js +89 -0
- package/dist/resources.d.ts +105 -0
- package/dist/resources.js +60 -0
- package/dist/reward-app/schedules/progression.dispatchRewards.cron.tsx +19 -0
- package/dist/reward-app/workflows/progression.deliverReward.workflow.server.tsx +10 -0
- package/dist/reward-app/workflows/progression.deliverReward.workflow.tsx +1 -0
- package/dist/reward.d.ts +27 -0
- package/dist/reward.js +39 -0
- package/dist/rewardDelivery.d.ts +163 -0
- package/dist/rewardDelivery.js +38 -0
- package/dist/rewardDelivery.server.js +430 -0
- package/dist/rewardDeliveryRecord.js +48 -0
- package/dist/rewardDeliveryWorkflow.d.ts +36 -0
- package/dist/rewardDeliveryWorkflow.js +16 -0
- package/dist/rpc.d.ts +880 -0
- package/dist/rpc.js +498 -0
- package/dist/server.d.ts +2812 -0
- package/dist/server.js +21 -0
- package/dist/stats.js +45 -0
- package/dist/strategies.d.ts +131 -0
- package/dist/strategies.js +4 -0
- package/dist/systemFixtures.js +474 -0
- package/dist/tables.entity.js +155 -0
- package/dist/testing.d.ts +1566 -0
- package/dist/testing.js +11 -0
- package/dist/track.js +72 -0
- package/dist/tutorial.d.ts +157 -0
- package/dist/tutorial.js +110 -0
- package/dist/tutorialStore.js +496 -0
- package/dist/tutorialSystem.fixture.js +100 -0
- package/dist/tutorialTables.entity.js +91 -0
- package/dist/web.d.ts +363 -0
- package/dist/web.js +7 -0
- package/package.json +167 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Voltro — Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Voltro UG. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and all associated source code, documentation, and other
|
|
6
|
+
materials (the "Software") are the proprietary property of Voltro UG
|
|
7
|
+
("Voltro"). This is NOT open-source software.
|
|
8
|
+
|
|
9
|
+
Your access to and use of the Software is governed exclusively by the Voltro
|
|
10
|
+
Terms of Service at:
|
|
11
|
+
|
|
12
|
+
https://voltro.cloud/legal/terms
|
|
13
|
+
|
|
14
|
+
By downloading, installing, or using the Software you agree to those Terms.
|
|
15
|
+
Subject to and conditioned on your continued compliance with them, Voltro
|
|
16
|
+
grants you a limited, non-exclusive, non-transferable, non-sublicensable,
|
|
17
|
+
revocable license to use the Software. The license covers use by your
|
|
18
|
+
authorised named users only, up to the number of seats (or other billing metric)
|
|
19
|
+
your plan or order covers; each named user must be an identified individual
|
|
20
|
+
using their own personal account, and accounts, access credentials and seats
|
|
21
|
+
must not be shared. All rights not expressly granted in the
|
|
22
|
+
Terms are reserved by Voltro. If you do not agree to the Terms, you have no
|
|
23
|
+
license and may not use the Software.
|
|
24
|
+
|
|
25
|
+
The Terms of Service — including the scope of permitted use, plan
|
|
26
|
+
entitlements, fees, and pricing — may change from time to time. The version in
|
|
27
|
+
force is the one published at the URL above. Commercial terms and pricing are
|
|
28
|
+
NOT part of this notice and are not fixed by, or granted in perpetuity by, the
|
|
29
|
+
version of the Software in which this file appears.
|
|
30
|
+
|
|
31
|
+
Permitted use — your own software. Voltro is a development framework. You may
|
|
32
|
+
use the Software to build, run, and commercially distribute your own
|
|
33
|
+
applications and services, and to charge your own customers for them, without
|
|
34
|
+
owing Voltro any share of that revenue or any further permission beyond the
|
|
35
|
+
Terms. Applications you build with the Software are yours.
|
|
36
|
+
|
|
37
|
+
Prohibited use — reselling Voltro. You may NOT redistribute, resell,
|
|
38
|
+
sublicense, publish, or otherwise provide the Software itself — in whole or in
|
|
39
|
+
part, in source or compiled form — to third parties as a library, framework,
|
|
40
|
+
SDK, template, starter, or development tool for them to build upon, whether
|
|
41
|
+
standalone or repackaged. Shipping the Software as a non-separable, bundled
|
|
42
|
+
dependency inside an application you deliver (for example, compiled into your
|
|
43
|
+
deployed app) is permitted; extracting or re-exposing it as a reusable
|
|
44
|
+
component for others is not.
|
|
45
|
+
|
|
46
|
+
Except as expressly permitted above or by the Terms, you may not copy, modify,
|
|
47
|
+
merge, publish, sublicense, sell, distribute, or create derivative works of the
|
|
48
|
+
Software, in whole or in part.
|
|
49
|
+
|
|
50
|
+
Third-party open-source components distributed alongside the Software are the
|
|
51
|
+
property of their respective owners and are licensed under their own terms; see
|
|
52
|
+
THIRD-PARTY-NOTICES.md where provided. Nothing in this notice limits your
|
|
53
|
+
rights, or extends Voltro's, in those components.
|
|
54
|
+
|
|
55
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
56
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
57
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
58
|
+
VOLTRO BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM,
|
|
59
|
+
OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
|
60
|
+
|
|
61
|
+
For licensing inquiries, contact Voltro UG.
|
package/README.md
CHANGED
|
@@ -1,3 +1,479 @@
|
|
|
1
|
-
# Temporary Holding Version
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
## Tracks, exact values and rewards
|
|
3
|
+
|
|
4
|
+
`defineProgression` captures a track ID, immutable definition version, explicit sources, XP curve and level/every-period rewards. `levelCurve` uses exact decimal ratios and upward rounding of each next requirement; `explicitLevelCurve` captures a strictly increasing threshold table starting at zero. XP/stat values are canonical nonnegative decimal strings within signed SQL BIGINT. Unsafe numbers, noncanonical strings and overflow refuse. Example amounts and balancing below are illustrative app-owned configuration.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { progressionPlugin, levelCurve, grant, defineStat } from '@voltro/plugin-progression'
|
|
8
|
+
|
|
9
|
+
const progression = progressionPlugin({
|
|
10
|
+
tracks: [{
|
|
11
|
+
id: 'account', version: 1,
|
|
12
|
+
xpCurve: levelCurve({ base: 100, growth: 1.15, maxLevel: 5 }),
|
|
13
|
+
sources: ['match.finished'],
|
|
14
|
+
rewards: { 2: [grant('gold', 5)] },
|
|
15
|
+
}],
|
|
16
|
+
stats: [defineStat({ id: 'wins', operators: ['increment', 'max', 'min', 'set'] })],
|
|
17
|
+
statSources: ['match.finished'],
|
|
18
|
+
})
|
|
19
|
+
// Add progression to the API app's plugins array.
|
|
20
|
+
// After store binding, a trusted server job calls:
|
|
21
|
+
// progression.progression.service().applyXp(subject, {
|
|
22
|
+
// tenantId, ownerId, trackId: 'account', amount: '100',
|
|
23
|
+
// requestId, source: 'match.finished', origin: matchId,
|
|
24
|
+
// })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`grant`, `grantItem` and `xp` capture currency, item and XP reward intentions. XP rewards identify an explicit track. `defaultXpCurve` and `defaultRewardResolver` are the actual exported slot defaults, and `./testing` calls them directly. A custom slot must provide an explicit strategy `version`; accepted retries do not rerun reward resolution. One operation admits at most 256 detached reward intentions. A large level jump exceeding that bound refuses before accepted value.
|
|
28
|
+
|
|
29
|
+
The `./daily`, `./delivery`, `./delivery/workflow`, `./achievements`, `./resources`, `./quests`, `./quest-port`, `./curves`, `./rewards`, `./strategies`, `./errors`, `./rpc` and `./web` entry points are designed as browser-safe source. SQL adapters and table roles live in `./server`; `./cms` is server-only.
|
|
30
|
+
|
|
31
|
+
## Authority and event origins
|
|
32
|
+
|
|
33
|
+
The installed facade `progression.progression.service()` and request `ProgressionPort` expose the same captured six-method service. `applyXp` and `applyStat` need a system Subject or an explicitly allowed service. Tenant, owner, declared source, independent request identity and event origin are mandatory. There is no player XP/stat value-write route.
|
|
34
|
+
|
|
35
|
+
A SQL subject fence serializes writes across borrowed stores. Track/stat configuration is persisted at admission and cannot silently drift. XP/stat materialization, append-only movement, origin index, owner sequence, terminal operation receipt and queued level rewards commit together. Pending admissions are not accepted value.
|
|
36
|
+
|
|
37
|
+
The operation digest excludes only the delivery request ID. Repeating the same origin/source/definition under another request identity returns the original receipt without new XP/stat movement, sequence or reward. Reusing a request identity or an event origin for changed intent refuses. Lifetime/season statistics support the declared subset of `increment`, `max`, `min`, `set`; a season requires an explicit season ID, while lifetime refuses one. A first `min` starts from its operand, not an invented zero observation.
|
|
38
|
+
|
|
39
|
+
Verified users and guests can read only `progression.mine` and `progression.stats`; the service derives tenant/owner from their Subject. `useProgression` and `useStats` use normal live subscriptions.
|
|
40
|
+
|
|
41
|
+
## SQL truth, repair and privacy
|
|
42
|
+
|
|
43
|
+
Nine roles cover immutable definitions, subject fences, tracks, XP ledger, stats, stat ledger, operations, origin deduplication and queued rewards. `auditProgression` and `rebuildProgression` are available on the installed default SQL facade and standalone `dataStoreProgression`. They validate complete bounded accepted sequences, terminal receipts, origin/content digests, admitted definitions, XP/stat movement and queued reward identity before comparing or repairing materialization. Repair creates no new movement, origin or reward and refuses contradictory history. Strategy/configuration migration and arbitrary metadata mixins remain open; repair currently restores the baseline empty metadata object. Replacement ports have no default SQL repair facade.
|
|
44
|
+
|
|
45
|
+
Default whole-work bounds are 10,000 history/request/reward rows and 4,096 pending reward rows. Overflow refuses complete work; no truncated result is accepted. These are configurable budgets, not measured capacity. All nine registered tables participate in the privacy declaration; shared tenant definitions remain available to peers. Own export contains track/stat values and movements. Erase permanently fences new writes, removes owned materializations, nulls retained owner/origin/actor fields, tombstones request aliases and origin bodies, and removes queued reward payloads. Retained deterministic keys are pseudonymous. Arbitrary actor references, account merge, custom metadata/table wiring and legal retention remain open.
|
|
46
|
+
|
|
47
|
+
## Opt-in quest windows and content
|
|
48
|
+
|
|
49
|
+
Pass `quests` to `progressionPlugin` to install its independent six-method `QuestPort`, `QuestSource`, seven SQL roles, `quests.mine` and `quests.reroll`. With the option absent, those routes, tables and request layers are not installed. The standalone `dataStoreQuests` borrows the app's DataStore; a replacement `quests.service` supplies the same assign/record/reroll/mine/export/erase contract. The example is illustrative configuration.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { defineQuestPool, staticQuestSource, grant } from '@voltro/plugin-progression'
|
|
53
|
+
|
|
54
|
+
const daily = defineQuestPool({
|
|
55
|
+
id: 'daily', version: 1, slots: 1, rerolls: 1,
|
|
56
|
+
refresh: { cron: '0 4 * * *', timezone: 'player' },
|
|
57
|
+
quests: [
|
|
58
|
+
{ id: 'win3', titleKey: 'quests.win3', objectives: [{ stat: 'wins', target: 3 }], reward: [grant('gold', 5)] },
|
|
59
|
+
{ id: 'arena', objectives: [{ event: 'match.finished', where: { mode: 'arena' }, target: 2 }], reward: [] },
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
const quests = {
|
|
63
|
+
source: staticQuestSource([daily]), channel: 'live', locale: 'en',
|
|
64
|
+
eventSources: ['match.finished'], statSources: ['match.stats'],
|
|
65
|
+
}
|
|
66
|
+
// Pass quests to progressionPlugin({ tracks, stats, statSources, quests }).
|
|
67
|
+
// A trusted server job resolves the player's timezone and current window,
|
|
68
|
+
// then calls progression.progression.quests().assign(subject, scopedWindow).
|
|
69
|
+
// Trusted match/stat jobs call its record(subject, scopedOriginAndSignal).
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`defineQuestPool` captures up to 128 unique quests, 1–8 slots, 0–8 rerolls, positive integer weights and typed stat/event objectives. Each quest has 1–16 objectives and at most 64 reward intentions. Targets are positive integers capped at 1,000,000. Event filters contain at most 32 finite scalar fields. The actual exported `defaultQuestSelector` performs deterministic weighted selection without replacement from a **trusted** seed; it is not a security random generator. `questSelector` and `questProgressResolver` are actual pure override slots. A custom slot requires its own immutable strategy version. Outputs are captured and must select distinct known quests, avoid excluded assignments and keep progress within monotonic objective bounds.
|
|
73
|
+
|
|
74
|
+
Assignments persist the complete pool/content revision, strategy version, timezone, window start/end, trusted seed and selected quest definitions. Pool versions cannot silently change gameplay configuration. Existing assignments never reread a changing CMS catalog. Replay of a terminal assignment request returns before source I/O. Another request for the same exact window acknowledges current assignments; changed window/seed/configuration refuses. Overlapping windows for one owner/pool refuse. Windows are explicitly UTC timestamps, at most 366 days; seed fits nonnegative signed SQL INTEGER. `timezone:'player'` requires an explicit server-resolved IANA timezone at assignment. **Without `quests.refresh`, no scheduler is installed.** Cron syntax is validated at declaration; the optional coordinated refresh is described below. Player-profile lookup is supplied through its source port.
|
|
75
|
+
|
|
76
|
+
`staticQuestSource` captures source definitions. `cmsQuestSource` in `./cms` reads only published `quest_pool` rows with explicit tenant, site, channel-to-environment mapping and locale. Register `progressionQuestContentTables` and normal CMS revision/reference roles with the CMS pipeline. `poolId`, version and serialized `definitionJson` must agree with the captured pool. Drafts and other scopes are excluded. A malformed or over-budget whole source refuses. The adapter allows at most 64 pools and 64-KiB definition JSON per row. Typed editor, release promotion, replacement-source CDC and full permission integration remain open.
|
|
77
|
+
|
|
78
|
+
`record` is a trusted operation with an explicit window, source, origin and typed signal. Stat signals are **nonnegative deltas**, not lifetime totals: `{ kind:'stat', stat:'wins', delta:'1' }`; the trusted stat/match workflow must derive them from authoritative movement. Matching events increment their objective by one, respecting `where` filters. An owner/window/source/origin digest prevents new request IDs from double advancing progress. Completion and its bounded reward intentions commit in the same SQL transaction; later signals do not queue the reward again. Delivery requires the optional binding below; `quest.completed` publication remains open.
|
|
79
|
+
|
|
80
|
+
The player `quests.reroll` action accepts only pool/window, owned assignment ID and request identity. It refuses completed, foreign or expired work and used-up allowances, preserves past assignments, excludes all previously selected quests in that window and returns the original accepted receipt on retry. The action descriptor avoids an extra dispatch transaction because the borrowed QuestPort owns its atomic SQL commit. `quests.mine` and `useQuests` expose owned current quests, objective progress, expiry and remaining allowance; reads take a transaction-scoped lock on an existing subject fence without writing or auto-refreshing. Expired windows are hidden on a new read, not automatically pushed at wall-clock expiry yet.
|
|
81
|
+
|
|
82
|
+
Quest privacy uses its own permanent subject fence, preserves shared pool definitions, exports scoped assignments/windows and removes owned materializations on erase. Origin/request bodies and queued reward payloads are tombstoned. It participates in the plugin's combined hook pair and complete table declaration. Default whole-work budget is 10,000 windows, assignments, requests, origins or rewards **per table/owner**, configurable up to 100,000; whole overflow refuses. This is a budget, not measured capacity.
|
|
83
|
+
|
|
84
|
+
## Coordinated automatic refresh
|
|
85
|
+
|
|
86
|
+
`quests.refresh` optionally installs `QuestPlayerSource`, `QuestRefreshPort` and the framework's `scheduleCoordinated` task. It is a meta-worker, outside all Room ticks. A bind without the real scheduler context refuses; deactivation stops the handle and fences the public facade. With no refresh option, no task or refresh Layer is installed. The supplied player source owns verified profile/timezone acquisition and must return the complete bounded roster; it must not silently truncate. The later Players adapter is not wired yet.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { progressionPlugin, type QuestPlayerSourceService, type QuestSourceService } from '@voltro/plugin-progression'
|
|
90
|
+
|
|
91
|
+
export const questProgression = (players: QuestPlayerSourceService, source: QuestSourceService) => progressionPlugin({
|
|
92
|
+
tracks: [],
|
|
93
|
+
quests: {
|
|
94
|
+
source, channel: 'live', locale: 'en', eventSources: ['match.finished'],
|
|
95
|
+
refresh: { players, intervalMs: 60_000, maxPlayers: 1_000 },
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The default window slot uses Effect Cron parsing and timezone-aware previous/next firings, includes the current boundary second, and captures the player's IANA zone or the pool's fixed zone. Refresh windows must last 1 minute–366 days. `defaultQuestWindow` is the actual pure exported slot; `defaultQuestSeed` in `./server` is the actual deterministic trusted seed slot. Seed derivation is not a security RNG. Custom `questWindow`/`questSeed` slots need an immutable refresh strategy version. A replacement `refresh.service` implements `refreshOwner`/`refreshAll`; a replacement `refresh.players` implements the trusted roster read. No player refresh/write RPC is added.
|
|
101
|
+
|
|
102
|
+
`progression.progression.questRefresh().refreshOwner(subject, player)` and `refreshAll(subject)` require a system or explicitly allowed service Subject. Reads from profile/content ports stay outside SQL transaction callbacks. Stable tenant/owner/pool/version/window/rule request IDs return original SQL assignment receipts across task retries/rebinds. An `expectedPoolDigest` fences a changed catalog between planning and SQL acceptance. Bounds are 1,000 complete roster entries by default, configurable up to 10,000, and a 60,000-ms task interval by default, configurable from 1,000–3,600,000 ms. These are budgets, not measured capacity. Reports count acknowledged assignment calls, including retries; they do not claim newly created quests. Erased owners remain fenced while peer refresh continues; other per-player failures produce red task outcomes.
|
|
103
|
+
|
|
104
|
+
This is a coordinated polling task with SQL idempotency, not yet the Plan13 durable cron-workflow/history path. Overlapping changed windows refuse; timezone changes cannot silently rewrite active assignments.
|
|
105
|
+
|
|
106
|
+
## Quest UI and reroll recovery
|
|
107
|
+
|
|
108
|
+
`useQuestReroll` in `./web` invokes the actual action. `QuestCard` and `QuestList` in `./components` inherit the UI kit's unstyled semantic markup and CSS data hooks. They show objectives with accessible progress elements, actual completion state, expiry and remaining rerolls. `renderTitle` supplies translated content titles; kit labels use `UiStringsProvider`, with exported `questUiStrings.en/de`. Loading/error/empty are distinct; invalid observations hide quest content. The list locally hides expired entries at their deadline without writing, granting, or claiming an automatic server expiry push.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { QuestList } from '@voltro/plugin-progression/components'
|
|
112
|
+
import { UiStringsProvider, questUiStrings } from '@voltro/ui'
|
|
113
|
+
|
|
114
|
+
export function PlayerQuests({ scope }: { readonly scope: string }) {
|
|
115
|
+
return <UiStringsProvider strings={{ quests: questUiStrings.de }}>
|
|
116
|
+
<QuestList invocationScope={scope} locale="de" />
|
|
117
|
+
</UiStringsProvider>
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`scope` is an opaque stable identity for the verified app/API/credential owner, never a credential or raw player ID. Mount the list only within that verified context; change scope on identity/API changes. Consumer credential/cache behavior still needs browser proof. `QuestList` uses the actual own live query and a replaceable `QuestRerollJournal`. The browser default requires durable localStorage and native Web Locks, persists a bounded request identity before sending, and retains one accepted receipt per scope. An uncertain response reuses the same nonce; it blocks a different reroll until resolved. “Check previous reroll” recovers the original intent even if its old quest has already disappeared from the live list. A confirmed stale-tab retry returns the retained receipt without another transport call. No optimistic progress, currency or reward is invented. Browser journal retention/account-delete cleanup and native credential-scope integration remain open.
|
|
122
|
+
|
|
123
|
+
The journal's actual-port fixtures cover a committed response loss, re-created UI journal, concurrent admissions, malformed confirmation, unavailable storage and erased-owner recovery. DOM fixtures cover EN/DE, progress, pending focus/admission, expiry, error/empty and saved-request recovery. All are authored, not executed; no browser-support or capacity claim follows.
|
|
124
|
+
|
|
125
|
+
## Lazy energy and stamina
|
|
126
|
+
|
|
127
|
+
`resources` opts into a six-method `RegenResourcePort`, six SQL roles and the owned read query `resources.mine`/`useResources`. With the option absent, none of those roles/routes/Layers is registered. `defineRegenResource` captures ID/version, exact signed64-compatible cap/initial/regen amount, Effect-parsed positive duration and an optional `refillOffer` reference. Example values below are illustrative app-owned configuration; no price or shop fulfillment is attached to that offer reference.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { progressionPlugin, defineRegenResource } from '@voltro/plugin-progression'
|
|
131
|
+
|
|
132
|
+
const progression = progressionPlugin({
|
|
133
|
+
tracks: [],
|
|
134
|
+
resources: {
|
|
135
|
+
definitions: [defineRegenResource({ id: 'energy', max: '100', regenPer: '6 minutes' })],
|
|
136
|
+
sources: ['match.started', 'players.created', 'refill.applied'],
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
// A trusted pre-match application job, outside every Room tick:
|
|
140
|
+
// progression.progression.resources().consume(subject, {
|
|
141
|
+
// tenantId, ownerId, resourceId: 'energy', amount: '1',
|
|
142
|
+
// requestId, source: 'match.started', origin: matchId,
|
|
143
|
+
// })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`defaultResourceRegen` is the actual exported pure slot; `resolveResourceRegen` admits its captured output. Whole elapsed intervals credit exact integer units, fractional interval time remains in the anchor, and reaching the cap discards accumulated credit. A custom `resources.strategies.regen` needs an explicit version and cannot lower value, exceed the cap or move its anchor backwards/beyond the observation. No regeneration job or HotStore/tick call is installed. The pure `./resources` entry includes the contract/tag/default; native tables/adapter/repair stay in `./server`.
|
|
147
|
+
|
|
148
|
+
The installed facade exposes `initialize`, `consume`, `grant`, `mine`, `exportSubject`, `eraseSubject`. Value writes require a system or explicitly allowed service Subject and a declared authoritative source/origin; players receive only their read route. Owner, definition, value, sequence, movement, receipt and origin admission share the borrowed SQL transaction. Insufficient consumption and capped-grant overflow refuse the whole value operation. A new delivery nonce for the same origin returns its original receipt, with no further value/regeneration; changed accepted intent conflicts. Inputs are captured before Effect laziness. A materialization missing or differing from accepted history cannot be used as fresh starting value.
|
|
149
|
+
|
|
150
|
+
Before first accepted write, an unknown owner reads the declared initial value without inserting a row. Default initial equals cap. A nonfull initial resource requires a trusted `initialize` on player creation to establish its clock; reads alone never start or persist regeneration. Reads lazily derive availability from committed state/anchor and server time, take a read lock on an existing subject fence, and perform no write. Values are observations at `observedAt`; live-query invalidation is table-driven, so time passing alone is not an automatic server push. Backwards server time refuses. Consumption at match start still needs Room/seat failure compensation; refill purchase/fulfillment is not installed.
|
|
151
|
+
|
|
152
|
+
The standalone `dataStoreResources` and installed default facade expose `auditResources`/`rebuildResources`. They validate complete owner sequences, accepted/aliased receipts, source/origin digests, admitted configuration, exact regeneration/debit/grant conservation and before/after-images before comparing or restoring stock. Repair uses SQL after-images and never reruns a custom historical strategy or emits new value/movement. Default complete per-owner/table history budget is 10,000, configurable up to 100,000; declarations are bounded to 64 resources and 64 trusted sources. Overflow refuses, with no truncated acceptance. These are budgets, not measured capacity.
|
|
153
|
+
|
|
154
|
+
All six registered resource roles join the combined privacy hook pair. Export includes owned stored resources and movements. Erase removes stock, nulls ownership/source/origin/after-image bodies, tombstones original/alias receipts and origins, and preserves a permanent pseudonymous no-recreation fence. Shared tenant definitions and peers remain. Replacement `resources.service` supplies the same six-method port; default tables and default SQL repair are omitted.
|
|
155
|
+
|
|
156
|
+
Native cases cover all six roles, signed64 extremes, independent pools/concurrency, missing-stock rebuild and scoped repeated erase. Four inspect configuration layers show declared caps/rules/sources/slots/budgets without player values; shared/Cloud decoder and EN/DE DOM fixtures match.
|
|
157
|
+
|
|
158
|
+
## Achievements from accepted stat history
|
|
159
|
+
|
|
160
|
+
`achievements` optionally installs `AchievementPort`, `AchievementStatSource`, six SQL roles, the owned `achievements.mine` query and `useAchievements`. With the option absent, those routes/tables/Layers are omitted. `defineAchievement` captures immutable ID/version, a base condition, optional independent tier rewards, hidden-until-unlocked behavior and Steam/Game Center/Play Games ID mappings. Additional tiers require their own condition **and the base condition**. `statAtLeast` uses exact positive signed64-compatible thresholds and explicit lifetime/season scope; season scope requires a season ID. `composite` combines conditions with `all` or `any`. The following balancing and mapping are illustrative app configuration, not platform provisioning.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { progressionPlugin, defineAchievement, statAtLeast, composite, grant } from '@voltro/plugin-progression'
|
|
164
|
+
|
|
165
|
+
const progression = progressionPlugin({
|
|
166
|
+
tracks: [], stats: [{ id: 'kills' }, { id: 'wins' }], statSources: ['match.finished'],
|
|
167
|
+
achievements: {
|
|
168
|
+
definitions: [defineAchievement({
|
|
169
|
+
id: 'arena.veteran', version: 1, hidden: true,
|
|
170
|
+
condition: composite([statAtLeast('kills', '100'), statAtLeast('wins', '10')]),
|
|
171
|
+
reward: [grant('gold', '5')], platform: { steam: 'ACH_ARENA_VETERAN' },
|
|
172
|
+
tiers: [{ id: 'master', condition: statAtLeast('kills', '1000'), reward: [] }],
|
|
173
|
+
})],
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
// Accepted trusted applyStat calls through progression.progression.service()
|
|
177
|
+
// then evaluate achievements. Players only read achievements.mine.
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The default stat source shares the core adapter's complete bounded accepted-history validator, including XP sequence gaps, stat operator/conservation, terminal receipts, origins and definition identity. It reads the existing owner fence without seeding or updating it, returns immutable source version/initial stats/ordered stat after-images and never trusts a player's current state. Evaluating the entire accepted history retains transient threshold crossings even when a later authoritative `set`/`min` lowers a stat. Queries display the latest accepted evaluation, not an automatic recomputation on reads. Seasonal conditions start at zero until the source supplies that season's first after-image; lifetime initial values come from the stat declaration.
|
|
181
|
+
|
|
182
|
+
The installed six-method Progression facade evaluates achievements **after** its accepted `applyStat` commit. Stat and achievement evaluation are two separate SQL commits. If source acquisition or evaluation fails, the stat may already have committed; retry the same authoritative stat origin/request. It returns the original stat receipt and retries the stable evaluation request. Standalone `dataStoreProgression.applyStat` does not install that plugin facade: its caller must invoke `dataStoreAchievements.evaluate` separately. Raw SQL changes do not become accepted stat events, and no stat workflow/subscriber or `achievement.unlocked` event is installed yet.
|
|
183
|
+
|
|
184
|
+
`AchievementPort` exposes four actual methods: `evaluate`, `mine`, `exportSubject`, `eraseSubject`. Evaluation requires a system or explicitly admitted service Subject; default plugin admission inherits the core service allowlist. The default source is the bound core SQL service. A replacement core Progression service requires an explicit `achievements.source`; there is no hidden SQL fallback. Source methods and service methods are captured before binding. A replacement `achievements.service` supplies the same four-method contract and omits default achievement SQL roles/repair; replacement table/CDC/privacy ownership remains open.
|
|
185
|
+
|
|
186
|
+
Source I/O and pure evaluation planning run outside the SQL callback. A permanent owner fence is checked before and after acquisition, catching an erase during a source read. Immutable source version and accepted-prefix digest refuse source rollback or changed history. The owner evaluation sequence, unlocked materialization, append-only unlock ledger, terminal receipt, latest progress and queued reward intentions commit together. Deterministic tenant/owner/achievement/version/tier unlock IDs prevent another reward under a different evaluation nonce or another native pool. A terminal request returns before source I/O. Changed declaration/rule versions refuse; migration is not installed.
|
|
187
|
+
|
|
188
|
+
`defaultAchievementEvaluator` is the actual exported pure strategy slot. `resolveAchievementEvaluation` captures the output and requires truthful exact threshold progress. A custom `achievementEvaluator` may change fulfillment but cannot invent stat values, targets or progress; it requires an explicit strategy version. Both functions, DSL, types and tags live in the browser-safe `./achievements` source entry. Condition budgets are depth 8 and 64 leaves, 32 tiers per definition, 128 definitions and 256 total tiers. Stat snapshots are bounded to 4,096 distinct scopes. Default whole history/evaluation rows are 10,000 per table/owner, configurable to 100,000; reward-intent rows default to 4,096, configurable to 10,000. These are budgets, not measured capacity; complete overflow refuses instead of truncating.
|
|
189
|
+
|
|
190
|
+
`auditAchievements`/`rebuildAchievements` on the default standalone/installed SQL facade validate admitted definitions, complete evaluation sequence/receipts, unlock identities/tier/config/progress, source bounds, queued reward identity and retained views before comparing/restoring materialization. Rebuild uses accepted SQL unlock/evaluation journals, creates no new reward and never calls a historical custom evaluator or source port. This is journal repair, not a fresh external stat-source attestation. Contradictory journals refuse. All six registered roles join combined plugin privacy. Export includes own unlocks, evaluation receipts, proofs and queued rewards; erase removes unlock stock, nulls retained ownership/proof/view/reward bodies, tombstones receipts and preserves a pseudonymous permanent no-recreation fence. Peers and shared definitions remain.
|
|
191
|
+
|
|
192
|
+
`AchievementCard`/`AchievementList` in `./components` use semantic ejectable markup, exact decimal labels and bounded display ratios. `renderTitle` supplies content translations; `UiStringsProvider` accepts `achievementUiStrings.en/de`. Loading, error, invalid, empty and actual owned live observations differ. A stored unlock remains visible after stat decreases; a progress bar cannot grant it. Hidden locked entries are omitted by the server. Mount the list in a verified owner/API context and pass an opaque stable `invocationScope` that changes with that identity, never a credential. These components do not invoke platform SDKs.
|
|
193
|
+
|
|
194
|
+
**Without configured delivery, rewards remain queued intentions. Platform mappings are data, not platform synchronization.**
|
|
195
|
+
|
|
196
|
+
## Login calendars and timezone streaks
|
|
197
|
+
|
|
198
|
+
`daily` opts into the actual four-method `LoginCalendarPort`, `DailyProfileSource`, six SQL roles, `daily.state` and `progression.claimDaily`. Absent configuration installs none of those roles/routes/Layers. `defineLoginCalendar` captures ID/version, 1–366 reward days, at most 64 intentions per day and 0–30 protected missed days. Each day is a reward array, including an explicitly empty one. All values below are illustrative app-owned configuration and configured budgets, not capacity measurements.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { progressionPlugin, defineLoginCalendar, grant,
|
|
202
|
+
type DailyProfileSourceService } from '@voltro/plugin-progression'
|
|
203
|
+
|
|
204
|
+
export const calendarProgression = (profileSource: DailyProfileSourceService) => progressionPlugin({
|
|
205
|
+
tracks: [],
|
|
206
|
+
daily: {
|
|
207
|
+
profileSource,
|
|
208
|
+
calendars: [defineLoginCalendar({ id: 'daily',
|
|
209
|
+
days: [[grant('gold', '5')], [grant('gold', '7')], [grant('gold', '9')]],
|
|
210
|
+
streakProtection: 1,
|
|
211
|
+
})],
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
// A verified player sends only { calendarId: 'daily', requestId }.
|
|
215
|
+
// The borrowed port owns the SQL transaction; claimDaily is an action.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`profileSource.read(subject, { tenantId, ownerId })` must obtain a trusted timezone and source version from the owning profile system; no client timezone is admitted. There is no fallback profile source. The production Players adapter is not wired yet. Effect Cron calculates actual local-midnight boundaries, including 23/25-hour DST days; civil-date ordinals determine missed days, not elapsed 24-hour periods. The first accepted claim pins the timezone per calendar/owner. Profile timezone changes or backwards dates refuse until explicit migration, which is not implemented. Equivalent zone aliases are not silently migrated.
|
|
219
|
+
|
|
220
|
+
The player claim accepts only calendar ID and independent request identity. Tenant/owner comes from verified user/guest Subject. A permanent owner fence and immutable calendar/rule admission serialize acceptance. Stock, complete claim after-image, owner sequence, terminal request receipt and queued rewards commit together. A separate deterministic tenant/owner/calendar/version/civil-day claim identity deduplicates new request nonces; aliases return the original receipt without another reward. A terminal nonce returns before profile I/O or current-clock recomputation, including after the calendar day changes. Profile I/O occurs outside SQL callbacks; an erase between profile acquisition and acceptance remains fenced. Source reads take an existing owner lock without writes and do not seed unknown owners.
|
|
221
|
+
|
|
222
|
+
`defaultDailyClaimPolicy` is the actual pure exported slot. It advances one reward position per accepted claim, cycles the reward list, consumes one protection unit per missed civil day and counts accepted claims in the streak. A gap larger than remaining protection starts a new cycle at day zero/streak one with renewed protection. Protection renews on a completed reward cycle too. A custom `claimPolicy` requires its own immutable strategy version and returns bounded position/streak/protection/cycle; it cannot choose timezone, date, owner or reward payload. Queries show the last accepted streak and the selected current-day reward/protection projection; observations alone grant nothing. `useDaily` and `useClaimDaily` use normal query/action transport. Time passing alone has no SQL invalidation/new live push: refresh the observation at its boundary.
|
|
223
|
+
|
|
224
|
+
Default SQL `auditDaily`/`rebuildDaily` validates complete owner sequences, admitted calendar/rule definitions, receipt/day/claim identities, before/after continuity, aliases and exact captured reward/delivery evidence before restoring stock. Custom historical policies and profile ports are never rerun during repair. Default policy conservation is validated from its captured inputs. Missing stock with accepted history refuses further new-day claims until repair; it cannot start another first day. Contradictory journals refuse. Whole-work rows default to 10,000 per owner/table and can be configured up to 100,000; overflow refuses complete work. These are budgets, not measured capacity.
|
|
225
|
+
|
|
226
|
+
All six roles join combined plugin privacy. Export includes owned states, claims/profile versions, receipts and reward/delivery bodies. Erase removes stock, clears retained owner/before/after/profile/request/reward/delivery bodies and preserves a permanent no-recreation fence; peers and shared definitions remain. Retained deterministic IDs, civil dates and event timestamps are pseudonymous history, not an anonymization or legal-retention claim. Source erase and external delivered value still require all destination hook pairs. `daily.service` captures a real four-method replacement and omits default SQL roles/repair.
|
|
227
|
+
|
|
228
|
+
Configured reward delivery includes the `daily` source kind and validates its committed claim/calendar parent before real Economy/Inventory/XP fulfillment. A claim receipt confirms queued intentions, not already delivered value. Four inspect layers show calendar counts, protection budget, profile-source selection, actual policy/version and explicit timezone-migration/time-push limits, without player timezone/streak values. The thin template explicitly assigns UTC to verified demo owners through an app-owned profile policy; it does not pretend to read production Players profiles.
|
|
229
|
+
|
|
230
|
+
Native sources cover every role, exact reward JSON, independent pools, DST next-day acceptance, stock repair and scoped erase.
|
|
231
|
+
|
|
232
|
+
## Optional durable reward delivery
|
|
233
|
+
|
|
234
|
+
`delivery` opts into the actual two-method `RewardDeliveryPort`, the internal `progression.deliverReward` workflow and the UTC `progression.dispatchRewards` minute schedule through the plugin's normal source contribution. With no option, no delivery Layer, source contribution, workflow or schedule is installed. The example's currency, rewards and explicit funding account are illustrative app-owned configuration, not deployment prices.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { defineCurrency, economyPlugin } from '@voltro/plugin-economy'
|
|
238
|
+
import { progressionPlugin, levelCurve, grant } from '@voltro/plugin-progression'
|
|
239
|
+
|
|
240
|
+
const economy = economyPlugin({
|
|
241
|
+
currencies: [defineCurrency({ id: 'gold', kind: 'soft' })], realMoney: false,
|
|
242
|
+
})
|
|
243
|
+
const progression = progressionPlugin({
|
|
244
|
+
tracks: [{ id: 'account', xpCurve: levelCurve({ base: 100, growth: 1.15, maxLevel: 5 }),
|
|
245
|
+
sources: ['match.finished'], rewards: { 2: [grant('gold', '5')] } }],
|
|
246
|
+
delivery: { systemAccount: 'progression-rewards', economy: economy.economy.service() },
|
|
247
|
+
})
|
|
248
|
+
// Install both actual instances: plugins: [economy, progression].
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`delivery.economy`, `delivery.inventory` and `delivery.progression` are the actual destination service surfaces, with methods captured before binding. XP defaults to the installed Progression facade; the target track must explicitly allow `progression.reward`. Currency requires the configured Economy port; items require the configured Inventory port and use its captured catalog instance defaults. A missing required destination refuses and leaves the reward pending. A destination change after an uncertain grant may conflict with its accepted intent; configuration migration remains open. Replaced core/quest/achievement backends require a complete `delivery.service` replacement; there is no implicit SQL fallback. That replacement owns its source queues/privacy and implements `execute`/`dispatch`.
|
|
252
|
+
|
|
253
|
+
Trusted `progression.progression.delivery().execute(subject, { kind, rewardId })` resolves a committed level/quest/achievement/daily/pass/tutorial reward from SQL. Players cannot choose amount, target owner, reward or funding account. It validates the source parent, pins a detached intent and digest in the existing reward row under its permanent owner fence, commits that admission, calls the destination **outside** the SQL callback, then commits the redacted delivery receipt under the source fence. These are separate transactions, not a distributed atomic transfer. Stable source-kind/reward identity supplies the same permanent destination request/origin across retries and workers. A lost destination reply keeps the source pending; replay uses that accepted identity. A terminal delivery returns before another destination call. Funding-account/binding-version drift after admission refuses. Delivery evidence is nullable server-only JSON/digest/receipt on the existing four reward roles; no extra table is introduced.
|
|
254
|
+
|
|
255
|
+
The workflow has an immutable reward idempotency key, concurrency budget 4, activity attempt budget 5 and `suspendOnFailure:true`. Exhausted failures suspend it; operators must resolve the cause and resume the existing workflow through normal workflow operations. Repeated `workflows.start`/queue scans do **not** automatically resume it. The minute schedule scans SQL pending rows by keyset cursor in at most 100 pages of 100 rows, starts peer workflows despite individual start failures and reports incomplete/failed scans. Start handles acknowledge engine admission/status, never delivery success. These are configured budgets, not throughput or runtime proofs.
|
|
256
|
+
|
|
257
|
+
Export now includes owned reward state, pinned intent and delivery receipt when reward history exists. All four source erase paths remove those bodies/digests/timestamps and preserve permanent no-recreation fences. A source erase during or after an already committed external grant cannot atomically undo that grant; complete deletion must run the destination Economy/Inventory/Progression hook pairs too. Queue/workflow opaque IDs remain pseudonymous. Default core/achievement journal audit/rebuild validates retained delivery evidence without calling a destination; it cannot attest external delivery. Full cross-system erase ordering/crash convergence, destination privacy/replacement wiring, queue fairness above the scan budget and legal retention remain open.
|
|
258
|
+
|
|
259
|
+
Fixture source covers actual currency conservation/receipts, committed response loss and adapter reopening, concurrent workers, actual catalog item/XP delivery, quest/achievement source parents, source erase during a destination commit, corrupt-evidence repair refusal, SQL cursor/start failures and descriptor admission. Three native scenarios are authored for each of six dialects, with real destination tables/receipt JSON/reopened pools/privacy; missing connection fixtures refuse. Four inspect layers show captured configuration and explicitly explain suspension. The thin template installs virtual-only Economy delivery.
|
|
260
|
+
|
|
261
|
+
## Season-pass claims and trusted premium fulfillment
|
|
262
|
+
|
|
263
|
+
The browser-safe `./passes` entry exports `defineSeasonPass`, `SeasonPassPort`, the typed seven-method service and default `passCatchUp` rule. A definition captures explicit UTC season dates, 1–256 tiers, exact positive XP cost, equally sized free/premium reward arrays, admitted XP sources, optional premium offer, catch-up, auto-claim and expiry choice. Each tier has a budget of 64 reward entries. Dates, amounts and the multiplier below are illustrative app configuration, not Owner prices or measured limits.
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { defineSeasonPass } from '@voltro/plugin-progression/passes'
|
|
267
|
+
import { grant } from '@voltro/plugin-progression/rewards'
|
|
268
|
+
|
|
269
|
+
const pass = defineSeasonPass({
|
|
270
|
+
id: 'season-3', version: 1,
|
|
271
|
+
from: Date.UTC(2026, 9, 10), until: Date.UTC(2026, 10, 9),
|
|
272
|
+
tiers: 3, xpPerTier: '100', sources: ['match.finished'],
|
|
273
|
+
tracks: {
|
|
274
|
+
free: [[grant('gold', '5')], [], [grant('gold', '9')]],
|
|
275
|
+
premium: [[grant('gold', '10')], [], [grant('gold', '20')]],
|
|
276
|
+
},
|
|
277
|
+
premiumOffer: 'offer.pass-s3',
|
|
278
|
+
catchUp: { xpBoostAfterDay: 20, multiplier: 2 },
|
|
279
|
+
autoClaim: false, expireUnclaimed: 'forfeit',
|
|
280
|
+
})
|
|
281
|
+
// Add passes: { definitions: [pass] } to the existing progressionPlugin options.
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`passes: { definitions: [pass], strategies: { passCatchUp, version } }` configures the optional system. `defaultPassCatchUp` keeps base XP until the configured elapsed UTC day, then multiplies by the configured integer (default 2 when the catch-up declaration omits it). It does not use player timezone or float XP. Overrides require an immutable rule version; `resolvePassCatchUp` captures and bounds their exact positive output. The `/testing` entry supplies a pure pass fixture. XP saturates at the last tier; original and effective amounts remain in accepted journals.
|
|
285
|
+
|
|
286
|
+
The seven-method `SeasonPassPort` exposes trusted `applyXp`, trusted `grantPremium`, owned `claim`, trusted `close`, `mine` and export/erase. Pass XP accepts `{ tenantId, ownerId, passId, amount, source, originId, requestId }` only from system subjects or explicitly admitted services. `grantPremium` requires a matching configured `{ offerId, fulfillmentId }` and the same trusted authority. It is the server fulfillment seam; **Shop receipt verification or a purchase flow is not installed**. Do not call it from a player request that merely asserts a successful purchase. Another fulfillment identity after premium is already accepted refuses; a duplicate accepted identity replays.
|
|
287
|
+
|
|
288
|
+
`pass.claim` is an owned Action receiving only `{ passId, tier, track: 'free'|'premium', requestId }`. The adapter derives tenant/owner from the verified user or guest, checks the active season and accepted XP, and requires accepted premium for that track. A player cannot supply XP, purchase state, claimed masks or rewards. Request identity is intent-bound; an independent source-origin/fulfillment/tier identity deduplicates a new nonce. Replays return their original receipt before clock/season/catch-up processing. Reusing a source origin with a different amount conflicts.
|
|
289
|
+
|
|
290
|
+
Free and premium masks are separate exact decimal strings for up to 256 bits. Empty reward tiers still acquire a claim bit once. A tier can be claimed only after its XP threshold. `autoClaim:true` claims all reached eligible tiers in the accepting XP or premium transaction, including previously reached premium tiers after trusted fulfillment. Masks and queued reward intentions commit with state, accepted journal, receipt and permanent owner sequence. Queued intention is not delivered value. Configured reward delivery includes source kind `pass` and validates the committed pass parent before calling actual Economy/Inventory/XP ports outside SQL callbacks.
|
|
291
|
+
|
|
292
|
+
`pass.mine` / `useSeasonPasses` and `usePassClaim` expose owned views/intents. Reads never persist a clock or close a season; elapsed time alone does not push a live observation. At or after `until`, new XP/premium/player claims refuse. Explicit trusted `close` implements `forfeit` with a permanent receipt and no extra reward; already accepted reward intentions stay queued. **Automatic expiry workflow and Inbox integration are absent.** `expireUnclaimed:'mail'` remains captured configuration and close returns `unavailable`; it never silently forfeits or pretends to send mail. Clock reversal refuses.
|
|
293
|
+
|
|
294
|
+
Six default SQL roles store immutable definitions/rule versions, a permanent owner fence, `_voltro_prog_pass` materialization, accepted transition journals, permanent request/origin receipts and tier reward intentions. Missing stock with accepted history requires explicit `auditPasses`/`rebuildPasses` through the server facade. Repair validates full ordered journals, origin/digest identities, pure accepted transitions, tier reward conservation and retained delivery evidence, then restores only state. It does not rerun historical custom catch-up callbacks, create another gift or attest an external destination. Definitions cannot mutate under an accepted version; explicit content migration remains open.
|
|
295
|
+
|
|
296
|
+
The whole-work defaults are budgets of 10,000 history rows and 10,000 retained reward rows per owner, configurable up to 100,000 each. Overflow refuses whole work. Export includes owned state/history/receipts and delivery bodies. Erase removes stock, clears owner links/fulfillment/intent/after-image/receipt/reward/delivery bodies and retains a permanent owner fence and pseudonymous deterministic identifiers. Complete privacy must also erase destination systems; clearing this source cannot undo an already committed gift.
|
|
297
|
+
|
|
298
|
+
`passes.service` captures an actual full replacement port and omits the six default roles and default SQL repair. Default SQL reward delivery refuses a replaced pass backend; supply a full delivery replacement rather than an implicit SQL fallback. Four inspect layers show exact role parity, declared seasons, slot selection/version and truthful Shop/Inbox/expiry flags, without player XP or purchase references.
|
|
299
|
+
|
|
300
|
+
Host decoders, EN/DE DOM and a thin free-pass template are also authored.
|
|
301
|
+
|
|
302
|
+
## Tutorial intentions and authoritative progress
|
|
303
|
+
|
|
304
|
+
The pure `./tutorial` entry exports `defineTutorial`, `TutorialPort`, `TutorialVerifier`, the exported default step policy and typed contracts. Definitions capture an ID (default `tutorial`), immutable version, 1–128 unique sequential step IDs, explicit skip permission, up to 64 completion reward entries, `rewardOnSkip:false` by default and an optional hints content ID. The hints ID is a reference; it does not install CMS content delivery. Reward values below are illustrative app configuration.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { defineTutorial } from '@voltro/plugin-progression/tutorial'
|
|
308
|
+
import { grant } from '@voltro/plugin-progression/rewards'
|
|
309
|
+
|
|
310
|
+
const tutorial = defineTutorial({
|
|
311
|
+
id: 'tutorial', version: 1, steps: ['move', 'shoot', 'shop', 'friend'],
|
|
312
|
+
skippable: true, reward: [grant('gold', '5')], rewardOnSkip: false,
|
|
313
|
+
hints: 'tutorial-hints',
|
|
314
|
+
})
|
|
315
|
+
// Add tutorials: { definitions: [tutorial], eventSources: ['game.authority'] }
|
|
316
|
+
// to the existing progressionPlugin options. A player step needs an explicit verifier.
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`tutorials: { definitions, eventSources, verifier?, strategies: { stepPolicy, version } }` installs the source. Its six-method `TutorialPort` offers `completeStep`, trusted `record`, `skip`, `mine`, export and erase. The installed Action `tutorial.completeStep` accepts only `{ tutorialId, step, requestId }`, derives tenant/owner from verified user or guest and asks the explicit `TutorialVerifier.verify(subject, { tenantId, ownerId, tutorialId, version, step, requestId })` for authoritative evidence. That call happens outside SQL callbacks, after terminal-request/step preflight. Evidence has `{ source, originId, version, from, until }`; admission requires its declared source and valid server-time interval. No client completion boolean, screenshot or submitted progress is treated as proof. Missing verifier returns `unavailable`; there is no permissive default.
|
|
320
|
+
|
|
321
|
+
Trusted Room/server callbacks use `record(subject, { tenantId, ownerId, tutorialId, step, requestId, source, originId })` from a system subject or explicitly admitted service, with a declared source. Call the port outside every tick. This is an application bridge, not a connected Room-event subscriber. Different request IDs for an already accepted step return the original receipt; repeating a terminal request happens before clock/verifier work. An independent accepted evidence-origin fence refuses using the same event for the next step. Request identity cannot change tutorial/step/mode/explicit origin. Sequential order remains a system invariant; the pure `stepPolicy` slot can add a rule, deny a step or compose the exported default, but cannot invent skipped steps. Overrides require an immutable rule version and boolean result. `/testing` provides its pure fixture.
|
|
322
|
+
|
|
323
|
+
`tutorial.skip` accepts tutorial/request only. A captured `skippable:true` permits an owned permanent skip receipt; the accepted prefix stays unchanged, `skipped` is explicit and no unperformed steps are fabricated. Default skip grants no completion reward. `rewardOnSkip:true` explicitly queues the same one-time completion rewards; completion and skip cannot both mint them. Already complete tutorials refuse a new skip. `tutorial.mine` / `useTutorials`, `useTutorialStep` and `useTutorialSkip` expose owned progress and intentions. Reads never write or trigger verification. A hints ID remains data, not rendered CMS content.
|
|
324
|
+
|
|
325
|
+
Six default SQL roles hold immutable definitions/rules, permanent owner fence, `_voltro_prog_tutorial` state, ordered intent/evidence/before/after journal, request/step aliases and queued completion rewards. Step/skip, prefix/terminal state, sequence, receipt and final reward intentions commit together under one owner fence. Verification failure or erase during proof acquisition cannot recreate a fenced owner. Reward delivery source kind `tutorial` admits the committed completion parent and uses the real Economy/Inventory/XP ports between source transactions. A queued intention is not a delivered gift; source-only erase cannot undo an already accepted external value.
|
|
326
|
+
|
|
327
|
+
`auditTutorials` / `rebuildTutorials` through the server facade validate full ordered accepted state, rule/definition admission, prefix transitions, unique claim/evidence origins, request alias/digest identities, reward conservation and retained delivery evidence. Missing stock requires this repair; it never restarts a completed tutorial. Rebuild does not call historical custom policy/verifier or mint another reward. It validates accepted evidence, without freshly attesting gameplay. Defaults of 10,000 history and 10,000 retained reward rows per owner, each configurable to 100,000, are whole-work budgets; overflow refuses rather than truncating. They are not measured capacity.
|
|
328
|
+
|
|
329
|
+
Export contains own state, accepted proof/transition/receipt bodies and reward/delivery evidence. Erase removes stock, nulls owner links and intent/proof/after-image/receipt/reward/delivery bodies and retains permanent pseudonymous fences/claim IDs. Destination privacy remains required. `tutorials.service` accepts a full six-method replacement, omits default roles/SQL repair and requires full delivery replacement when reward delivery is enabled. No implicit default SQL path remains behind a replaced port.
|
|
330
|
+
|
|
331
|
+
Four inspect source layers carry definitions, default/custom step policy/version, source names, actual verifier binding (`absent`, `custom` or `customPort`) and exact role parity, without player state or proof values. **Telemetry events/funnels, variant experiments, CMS hint delivery and Room join-context 3 are not installed.** The thin template derives `win1`/`win2` from actual accepted match-stat receipts, sends trusted tutorial events and queues a virtual completion gift; player verification is explicitly unconfigured.
|
|
332
|
+
|
|
333
|
+
Three native scenarios per six SQL dialects use actual schema/JSON, reopened independent pools, completion/skip/rebuild/delivery/erase. Factory/privacy, strict host decoder and EN/DE DOM/template sources are written.
|
|
334
|
+
|
|
335
|
+
## Inspection and remaining work
|
|
336
|
+
|
|
337
|
+
Four written inspect layers show process binding, default/replacement service, actual slot selection/version, declared tracks/stat rules, exact caps and whole-work budgets. The `/status` endpoint, strict shared/Cloud decoders, shared page, Local selected-plugin/replica provider and tenant-authorized Cloud proxy contain no player values, origin IDs or reward payloads. Reload remains focusable during pending reads with an actual admission guard; failed observations hide stale configuration. Player lookup, reward aggregates and authorized GM actions are not built.
|
|
338
|
+
|
|
339
|
+
**Reward delivery requires explicit configuration.** Durable engine/crash evidence, events/animation, durable quest cron-workflows/story chains, achievement platform synchronization, complete rank integration, Shop/premium purchase and Inbox/expiry integration, verified daily/timezone migration, complete resource integration, tutorial telemetry/experiments/hints/join integration, full template integration, schema mixins/removal and full `systemContractConformance` remain required work.
|
|
340
|
+
|
|
341
|
+
## Rank ladders: explicit rating projection
|
|
342
|
+
|
|
343
|
+
`./ranks` captures a ladder and calculates its displayed rank. The explicit `makeRankReader` service composes the actual Leaderboards `myRating` API through `ratingSourceFromLeaderboards`. No alternate rating store or algorithm is installed. This explicit read service remains separate. The opt-in `progressionPlugin({ ranks: ... })` binding installs the season RankPort, owned routes, hooks and rank configuration panel described below; automatic closure is separately opt-in below.
|
|
344
|
+
|
|
345
|
+
### Take and configure
|
|
346
|
+
|
|
347
|
+
An app supplies its actual bound Ratings service and explicit ascending thresholds. No hidden tier cutoffs are inferred from tier labels. For `tiers.length × divisions` grades, supply exactly one fewer threshold; equality promotes to the next grade. Labels are unique trimmed text, including Unicode. Division1 is highest within its tier. Below the first threshold remains the lowest division; above the last remains the highest. Placements default to10 and conceal the grade until the actual SQL game count reaches the configured count. The initial rating is not a played placement.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { defineRankLadder, defaultRankGrade } from '@voltro/plugin-progression/ranks'
|
|
351
|
+
import { makeRankReader, ratingSourceFromLeaderboards } from '@voltro/plugin-progression/server'
|
|
352
|
+
|
|
353
|
+
// Illustrative app-owned balancing; choose your game's thresholds explicitly.
|
|
354
|
+
const ladder = defineRankLadder({
|
|
355
|
+
id: 'arena', rating: 'ranked-2v2',
|
|
356
|
+
tiers: ['Bronze', 'Silver', 'Gold'], divisions: 2,
|
|
357
|
+
thresholds: [100, 200, 300, 400, 500], placements: 10,
|
|
358
|
+
decay: { afterDays: 14, perDay: 15, floor: 0 },
|
|
359
|
+
softReset: { target: 250, retain: 0.5 },
|
|
360
|
+
})
|
|
361
|
+
// ratings is the actual server-bound Leaderboards service exposing myRating.
|
|
362
|
+
const ranks = makeRankReader({
|
|
363
|
+
ladders: [ladder], source: ratingSourceFromLeaderboards(ratings),
|
|
364
|
+
})
|
|
365
|
+
// Read in a meta handler with its verified player Subject, outside the Room tick.
|
|
366
|
+
const result = ranks.mine(subject)
|
|
367
|
+
// Custom pure grade rules must have an explicit nondefault rule version.
|
|
368
|
+
const custom = makeRankReader({
|
|
369
|
+
ladders: [ladder], source: ratingSourceFromLeaderboards(ratings),
|
|
370
|
+
strategies: {
|
|
371
|
+
version: 'app.rank.v1',
|
|
372
|
+
grade: input => defaultRankGrade(input),
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`defaultRankScore` uses Elo/Glicko2 rating, or OpenSkill `mu − confidence × sigma` (default confidence3), within the captured numeric range. It retains each algorithm's actual state and uncertainty fields. `defaultRankDecay` only changes the displayed score: after the grace period, each whole elapsed UTC day subtracts `perDay`, bounded by the explicit floor; it never boosts a below-floor score. It does not write Ratings, install the required periodic decay job or count local civil days. `resolveRankSeasonReset` calculates `target + (score − target) × retain` from explicit configuration; it does not perform a season reset or mutate uncertainty. All counts, bounds and example amounts are configuration budgets, not measured capacity.
|
|
378
|
+
|
|
379
|
+
### Hook and override
|
|
380
|
+
|
|
381
|
+
The four exported defaults are `defaultRankScore`, `defaultRankDecay`, `defaultRankGrade` and `defaultRankSeasonReset`; `./testing` exports an actual detached fixture. Override slots through an explicit nondefault rule version. Inputs are captured and frozen before callbacks; outputs are checked for finite range, grade topology and placement authority. Decay cannot boost the input score. A grade override cannot reveal a player's grade during placement. Rank events and mutation/workflow interception are still open.
|
|
382
|
+
|
|
383
|
+
### Extend or replace
|
|
384
|
+
|
|
385
|
+
`RankRatingSource` is the Effect service seam for a trusted observation source. `makeRankReader` admits only a verified user Subject, captures owner/tenant before laziness, reads each distinct pool once and performs no writes. Custom sources must return actual authoritative observations scoped to that Subject; the service validates pool, algorithm, game count and timestamps. An unavailable or invalid source fails without an invented initial rating. This read-only service owns no tables or subject data; Ratings keeps its own export/erase contract. The explicit RankPort and SQL peak/season/reward journals are described below.
|
|
386
|
+
|
|
387
|
+
### Panel, seasons and evidence
|
|
388
|
+
|
|
389
|
+
The opt-in rank panel reports configured ladders/seasons, source/slot/backend and budgets; it keeps complete match coverage, season placements and pool decay/reset false, and reports the actual optional delivery binding. Complete match-covered highest tier, seasonal placement baselines, periodic decay, authoritative pool soft reset and complete season-end integration remain open; the observation journal described below persists the peak of its admitted snapshots. No support claim follows from this source.
|
|
390
|
+
|
|
391
|
+
### Durable rank-season observation journal
|
|
392
|
+
|
|
393
|
+
Additional partial source: `./rank-seasons` exposes `defineRankSeason` and the five-method `RankPort` (`observe`, trusted `close`, `mine`, `exportSubject`, `eraseSubject`). `dataStoreRankSeasons` and `rankTables` in `./server` are the actual SQL implementation and seven required table roles. The opt-in `progressionPlugin` rank binding registers these default roles; explicit standalone composition must register/apply them itself. A season captures explicit UTC `from`/`until` and a complete array of rewards, one list per configured tier. No prices or balancing are inferred.
|
|
394
|
+
|
|
395
|
+
`observe` accepts only a season/request intention from a verified player; its actual trusted Rating source runs outside SQL callbacks. The resulting captured rank observation, highest admitted division, owner sequence, before/after journal and terminal receipt commit atomically. An independent pool-state revision key deduplicates a different request nonce. A completed exact request returns its original receipt before source/clock callbacks. Changed persisted declarations or versions refuse. Missing stock requires journal repair, not a fresh initial season.
|
|
396
|
+
|
|
397
|
+
`close` requires an admitted system/service and the actual season end. It captures the pure reset result before SQL and refuses a racing change to its prior-rating basis. Closure and one pending reward list for the highest **admitted observation's** tier commit together; a season without an observation has no invented tier or gift. Reward rows initially contain intentions; with explicit delivery configuration, rank joins RewardDeliveryPort and the actual Economy/Inventory/XP destination ports. Pending rows are not accepted external value. `resetScore` is a captured intention, not a performed Ratings reset. Current placement counts are the source pool's actual game count, not a new season baseline. An observation reader cannot prove the highest tier of unobserved matches; the complete match-result subscription and season placement/reset workflow remain open.
|
|
398
|
+
|
|
399
|
+
Whole-journal `auditRanks`/`rebuildRanks` checks sequence, request/claim aliases, captured pure transitions, peak, closure and exact pending reward conservation. Repair writes stock and accepted closure units only; it does not recall historical custom grade/score/reset or Rating callbacks, perform a pool reset or deliver another reward. It is not fresh gameplay attestation. Full export covers states, accepted source observations/intent/before/after, receipts and gifts. Erase clears owned bodies/links and leaves permanent pseudonymous owner/request/claim fences; an in-flight source read cannot revive the owner. Shared configuration remains. Default roles are declared to plugin schema/privacy and ordinary query invalidation; full replacement omits them and uses the same port and privacy callbacks. Owned routes/hooks and four strict inspect layers are written.
|
|
400
|
+
|
|
401
|
+
Those native scenarios use real SQL schema and pools with an explicitly isolated Rating-source boundary fixture; they do not prove the still-pending cross-wave actual Ratings SQL binding.
|
|
402
|
+
|
|
403
|
+
### Optional plugin binding and configuration panel
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { progressionPlugin, defineRankSeason } from '@voltro/plugin-progression'
|
|
407
|
+
import { ratingSourceFromLeaderboards } from '@voltro/plugin-progression/server'
|
|
408
|
+
|
|
409
|
+
const season = defineRankSeason({
|
|
410
|
+
id: 'arena.s1', ladderId: ladder.id,
|
|
411
|
+
from: Date.UTC(2026, 9, 10), until: Date.UTC(2026, 10, 9),
|
|
412
|
+
rewards: [[], [], []],
|
|
413
|
+
}, ladder)
|
|
414
|
+
const progression = progressionPlugin({
|
|
415
|
+
tracks: [],
|
|
416
|
+
ranks: {
|
|
417
|
+
ladders: [ladder], seasons: [season],
|
|
418
|
+
source: ratingSourceFromLeaderboards(ratings),
|
|
419
|
+
},
|
|
420
|
+
})
|
|
421
|
+
// After app binding, verified players send only season/request intention:
|
|
422
|
+
const observation = progression.progression.ranks().observe(subject, {
|
|
423
|
+
seasonId: season.id, requestId,
|
|
424
|
+
})
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`ranks.mine` reads captured season state; `ranks.observe` is an Action that lets the adapter own its transaction and derives owner/tenant from verified Subject. `useRanks` and `useObserveRank` in `./web` use the existing client subscription/action primitives; no player rating-write or close route exists. An uncertain request must reuse its original identity. A complete recovery UI is still open.
|
|
428
|
+
|
|
429
|
+
`ranks.service` replaces all five methods through the same RankPort. Default SQL roles, repair and implicit SQL fallback are omitted; app-owned replacement tables need their own schema/privacy/CDC registration. Factory export/erase forwards to the replacement. The inspect endpoint, shared semantic EN/DE panel and ordinary Local/Cloud providers use strict configuration-only snapshots. New negative fixtures refuse fabricated payout/job capabilities, private fields, missing roles and invalid threshold geometry.
|
|
430
|
+
|
|
431
|
+
### Delivering accepted rank-season rewards
|
|
432
|
+
|
|
433
|
+
With explicit delivery configuration, `rank` is an admitted RewardDeliveryPort source kind and uses the existing actual durable delivery workflow/schedule and destination ports. Before pinning an external intent, SQL admission checks the complete accepted season history (bounded to100000 rows), captured immutable ladder/season/rule version, every request/revision identity, ordered pure peak transitions, close parent, exact reward amount and stable gift ID. It never reruns historical custom score/grade/reset or Rating source callbacks. Accepted snapshots are the authority of this journal; missing unobserved matches remain outside its evidence.
|
|
434
|
+
|
|
435
|
+
The destination call runs outside source SQL. Its stable request/origin survives a lost reply; settlement retains destination receipt in the existing source role. Terminal delivery returns before another destination call. Missing destination ports refuse. A replaced RankPort requires complete delivery-service replacement; no default SQL fallback. Source audit/rebuild accepts and validates pending or delivered evidence without creating more value, and export/erase also clears delivery intent/digest/receipt. Source-only erase cannot undo an external gift; destination privacy is required too.
|
|
436
|
+
|
|
437
|
+
The EN/DE rank panel separately shows whether delivery is configured; that flag is not proof of execution.
|
|
438
|
+
|
|
439
|
+
### Optional durable rank-season closure
|
|
440
|
+
|
|
441
|
+
`ranks.closure: {}` installs the two-method `RankClosurePort` (`execute`, `dispatch`), `progression.closeRankSeason` internal workflow and `progression.dispatchRankClosures` UTC schedule through ordinary named plugin source groups. With closure absent, these sources and this Layer are omitted. Reward delivery is independently optional; closure queues exact tier intentions and does not itself pay them.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { progressionPlugin } from '@voltro/plugin-progression'
|
|
445
|
+
|
|
446
|
+
const progression = progressionPlugin({
|
|
447
|
+
tracks: [],
|
|
448
|
+
ranks: { ...rankConfiguration, closure: {} },
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`rankConfiguration` is the app's explicit ladder, season and trusted Rating-source configuration. The seventh default SQL role, `_voltro_prog_rank_closure_units`, is created atomically with the first accepted season transition. It pins opaque unit/state identity, tenant/owner, version and end date. Close, peak gifts, accepted history and redacted terminal closure receipt commit together. No new unit is invented on a read. Journal-only repair reconstructs stock and units without running historical callbacks or paying another reward.
|
|
453
|
+
|
|
454
|
+
The workflow payload contains only `{ unitId }`. Its activity resolves private scope from accepted SQL, calls the actual RankPort outside SQL callbacks and validates complete accepted evidence. Completed units return their persisted receipt before another source/clock/close call. A missing unit is an error requiring repair, never an erasure assumption. Erase clears owner/link/receipt bodies and leaves an explicit erased unit; delayed work can end erased only from that marker. The erased receipt's `at` is the retained unit creation timestamp, not the time of erasure. Export includes owned closure state and receipt. Unit IDs are pseudonymous retained fences, not anonymous records.
|
|
455
|
+
|
|
456
|
+
The UTC schedule scans due open units each minute, with configured budgets of100 rows per page and100 pages per invocation. SQL remains the retry/rebuild source; there is no tick, extra queue store or implicit backend switch. Failed/dropped starts and an exhausted scan make the task fail while accepted units remain due. Activity retries have a budget of5 attempts; exhausted failures suspend the workflow. A valid running/queued/replayed handle is admission, not completed closure. Repeated dispatch does not resume a suspended incumbent; the operator must resume it after correcting the cause.
|
|
457
|
+
|
|
458
|
+
`ranks.closure.service` replaces both methods through the same port. A replaced RankPort requires explicit closure-service replacement; default SQL closure cannot silently borrow custom storage. The replacement must own compatible accepted identities, terminal evidence and privacy through the replacement rank system. Strict inspect/Shared/Local/Cloud configuration reports the closure backend, workflow/schedule and budgets separately from delivery. No owner bodies, outcome counters or claimed complete match coverage are exposed.
|
|
459
|
+
|
|
460
|
+
All six native dialect scenarios include competing borrowed pools and terminal readback after privacy. Configuration-only decoder/provider and EN/DE DOM cases are written. Full match coverage, season placement baseline, authoritative Ratings reset and periodic decay remain open.
|
|
461
|
+
|
|
462
|
+
## Scoped system port fixtures
|
|
463
|
+
|
|
464
|
+
`./testing` is now a **server/testing entry**, because its canonical Layers load the actual installed Progression/Quest/Resource adapters. Never import it into a browser descriptor. Existing pure declaration fixture names remain available; declarations live in one private module to avoid initialization cycles. Use the dedicated curves/rewards/strategies/quests/resources entries in browser-safe code.
|
|
465
|
+
|
|
466
|
+
`progressionFakeLayer`, `questFakeLayer`, `questSourceFakeLayer` and `resourceFakeLayer` share one Layer identity and compose the actual ProgressionPort, QuestPort, QuestSource and RegenResourcePort. Each scoped acquisition creates a private InMemoryDataStore and real plugin. Release is registered before schema/bind preparation; escaped mutable facades refuse after Scope closes. No refresh job or reward destination is installed. Static content remains immutable; fixture rewards are queued intentions, never delivered wallet value. Example balances, UTC dates and content are test configuration.
|
|
467
|
+
|
|
468
|
+
Structural `progressionPortSystemFixture`, `questPortSystemFixture`, `questSourceSystemFixture` and `resourcePortSystemFixture` use actual Tags and accepted/replayed receipts. `xpCurveSystemFixture`, `rewardResolverSystemFixture`, `questSelectorSystemFixture`, `questProgressResolverSystemFixture` and `resourceRegenSystemFixture` expose the real default identity and an admitted distinguishing override. Native SQL/durability, complete per-table privacy, events/hooks, inspect, mixins/upgrades and generic systemContractConformance registration/execution remain open. Fixtures and normal guide generation are no acceptance proof.
|
|
469
|
+
|
|
470
|
+
## Achievement and Pass port fixtures, captured replacement receivers
|
|
471
|
+
|
|
472
|
+
The canonical scoped Progression Layer also installs the actual AchievementPort, AchievementStatSource and SeasonPassPort. `achievementFakeLayer`, `achievementStatSourceFakeLayer` and `passFakeLayer` share that identity; `achievementPortSystemFixture`, `achievementStatSourceSystemFixture` and `passPortSystemFixture` exercise accepted core-stat history, hidden achievement unlock and original pass claim receipts. No invented stat source, storefront or reward delivery. `achievementEvaluatorSystemFixture` retains truthful threshold progress while an admitted override changes fulfillment; `passCatchUpSystemFixture` composes the actual default with a distinct admitted amount. The source fixture layer now has seven actual installed Tags, not a complete Progression-system inventory.
|
|
473
|
+
|
|
474
|
+
Installed core, Quest, Resource, Achievement, Daily, Pass, Tutorial and Rank **service replacements** now capture required methods once from the real receiver/prototype chain, bounded to8 levels. Required getters/shadowed accessors refuse without invocation. Original class private fields stay on their real instance; later method substitution cannot change the admitted method pointers. Configuration remains plain data; this does not claim class admission for source providers or delivery/closure replacements. Existing optional/default roles, active gates and privacy delegation stay owned by each configured service. No test/type/native/normal boot conformance claim.
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
## Production license admission
|
|
478
|
+
|
|
479
|
+
Declare the actual `licensingPlugin()` before this plugin in the API app. Production and staging activation require its verified active or grace deployment-wide license to grant `progression`. Explicit development/test mode stays free without recurring per-instance notices; CLI owns the actual template first-boot receipt. Tenant-scoped licenses refuse until actual tenant-bound authority exists. This server-only gate preserves normal schema, store binding and cleanup; it runs before the original activation hook, not before schema/store binding.
|