From 74cc8297ced6354cdeb80c71df844831f1c45706 Mon Sep 17 00:00:00 2001 From: lobo Date: Sat, 3 Oct 2026 17:31:19 +0200 Subject: [PATCH] feat(rewards): add 14 new shop items with twin backgrounds Each page background gets a matching banner background. Tests verify pairing, price constraints, and code length limits --- readme.md | 2 +- .../modules/rewards/domain/ShopItem.java | 14 ++++++++ .../modules/rewards/domain/ShopItemTest.java | 33 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/test/java/core_service/modules/rewards/domain/ShopItemTest.java diff --git a/readme.md b/readme.md index a964a2e..106e323 100644 --- a/readme.md +++ b/readme.md @@ -65,7 +65,7 @@ To Be Played is split into three repositories: snapshots, once per user, kind and game (`ref`). Rules and numbers: `RewardRules`; a Steam finish needs playtime and one proof of fresh play (ending achievement or playtime around the finish, or half of the IGDB time played since the finish; old history pays nothing). The month bonus needs a finished game in that month. Games added by hand pay 15 for a finish, at most 3 per ISO week. The settle takes a per-user advisory lock. V26 also pays a one-time starter grant - for finishes from before the status log. Shop: a whitelist of 43 looks (`ShopItem`; 31 buyable and 12 unlock-only) in eight slots, including animated completed effects, page backgrounds, + for finishes from before the status log. Shop: a whitelist of 57 looks (`ShopItem`; 45 buyable and 12 unlock-only) in eight slots, including animated completed effects, page backgrounds (CSS, SVG and two canvas engines in the Frontend), banner backgrounds (V27 widens `reward_equipped.slot`) and three buyable skins (full themes, slot `SKIN`). The unlock-only items (price 0, never sold): the settle books an `UNLOCK` row (ref = item code, 0 points) once a track reaches the item's target, retroactively and never lost. The five tracks (`Unlocks`): games finished diff --git a/src/main/java/core_service/modules/rewards/domain/ShopItem.java b/src/main/java/core_service/modules/rewards/domain/ShopItem.java index 4f5c82d..679fced 100644 --- a/src/main/java/core_service/modules/rewards/domain/ShopItem.java +++ b/src/main/java/core_service/modules/rewards/domain/ShopItem.java @@ -31,6 +31,13 @@ public enum ShopItem { BG_GRID(LookSlot.PAGE_BACKGROUND, 450), BG_WAVES(LookSlot.PAGE_BACKGROUND, 400), BG_BOKEH(LookSlot.PAGE_BACKGROUND, 400), + BG_SYNTHWAVE(LookSlot.PAGE_BACKGROUND, 450), + BG_FIREFLIES(LookSlot.PAGE_BACKGROUND, 400), + BG_SNOW(LookSlot.PAGE_BACKGROUND, 400), + BG_SAKURA(LookSlot.PAGE_BACKGROUND, 400), + BG_NEBULA(LookSlot.PAGE_BACKGROUND, 450), + BG_MATRIX(LookSlot.PAGE_BACKGROUND, 500), + BG_CONSTELLATION(LookSlot.PAGE_BACKGROUND, 500), BN_TRIANGLES(LookSlot.BANNER_BACKGROUND, 300), BN_STARFIELD(LookSlot.BANNER_BACKGROUND, 300), BN_PIXELS(LookSlot.BANNER_BACKGROUND, 300), @@ -39,6 +46,13 @@ public enum ShopItem { BN_GRID(LookSlot.BANNER_BACKGROUND, 300), BN_WAVES(LookSlot.BANNER_BACKGROUND, 300), BN_BOKEH(LookSlot.BANNER_BACKGROUND, 300), + BN_SYNTHWAVE(LookSlot.BANNER_BACKGROUND, 300), + BN_FIREFLIES(LookSlot.BANNER_BACKGROUND, 300), + BN_SNOW(LookSlot.BANNER_BACKGROUND, 300), + BN_SAKURA(LookSlot.BANNER_BACKGROUND, 300), + BN_NEBULA(LookSlot.BANNER_BACKGROUND, 300), + BN_MATRIX(LookSlot.BANNER_BACKGROUND, 300), + BN_CONSTELLATION(LookSlot.BANNER_BACKGROUND, 300), SKIN_PASTEL(LookSlot.SKIN, 600), SKIN_RETRO_CRT(LookSlot.SKIN, 700), SKIN_CYBERPUNK(LookSlot.SKIN, 800), diff --git a/src/test/java/core_service/modules/rewards/domain/ShopItemTest.java b/src/test/java/core_service/modules/rewards/domain/ShopItemTest.java new file mode 100644 index 0000000..4ac91fd --- /dev/null +++ b/src/test/java/core_service/modules/rewards/domain/ShopItemTest.java @@ -0,0 +1,33 @@ +package core_service.modules.rewards.domain; + +import core_service.shared.test.AbstractUnitTest; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +class ShopItemTest extends AbstractUnitTest { + + @Test + void everyPageBackgroundHasABannerTwin() { + for (ShopItem item : ShopItem.values()) { + if (!item.name().startsWith("BG_")) continue; + ShopItem twin = ShopItem.valueOf("BN_" + item.name().substring(3)); + assertThat(item.slot()).isEqualTo(LookSlot.PAGE_BACKGROUND); + assertThat(twin.slot()).isEqualTo(LookSlot.BANNER_BACKGROUND); + } + } + + @Test + void buyableBannerBackgroundsCost300() { + assertThat(Arrays.stream(ShopItem.values()) + .filter(item -> item.name().startsWith("BN_") && !item.unlockOnly()) + .map(ShopItem::price)).containsOnly(300); + } + + @Test + void codesFitTheColumn() { + assertThat(Arrays.stream(ShopItem.values()).map(Enum::name)).allMatch(name -> name.length() <= 32); + } +}