RaidLoop is a lightweight extraction-shooter loop built with Blazor WebAssembly. You prepare gear, run raids, fight or loot, and try to extract with profit.
- Authored item catalog with explicit value, gameplay loot tier, and display rarity.
- Separate display rarity colors:
SellOnly= grayCommon= whiteUncommon= greenRare= blueEpic= yellowLegendary= orange
- Tiered loot generation for containers and enemy loadouts, with support for rarity-tier boosters.
- Item pricing based on usefulness:
- stronger weapons sell for more
- higher-reduction armor sells for more
- larger backpacks sell for more
- Luck Runs with cooldown, random starter kit generation, and post-run loot processing.
- Luck Run starter kits are intentionally capped below
EpicandLegendary.
- Move items between
StorageandFor Raid. - Equip weapon/armor/backpack before entering raid.
- Buy essentials from the shopkeeper.
- Storage and raid-prep rows use item-type icons instead of text labels.
- Encounters are combat, loot, extraction opportunities, or clear areas.
- Raid HUD now tracks two explicit raid-state numbers:
ChallengeandDistance from Extract. - When you reach extract, you can
Attempt ExtractionorStay at Extract. - Away from extract, the main travel choices are
Go DeeperandMove Toward Extract. - During loot encounters, use:
Discovered Loot(what you found)Character Inventory(equipped + carried)
- You can loot, equip, and drop items directly in raid.
- On discovered loot,
Lootstays on the far right andEquipappears to its left when available. - Medkits can be used at any time during raid.
- If you die, your raid kit is lost.
- If you extract, equipped + carried loot returns to your persistent inventory.
- There is no separate extraction settlement screen; extracted items simply return to your inventory state.
- Luck Run has a cooldown.
- When ready, enter immediately to generate a surprise random kit.
- After a successful Luck Run, process returned loot (
Store,For Raid,Sell) before new raids.
MakarovPPSHAK746B2 body armor6B13 assault armorSmall BackpackTactical Backpack
SVDSFORT Defender-2Tasmanian Tiger Trooper 35
AK47PKP6B43 Zabralo-Sh body armorNFM THOR6Sh118
BandageAmmo BoxScrap MetalLegendary Trigger Group
- .NET SDK 10.x
- Docker Desktop
- Node.js with local npm dependencies installed via
npm install
dotnet restore RaidLoop.sln
dotnet run --project src/RaidLoop.Client/RaidLoop.Client.csprojdotnet test RaidLoop.slndotnet build RaidLoop.sln- Use F5 with
RaidLoop Client (http profile)in.vscode/launch.json. - Browser auto-opens when Kestrel is ready.
Continuous Deliveryworkflow runs quality checks, coverage, Pages deploy, and release tagging in one pipeline.- Coverage reports are uploaded as workflow artifacts.
game.item_defs.item_def_idis the database surrogate key for authored items.itemDefIdis the canonical runtime gameplay and contract identity.- Player-facing labels are client-owned display data and should not be used as server-side identity.
- Authored item renames should be done in
src/RaidLoop.Client/Resources/ItemResources.resx, not by changing gameplay logic or contracts. - For authored items,
Item.Nameis presentation data only. Runtime gameplay behavior followsitemDefIdonly. - Bootstrap returns the full non-localized item rules catalog once, keyed by
itemDefId. - Runtime action payloads stay lean and should not repeat the rules catalog or server-authored item labels.
- Localization should resolve labels from client-owned assets keyed by
itemDefId, not from server-authored English names. - Persisted authored items should store
itemDefIdonly for identity ingame_saves.payloadandraid_sessions.payload. item_keyand runtimeitemKeyare removed from authored item identity entirely.- Non-item display text fields like
randomCharacter.name,enemyName, and raid log entries remain normal persisted text and are not part of item identity cleanup. - Payload identity changes must use forward-only migrations. Do not rewrite already-applied migrations to rename or re-key persisted item data.
- Local development uses
ASPNETCORE_ENVIRONMENT=Localfromsrc/RaidLoop.Client/Properties/launchSettings.json. src/RaidLoop.Client/wwwroot/appsettings.Local.jsonpoints the client at local Supabase onhttp://127.0.0.1:54321.src/RaidLoop.Client/wwwroot/appsettings.Test.jsonis the hosted Supabase smoke-test configuration.- Keep hosted Supabase for later smoke testing. Day-to-day development should use the local Supabase CLI stack.
From the repo root:
. .\env.local.ps1
npm install
npx supabase start
npx supabase db resetUse npx supabase db reset whenever you need a clean local database rebuilt from migrations. Use npx supabase db push --include-all only for quick iteration when you do not need a full reset.
env.local.ps1 only loads local-safe values from .env and refuses hosted Supabase refs, hosted URLs, and remote deploy credentials.
Local Edge Functions are served from the checked-out files in supabase/functions/. They are not deployed with supabase functions deploy during normal local development.
Typical local function workflow:
. .\env.local.ps1
npx supabase start
npx supabase functions serveCall local functions through the gateway at:
http://127.0.0.1:54321/functions/v1/<function-name>
Use the lightest restart that matches the change:
- SQL migrations changed:
npx supabase db reset
- Edge Function code changed:
restart
npx supabase functions serve - Both SQL and Edge Functions changed:
npx supabase db reset- restart
npx supabase functions serve
Do not use full npx supabase stop / npx supabase start for every function edit. Only restart the full stack when the local Supabase services themselves are unhealthy.
- Start from the smallest relevant failing test.
- write the smallest failing test first.
- Run the narrowest command that proves it fails.
- Make the minimum code or migration change to pass.
- Re-run the same narrow test until it is green.
- Run local integration checks against the local Supabase stack before committing.
Typical narrow commands:
dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter NameOfTest
deno test supabase/functions/game-action/handler.test.mjsFor SQL or migration work, prefer:
npx supabase db reset
dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter "HomeMarkupBindingTests|RaidActionApiTests|ProfileMutationFlowTests"For SQL changes, do not stop at static migration inspection. Use a three-layer test process:
- Migration shape checks
- Fresh local database rebuild
- Runtime SQL integration tests against local Supabase
Add or update narrow tests that read the migration file and assert the critical SQL is present.
Use this layer to verify things like:
- a new function or
jsonb_setpath exists - the canonical stat source expression is correct
- an
update public.game_savesorupdate public.raid_sessionsstatement exists - a known bad expression is no longer present
Run:
dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter "ItemCatalogTests|HomeMarkupBindingTests"This layer is fast, but it does not prove the SQL behaves correctly at runtime.
Apply the entire migration chain to a clean local database:
. .\env.local.ps1
npx supabase start
npx supabase db resetUse npx supabase db reset for SQL verification, not db push --include-all.
This layer proves:
- the migrations still apply in order
- patch migrations still match the current function text
- there are no hidden dependency or ordering problems
For any bug involving persisted state, stats, raid snapshots, or migration repair behavior, add a runtime test that exercises the real database path.
The preferred pattern is:
- Create a real local auth user
- Seed or mutate
public.game_savesand/orpublic.raid_sessionsinto a known stale or bad state - Call the real RPC path such as
profile_bootstraporgame_action - Assert on the returned snapshot
Examples of what to test this way:
- stale
playerMaxHealthrepaired from accepted constitution - stale raid
maxEncumbrancerepaired from accepted strength - stale raid payload stats ignored in favor of canonical save stats
- migration rewrites persisted raid payload fields correctly
Run:
deno test --allow-env --allow-net supabase/functions/game-action/local-integration.test.mjsUse these runtime tests as the source of truth for SQL behavior. Static file checks are support checks only.
When changing SQL:
- Write the smallest runtime failing case first if the bug involves persisted or derived database state.
- Add or update static migration-shape assertions for the critical SQL text.
- Run the narrow tests and confirm the runtime case fails for the right reason.
- Add a new forward-only migration. Do not edit already-applied migrations.
- Run
npx supabase db reset. - Re-run the runtime SQL test until it passes.
- Re-run the narrow C# and handler tests.
Typical command sequence:
. .\env.local.ps1
npx supabase start
dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter "ItemCatalogTests|HomeMarkupBindingTests"
npx supabase db reset
deno test --allow-env --allow-net supabase/functions/game-action/local-integration.test.mjs
node --test supabase/functions/game-action/handler.test.mjsIf the SQL change affects profile bootstrap or other Edge Functions, also run:
deno test supabase/functions/profile-bootstrap/handler.test.mjs supabase/functions/profile-save/handler.test.mjsIf the change affects the game-action Edge Function response shape or projection behavior, also run:
node --test supabase/functions/game-action/handler.test.mjs
deno test --allow-env --allow-net supabase/functions/game-action/local-integration.test.mjsDo not call SQL work complete until:
- the migration applies cleanly on
db reset - the runtime SQL integration case passes
- the relevant narrow tests pass
- any changed Edge Function handler tests pass
Before pushing commits, run:
npx supabase db reset
dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter "HomeMarkupBindingTests|RaidActionApiTests|ProfileMutationFlowTests"
dotnet test RaidLoop.slnIf you changed Edge Functions, also run:
deno test supabase/functions/profile-bootstrap/handler.test.mjs supabase/functions/profile-save/handler.test.mjs
node --test supabase/functions/game-action/handler.test.mjs
deno test --allow-env --allow-net supabase/functions/game-action/local-integration.test.mjsPush only after local verification is green. Use the hosted Supabase environment after that for smoke testing of integration details such as auth-provider behavior.
GitHub Actions production deploy expects:
- Repository variables:
SUPABASE_PROJECT_IDSUPABASE_URLSUPABASE_PUBLISHABLE_KEY
- Repository secrets:
SUPABASE_ACCESS_TOKENSUPABASE_DB_PASSWORD
The project id is the Supabase project ref, for example dblgbpzlrglcdwqyagnx, not the project display name.
These values belong in GitHub repository variables and secrets only. Do not put remote project refs, remote database passwords, or access tokens in the repo-root .env.
Remote Supabase changes are CI-only:
- database migrations run from GitHub Actions using repository variables and secrets
- Edge Functions deploy from GitHub Actions using repository variables and secrets
- local developer shells must not run
supabase linkagainst hosted projects
After applying migrations, useful local checks are:
- Verify tables exist:
select to_regclass('public.game_saves');
select to_regclass('public.raid_sessions');- Verify RLS is enabled:
select relname, relrowsecurity
from pg_class
where relname in ('game_saves', 'raid_sessions');- Verify bootstrap function exists:
select routine_name
from information_schema.routines
where routine_schema = 'game'
and routine_name = 'bootstrap_player';- Verify bootstrap creates a starter save for an authenticated user:
select game.bootstrap_player(auth.uid());- Verify the created save row:
select user_id, save_version, payload, created_at, updated_at
from public.game_saves
where user_id = auth.uid();