mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-19 07:08:33 +02:00
feat(monetization): lock the cosmetic ladder (loyalty model) + free Original pack
Nails down the premium unlock system per the design pass: - FREE `original` topdown pack (NEW): the game's bare tiles — floor/wall/door/ torch/features only, no furniture, so the free tavern reads plain (the intended "reason to unlock"). Registered in assetPacks (RK.THEMES.original) as the free default topdown pack; plain ASCII stays free (accessibility). - Loyalty ladder (lifetime credits PURCHASED, cumulative, NOT deducted → buying to play also unlocks cosmetics): Interior 1, skins 5, iso 10, +tilepack 20, +iso 40, 3D 50. Each row is data (tilepack OR skin) → operator appends more at any rung. DEFAULT_CATALOG (entitlements.js) + migration 028 + MONETIZATION.md ladder table. - Tests updated to the new thresholds (catalogEntitlements_ultra, directEntryCredit_ultra). Note: this reprices roguelike-interior from free → 1cr, so once deployed the free tavern becomes bare (by design). Deploy is bundled with the single-player render-kit migration so the free-tier change lands with its payoff. Suite 61/432 green.
This commit is contained in:
@@ -13,8 +13,9 @@ const { snapshotForUser, DEFAULT_CATALOG } = Entitlements;
|
||||
describe('three-way unlock — default (seed) catalog', () => {
|
||||
test('free pack is always unlocked; premium packs are locked with no spend', () => {
|
||||
const s = snapshotForUser({ credits: 0, total_credits_purchased: 0 });
|
||||
expect(s.packs['roguelike-interior']).toBe(true); // free
|
||||
expect(s.packs['generated-skins']).toBe(false); // threshold 1, no spend
|
||||
expect(s.packs['original']).toBe(true); // free baseline
|
||||
expect(s.packs['roguelike-interior']).toBe(false); // now 1-credit premium
|
||||
expect(s.packs['generated-skins']).toBe(false); // threshold 5, no spend
|
||||
expect(s.packs['iso-dungeon']).toBe(false);
|
||||
expect(s.packs['kenney-3d-characters']).toBe(false);
|
||||
expect(s.premium).toBe(false);
|
||||
@@ -22,18 +23,22 @@ describe('three-way unlock — default (seed) catalog', () => {
|
||||
expect(s.tier).toBe(0);
|
||||
});
|
||||
|
||||
test('graduated ladder: 1 credit unlocks Fancy (skins) only, not Iso or 3D', () => {
|
||||
test('graduated ladder: 1 credit unlocks the first tilepack only, not skins/iso/3D', () => {
|
||||
const s = snapshotForUser({ credits: 5, total_credits_purchased: 1 });
|
||||
expect(s.packs['generated-skins']).toBe(true); // threshold 1
|
||||
expect(s.packs['roguelike-interior']).toBe(true); // threshold 1
|
||||
expect(s.packs['generated-skins']).toBe(false); // threshold 5
|
||||
expect(s.packs['iso-dungeon']).toBe(false); // threshold 10
|
||||
expect(s.packs['kenney-3d-characters']).toBe(false); // threshold 25
|
||||
expect(s.packs['kenney-3d-characters']).toBe(false); // threshold 50
|
||||
expect(s.premium).toBe(true); // a premium pack IS unlocked
|
||||
});
|
||||
|
||||
test('graduated ladder: 25 credits unlocks the whole ladder', () => {
|
||||
const s = snapshotForUser({ credits: 0, total_credits_purchased: 25 });
|
||||
test('graduated ladder: 50 credits unlocks the whole ladder', () => {
|
||||
const s = snapshotForUser({ credits: 0, total_credits_purchased: 50 });
|
||||
expect(s.packs['roguelike-interior']).toBe(true);
|
||||
expect(s.packs['generated-skins']).toBe(true);
|
||||
expect(s.packs['iso-dungeon']).toBe(true);
|
||||
expect(s.packs['roguelike-dungeon']).toBe(true);
|
||||
expect(s.packs['iso-medieval']).toBe(true);
|
||||
expect(s.packs['kenney-3d-characters']).toBe(true);
|
||||
});
|
||||
|
||||
@@ -96,7 +101,7 @@ describe('grant-only packs', () => {
|
||||
describe('canUsePack reads the snapshot', () => {
|
||||
test('gates on the computed packs map', () => {
|
||||
const locked = snapshotForUser({ total_credits_purchased: 0 });
|
||||
expect(Entitlements.canUsePack(locked, 'roguelike-interior')).toBe(true); // free
|
||||
expect(Entitlements.canUsePack(locked, 'original')).toBe(true); // free
|
||||
expect(Entitlements.canUsePack(locked, 'iso-dungeon')).toBe(false); // locked
|
||||
const unlocked = snapshotForUser({ total_credits_purchased: 10 }); // iso threshold is 10
|
||||
expect(Entitlements.canUsePack(unlocked, 'iso-dungeon')).toBe(true);
|
||||
|
||||
@@ -47,9 +47,10 @@ describe('recordDirectEntryPurchase', () => {
|
||||
expect(insert[0]).toMatch(/'direct_entry'/); // audits the purchase leg
|
||||
expect(insert[0]).toMatch(/'game_entry'/); // audits the spend leg
|
||||
|
||||
// A direct entry = 1 credit, which unlocks Fancy (generated-skins, threshold 1) but not the
|
||||
// pricier Iso (10) / 3D (25) tiers.
|
||||
expect(rec.entitlements.packs['generated-skins']).toBe(true);
|
||||
// A direct entry = 1 credit, which unlocks the first tilepack (roguelike-interior, threshold
|
||||
// 1) but not skins (5) / Iso (10) / 3D (50).
|
||||
expect(rec.entitlements.packs['roguelike-interior']).toBe(true);
|
||||
expect(rec.entitlements.packs['generated-skins']).toBe(false);
|
||||
expect(rec.entitlements.packs['iso-dungeon']).toBe(false);
|
||||
expect(rec.entitlements.premium).toBe(true);
|
||||
// ...but a direct buyer is still NOT a premium tier.
|
||||
|
||||
Reference in New Issue
Block a user