Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A dark-comedy creature shelf. Draw little monsters, keep them fed, and survive their small conspiracies.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Play Shelf Life · Report a problem
Small creatures. Long memories. A free, darkly comic creature game for phones and desktops.
Make peculiar residents, look after them, play together and collect the evidence of a small life.
They cannot die. They have looked into it.
Meet Mabel, Pip or Oswald, or create someone entirely your own. The face you choose is the
face you take into the creator. Change its features, colours, name, quirks and backstory,
or draw a creature with your own hand. Appearance changes later keep its personality and history.README 전문 보기 ↗Play Shelf Life · Report a problem · Privacy · Delete your account
Small creatures. Long memories. A free, darkly comic creature game for phones and desktops. Make peculiar residents, look after them, play together and collect the evidence of a small life. Immortal. Unwashed. In arrears.
Every twelve minutes something goes wrong on the shelf. Somebody finds the rat poison, starts a cult, writes you out of their will or holds a funeral for a raisin. Up to three emergencies pile up while you are away. Each one is a card with two choices and a random, usually regrettable, result. Who is involved changes the odds: cute residents tend to get good endings, menacing ones bad endings, mysterious ones strange endings, and trust turns disasters aside. Some cards are written for a particular sort of resident (a Doomsayer, a Landlord, someone Theatrical) and tilt towards the ending that suits them. Each choice shows a plain risk read, from Safe enough to Ill-advised.
Every result pays souls. Souls buy coffins, and every coffin holds a cursed curio for the Cabinet of Curiosities: 34 of them, from Common to Unholy. Souls earned for life set what the neighbours call your house, from Suspiciously Normal to Unspeakable. Care, rounds and finished Playroom games pay a few souls too, so the slow loop feeds the fast one.
Turn over tonight’s omen once a night for free souls and a daily twist: double pay, cheaper coffins, better luck or one extra disaster. Nights in a row pay more, and the seventh leaves something rare on the step. Miss one night and the candle gutters but holds, once, until the next seventh night. Three unholy chores a day pay souls each and a free curio for the set. Nothing is ever taken away.
Missing a particular curio? A special order buys it outright, priced by rarity. A long run of coffins without anything cursed ends in one, and the coffin gets heavy when it is due. Doing the rounds pays for the first five each day.
A new household gets a six-step tour on the Shelf, one real action each, with a soft ring on the exact button: say hello, feed someone, clear the first emergency, turn over the omen, read a note and open the first coffin. Each step pays a few souls. Skip the tour is always there, and Help brings it back. One card above the shelf says what to do next; the desk grows over the first days; Help → Glossary explains every number in plain words. Established households never see any of it.
The shelf desk lists today in the Playroom. The daily challenge puts one arcade game in a modifier (a snack storm, an impatient undertaker, a backwards séance, flooded corners, forty-eight in all, one cycle every 48 days) that is the same for everyone on the same date. A strip on the arcade hub shows the last fourteen challenge days. It keeps its own best, and the first scoring run each day pays a bonus that grows with your streak. The docket is one Shelf Court case a day that pays a bonus for airing it. Seasons recur every year: from 15 October to 2 November, The Thin Season lets some coffins hold six seasonal curios, which stay in the cabinet once you have them. Streaks and sets are recorded as incidents. In the installed Android app, optional nudges (local notifications, no server) can tell you when the next emergency is due; they stay quiet overnight.
The Almanac is a calendar of monthly chapters, written in advance from October 2026 to December 2027 and then repeated, so it never runs out. Each chapter has a free 30 tier track (souls, four limited curios, a room set, a title and a badge), a countdown, and a Court spotlight case. XP comes from things you already do (care, emergencies, the omen, chores, Shelf Court, the arcade, expeditions), a little each, up to a limit every day. Three weekly challenges and a weekly chest sit beside it. Miss a chapter and its curios come back in Back Issues at a higher price, so nothing is lost for good. The Thin Season is the October flagship, with its own flavour lines under every emergency.
A streak freeze is earned for every seven days of the omen, the docket or the daily challenge (the shelf holds two) and covers one missed day on its own. Come back after a skipped day (16 hours or more) and the shelf has a "while you were away" card and a chest that grows with the days gone, up to seven. There is never a penalty for being away.
Collections group the curios, keepsakes and souvenirs into sets with prizes, shown in the Cabinet. Past Unspeakable the ranks go on as thirty Legacy ranks, each paying a title and a Legacy Token. The Collector's Exchange is where souls and tokens go once the cabinet is full: Back Issues, cabinet and portrait frames, rooms, woods, walls, accents and commissions. Everything the game offered free before stays free and owned. In the Android app, optional nudges also cover a way-back ladder (a day, three, seven, fourteen and thirty days away, then no more), new chapters and the weekly chest, each switchable on its own.
Meet Mabel, Pip or Oswald, or create someone entirely your own. The face you choose is the face you take into the creator. Change its features, colours, name, quirks and backstory, or draw a creature with your own hand. Appearance changes later keep its personality and history.
Tap a resident for food, attention and a wash. Individual care builds trust and unlocks furniture. Do the rounds gives everyone a modest top-up; Notes → Check the shelf collects their latest opinions. Drag residents and furniture to rearrange them. On a phone, hold first. Each row always has six positions: neighbours, furniture, wings, horns and personalities have real effects.
The cabinet gets its own screen. The same five destinations work on phone and desktop: Shelf, Play, Stories, Notes and More. Stories holds little adventures, household correspondence and the workshop; folders keep their contents within easy reach.
The cabinet also hosts small domestic scenes. Place appropriate furniture near residents and choose Play a scene, or let them improvise while you watch. Bowl servings, lamp switches, relationships, requests, case files and keepsakes persist. The residents remember how you treat them.
Pairs can develop three-chapter household subplots. Let two compatible residents live together, share plots, then stage a scene to see the ending. Rivals tell a different story after a feud and a truce. Their case file stays in each resident's relationship card and the Memory museum even after the rolling note board fills up.
Your residents now have plans. Share a personal care moment, finish a game together and choose how their story ends. Build a crumb observatory, audition a very small ghost or prepare an unbirthday: eight stories lead to sixteen illustrated keepsakes.
Each story offers two ways to play. An imperfect game or completed practice still counts; the important part is doing it with the resident who invited you. Do the care and game in either order, then choose the ending. Progress waits through closing, reloads and days away. There are no deadlines or streaks to lose, and another story is always available.
The keepsake album records the original maker. Replaying an ending makes a new household scene with the current resident; each different ending earns two discoveries once. Residents can recall the moments they actually shared. Collected memories survive renaming and rehoming, and their exact objects appear in the scene journal.
Time on the shelf leaves its own small record. Residents earn keepsakes after a day, a week, a month, one hundred days and a year; these are dated from their move-in, never expire and carry no streak or care penalty. Their habits, old names and changing moods also shape what they say about the life they have had here.
Shelf Court, four quick arcade games and one expedition. Arcade games are endless, come in waves, and end when your luck does. Instructions are one line. A quick 3, 2, 1 starts a run (any tap skips it) and Again is always one key away. Each game has three skulls to win, shown as a ladder on screen with what is left to the next one. The chosen resident reacts on the field and, on the result card, celebrates or sulks.
A daytime small-claims TV show, filmed live on the shelf. You are Judge Mortis (a skeleton in a powdered wig). Two of your residents sue each other over something petty: a coffin lent for "a nap" and returned with a stranger called Keith in it, a eulogy delivered while the deceased was in the front row eating a salad, a biscuit labelled "I WILL KNOW". Every other resident sits in the jury box, empty seats go to the neighbours (Geoffrey the Woodlouse, Susan, Uncle, Mrs Widow and friends), and a ghost studio audience reacts to everything under an APPLAUSE sign.
Each episode: the announcer, "all rise", opening statements, then you ask three of six questions. Some questions turn up a clue for your notes, some are zingers for the audience, some bring on a surprise witness. Things go wrong live: outbursts, a thrown tooth, a sleeping juror, a heckler who dated the plaintiff in 1850, a power cut, the cat walking across your bench. For the interactive ones you choose to bang the gavel (the jury respects order) or let it play (the ratings love chaos). There is a commercial break with ads for Keith Removals and Coffin-Fresh.
Then you rule: for the plaintiff, for the defendant, or "you're both idiots". One of those is the truth and the clues point at it. The jury votes, the audience reacts, and the loser gives a hallway interview. Stars come from the right verdict, ratings of 70 or more, and a jury of five or more agreeing. Rule justly and the winner trusts you a little more; rule against the resident who was right and they hold a real grudge. Twenty-four hand-written cases, eight of each verdict, each with alternate takes so reruns rarely repeat.
Reruns are not repeats. A case can have a twisted version in which the investigation turns up a different truth: the opening statements are the same, but the testimony, the case notes, the rulings and the hallway lines change, so a player who memorised the case has to read the notes again. Today's docket airs one fixed version for everyone; free play favours versions you have not seen; the Case Notebook lists every case with the versions you have seen and your stars on each, and never names one you have not. Your residents matter too: a resident's traits give it a role at the podium or in the jury box (keeping receipts, exaggerating, playing to the room, holding a grudge, dozing through the vote, gossiping), explained in the lobby under Why this cast matters and on the cast notes. Each episode you get one Objection: check a note marked unconfirmed (a lead is struck, an exaggerated clue is confirmed) or press the last witness for another clue. The ad break, the number of random scenes, the order of the questions and an occasional bonus question change from episode to episode; reruns offer to skip the opening and the hallway interview. Lifetime stars climb a ten rank bench career (Courtroom Sweeper to Lord Chief Gavel) that pays souls and dresses the courtroom. When a friend serves papers they choose which side their resident takes; a summons reward over the daily cap is owed and paid on the next day with room.
| Game | What you do |
|---|---|
| Feeding Frenzy | Your resident catches falling snacks and dodges holy water, soap and mousetraps. Combos multiply; a still-beating heart doubles everything for six seconds. It runs in waves: a feast, a holy procession that leaves one lane open, and Brother Aldous, who throws the water by the bucket. Get through him untouched and you are paid, and a skull comes back. |
| Coffin Stack | Drop coffins onto a growing tower under a sky that darkens with height. Overhang is sawn off; clean drops in a row pay more each time, and three in a row win some width back. A miss sends the whole coffin over the edge. |
| The Séance | Candles light in sequence, some after a held breath. Repeat it, and keep the pauses for a point a round; three in time in a row mend a spared mistake. It opens with three candles and each round adds one. The spirits forgive one mistake. |
| Grave Whack | Push the hands back into their graves before they climb out; a hand about to escape shakes. Never tap the widow. Stuffed gloves only break your streak, the landlord is worth five and the gold tooth is worth eight and mends a skull. |
| Shelf Court | Judge your residents' petty lawsuits on live daytime TV. The rest of the shelf is the jury. Twenty-four cases. |
| Expeditions | Pack a tool, choose a crew and recover parts for working household objects. |
Every scoring run pays souls from a daily purse of 160, beating your best pays a bonus, and a good run earns a little trust (within the usual daily cap). Each skull is worth about the same souls a minute in every game. Skulls are kept as medals on the hub cards; lifetime runs and medals open cosmetic arenas (Moonlit, Embers, Hard frost, Pea soup, Gilded), and the hub names the next one. Haptics (small buzzes, on by default, with a switch in More) and a quiet ambient bed for each game come with the sound, and effects thin out by themselves on a phone that cannot keep up. Little adventures ask for a specific game with the resident who invited you. Expeditions show your crew, packed equipment, route and exact choice consequences; two distinct recovered parts build a permanent household project.
Crumb Chase, Handshake, The Alibi, the old Shelf Court hearings, the Night Market and the play rug have been retired. Saves keep their lifetime counts and earned achievements; unfinished hearings and market trips are dropped. Shelf Court returns rebuilt from scratch, above.
Notes → Paperwork keeps the latest 120 filed documents independently of the temporary note board. Expedition reports, incident reports and resident documents are filed automatically. You can request a household register drawn from your actual care and activity records. Clearing notes preserves these documents. Empty filters and display areas explain what belongs there and how to add it.
On desktop, P opens the Playroom. Frenzy uses ← → or A/D, Coffin Stack uses Space, the Séance uses 1 to 4 and Grave Whack uses 1 to 9. In Shelf Court, Enter or Space advances dialogue. P pauses any run; a long stall or a hidden tab pauses it for you. Escape closes the current sheet and returns focus to its opener; the Back button does the same on a phone. Motion follows your system preference; More also offers Automatic, Light and Full effects, and a Calm mode that stills everything. The narrator is off until enabled. Light retains brief gameplay reactions and scene movement while simplifying decoration. See the expedition and paperwork design notes.
No account is needed to play, now or later. The game stores your household in this browser under
shelflife.v4. Clearing browser data removes that local copy.
Use More → Back up to download a copy. Move to another device offers the device share menu or a download and email draft. If you use the draft, attach the downloaded file yourself. On the other device, open Shelf Life and choose More → Restore. Restore previews the replacement; it does not merge two households. JSON and equivalent text backups are accepted.
Earlier save versions migrate automatically. Invalid positions are repaired. Storage failures show a warning; unreadable saves are preserved as downloadable recovery files. Keep a backup before clearing site data or switching devices.
When an edition is connected to a cloud project, More → Cloud save can keep a copy of your shelf on a server so it follows you between browsers and phones. Nothing is sent until you choose Turn on cloud save. Guest play is unchanged, and offline the game carries on and catches up later.
android-…, and when it was last saved. The sign-in session stays in this
browser under shelflife.cloud, never inside the save or its backups.With cloud save on, More → Friends swaps shelves with people you know. Nothing social is sent until you open Friends for the first time on that account; without a cloud project there is no Friends button at all, and a player without cloud save is pointed at Cloud save first.
Nothing is public: there is no search, no global list of names and no way to see a stranger's shelf. Every reply from the server is checked again before the game draws it: text is clamped and escaped, creatures are rebuilt from known parts, and a drawing is only accepted as a PNG under 120 KB.
After the first visit, the installed game also works offline. Returning players receive a Save & refresh banner when a new edition is available. An active interaction or failed save prevents that refresh. Installation is available through your browser's app or home-screen menu.
More → Report a problem (also in Help → For testers) is the main way to tell us something. Pick Bug, Confusing, Idea or Love it, write up to 1000 characters, optionally say what you were doing, and choose whether to attach diagnostics: the version and build, web or Android, the browser, screen size, effects and Calm settings, how many residents, days since the shelf started, the save's size, which sheet was open, whether the device was online and signed in to cloud save (never the email), and the last 20 errors the game caught. The game shows exactly what will be sent before it sends. With cloud save on it goes straight to the project (ten a day). Without it the report waits on this device and is sent once you sign in; Copy report puts the same text on the clipboard for an email. Nobody is ever signed in silently. The issue form still works.
Errors are kept on the device (the last 20, message, file, line and time, never the shelf) under
shelflife.errors, outside the save. The first one in a session brings up a small "Something on the
shelf broke. Tell us?" notice. Three times in a tester's first week (after day two, after the first
Shelf Court hearing, after day seven) a one-tap How is it going? card asks for a rating from 1 to 5;
"Not now" twice and it stops asking.
The version and the release notes live in src/version.js. More shows "Shelf Life 1.1.0 · build
2026-10-11", and the first launch after an update shows a short What's new sheet, once, never on a
brand-new install. Tap the version line seven times, or open the game with ?tester=1, for the
tester panel: start an emergency now, ready tonight's omen, finish the waits, move the clock a day on
(and back), replay the first-day tour, rerun the effects benchmark, see the save's size and contents,
export the save, check for an update, and pretend to be offline. Anything there that bends time or hands
out souls marks the save as a tester save for good, and a tester save never sends daily challenge scores
or Court verdict stars to the server. The weekly notes for testers are in src/content/tester-notes.js:
edit them there.
With cloud save on, the game also records which days each player opens it (one row per day, nothing else) so the owner can see retention. More → Help improve the game: count which days I play switches it off. Switching it off deletes the days already counted, including the days counted for coven invite rewards, and stops both until it is switched back on; while it is off, invite rewards cannot count that player's days. privacy.html says so.
Both tables are in supabase/migrations/0006_feedback_and_play_days.sql; nobody can read them through
the API. To look at them, open the project at supabase.com/dashboard:
public → feedback shows every report as a row. Click a
diagnostics cell to read it. play_days is one row per player per day.-- Latest feedback, newest first
select created_at, kind, rating, moment, body, doing, app_version, platform,
diagnostics -> 'errors' as errors, diagnostics, user_id
from public.feedback
order by created_at desc
limit 200;
-- Daily active players, the last 30 days
select day, count(*) as players
from public.play_days
where day > current_date - 30
group by day
order by day desc;
-- Retention: D1, D7 and D30 by signup week. "Signup" is when the player's cloud
-- account was created (auth.users.created_at), which is the first day the game
-- could count them. Only players who leave the day count switched on are seen;
-- a blank means too soon to tell.
with signups as (
select u.id as user_id, (u.created_at at time zone 'utc')::date as signup_day
from auth.users u
where exists (select 1 from public.play_days p where p.user_id = u.id)
), marked as (
select s.signup_day, date_trunc('week', s.signup_day)::date as signup_week,
exists (select 1 from public.play_days p where p.user_id = s.user_id and p.day = s.signup_day + 1) as d1,
exists (select 1 from public.play_days p where p.user_id = s.user_id and p.day = s.signup_day + 7) as d7,
exists (select 1 from public.play_days p where p.user_id = s.user_id and p.day = s.signup_day + 30) as d30
from signups s
)
select signup_week, count(*) as players,
round(100.0 * count(*) filter (where d1 and signup_day + 1 < current_date)
/ nullif(count(*) filter (where signup_day + 1 < current_date), 0)) as d1_pct,
round(100.0 * count(*) filter (where d7 and signup_day + 7 < current_date)
/ nullif(count(*) filter (where signup_day + 7 < current_date), 0)) as d7_pct,
round(100.0 * count(*) filter (where d30 and signup_day + 30 < current_date)
/ nullif(count(*) filter (where signup_day + 30 < current_date), 0)) as d30_pct
from marked
group by signup_week
order by signup_week desc;
-- Check-ins: how it is going, at each moment
select moment, count(*) as answers, round(avg(rating), 1) as average_rating
from public.feedback
where kind = 'pulse'
group by moment
order by moment;
test/feedback_sql.test.mjs runs these exact queries, so they are known to work.
No runtime dependencies, required backend or bundler. Plain ES modules, local styles, SVG creatures and
bundled licensed fonts. Optional cloud save talks to Supabase with plain fetch. Serve over HTTP;
opening index.html as a file does not support modules.
node test/serve.mjs
# Open http://localhost:4175
Node 24 and Python 3.12+ are used for release checks. Browser tooling is development-only:
npm ci
npm test
python3 -m unittest discover -s test -p '*_test.py'
npx playwright install chromium webkit
npm run test:browser
npm run build
The browser suite uses isolated synthetic households and a separate local server. It checks
creation, saved appearance, care, every arcade game, a full Shelf Court episode ruled rightly and one ruled wrongly, expeditions, completed adventure endings,
abandoned runs, emergencies, coffins and the curio cabinet, expedition returns, report persistence,
replay identity, all sixteen keepsakes,
cancelled gestures, failed saves, responsive layouts, cloud save and friends against an in-memory Supabase
(test/support/fake-supabase.mjs) and runtime errors
in desktop Chromium and phone-sized Chromium/WebKit. npm test also runs the SQL migrations in PGlite. Offline reload is tested in Chromium;
Playwright does not support WebKit service-worker tooling. This is browser automation, not physical-device
testing. test/responsive-harness.html provides additional local fixture exploration.
| Location | Responsibility |
|---|---|
src/state.js, src/life-state.js | Saves, validation, migration and bounded history |
src/mayhem-state.js, src/engine/mayhem.js, src/content/mayhem.js, src/content/emergencies-extra.js | Emergencies, souls, coffins, curios, omens, chores and ranks |
src/arcade-state.js, src/engine/arcade.js, src/ui/arcade.js | The four arcade games, records and payouts |
src/content/court.js, src/court-state.js, src/engine/court.js, src/ui/court.js, src/art/court-cast.js, src/art/court-hallway.js | Shelf Court: cases, episode state machine, TV studio stage, the drawn cast and the hall cam's corridor set |
src/content/court-twists*.js, src/engine/court-twists.js, src/engine/court-traits.js, src/engine/court-objection.js, src/engine/court-career.js, src/content/court-bonus.js, src/art/court-dress.js | Shelf Court replay: twisted case versions and their validation, trait-driven cast, the Objection, the bench career and its cosmetics, bonus questions |
src/cloud/, src/ui/cloud.js, supabase/migrations/ | Optional accounts and cloud save: client, sync, sheet and schema |
src/cloud/social.js, src/ui/friends.js, css/social.css | Friends, shelves on show, Shelf Court summonses and daily boards |
src/cloud/hearings.js, src/engine/court-joint.js, src/ui/court-joint.js, src/content/court-joint.js, src/engine/court-guests.js | The Joint Bench: sealed hearings between friends, the reveal, the record, and guests on the jury |
src/ui/social-registry.js, src/cloud/postbox.js, src/engine/visits.js, src/visits-state.js, src/ui/postbox.js, src/ui/guests.js, css/postbox.css | The Friends hub's plug points, the postbox, visits, treats, guestbooks, waves and levels |
Main publishes to GitHub Pages only after domain, Python and browser checks pass. Packaging excludes
development files and writes the commit to release.json; the same revision identifies the offline
cache. Add new production assets to service-worker.js and bump its development cache version.
The tests check that the offline shell is complete.
The Play Store edition wraps the same web build in Capacitor 8. android/ is the
native project Capacitor generated; it is committed, and npx cap sync only rewrites its generated files
(capacitor.build.gradle, capacitor.settings.gradle and the copied web assets). The web game has no
Capacitor imports: the npm packages exist only for the native project.
src/native.js is the only native glue. It does nothing unless Capacitor.isNativePlatform() is true, and it
reaches plugins through window.Capacitor.Plugins, which the native bridge provides. In the app:
--safe-area-inset-* variables that css/style.css folds into --sat, --sab, --sal and --sar.test/native.test.mjs and test/browser/native-app.spec.mjs check those paths against a fake bridge. That
is not a device test: try each of them on a real phone before a release.
You need Node 22 or newer, JDK 21 and the Android SDK with platform 36 and its build tools. Android Studio Otter (2025.2.1) or newer bundles all of it.
npm ci
npm run android:sync # npm run build, then copy dist/ into android/
npm run android:open # open the project in Android Studio
npm run android:apk # debug APK in android/app/build/outputs/apk/debug/
npm run android:bundle # release AAB in android/app/build/outputs/bundle/release/
The version lives in one place: VERSION in src/version.js, which More shows and every feedback report
carries. The app's versionName is that string and its versionCode is major * 10000 + minor * 100 + patch
(1.1.0 is 10100, 1.2.3 is 10203), worked out in android/app/build.gradle, which reads src/version.js.
Every upload to Play needs a higher versionCode, even a closed-test build with one fix, because Play refuses
a versionCode it has seen before. So for each upload: bump VERSION (a patch bump is enough: 1.1.0 to 1.1.1),
set BUILD to the day, set version in package.json to the same string (test/version.test.mjs fails until
they match), add a line to CHANGES if players should hear about it, then build the bundle.
Play only accepts signed bundles. Make an upload key once, keep it outside the repository and back it up with its passwords somewhere safe:
keytool -genkeypair -v -keystore ~/keys/shelflife-upload.jks -alias upload \
-keyalg RSA -keysize 4096 -validity 10000
Then create android/keystore.properties (gitignored, as are *.jks and *.keystore):
storeFile=/home/you/keys/shelflife-upload.jks
storePassword=the store password
keyAlias=upload
keyPassword=the key password
android/app/build.gradle reads that file only if it exists, so without it npm run android:bundle still
builds an unsigned bundle. With it, the bundle is signed with the upload key. Enrol in Play App Signing when
you create the app: Google keeps the key that signs what players install, and a lost upload key can be
reset from the Play Console rather than ending the app.
The resident on the icon is drawn once, as Android adaptive-icon layers in icons/src/:
background.svg, foreground.svg and monochrome.svg (Android 13 themed icons) on a 108dp
canvas of 432 units. Launchers show the middle 288 units; the resident and candle stay inside
the 264-unit safe circle. Every other icon is a crop of the same layers, so edit the sources and
render again:
node scripts/render_icons.mjs # web, maskable, Apple, Play listing and Android launcher icons
node scripts/render_store_art.mjs # store/feature-graphic.jpg, drawn with the game's own residents
node scripts/store_screenshots.mjs # store/screenshots/: eight 1080x1920 phone screenshots for Play
All three use Playwright's Chromium (set CHROMIUM_PATH to use another). render_icons.mjs writes
the Android launcher resources only when an android/ project exists. The screenshots use a synthetic
household, a fixed afternoon and a seeded random, so a commit always shows the same scenes; any over
1 MB is saved as a JPEG instead. store/listing.md drafts the Play listing, the content rating and Data
safety answers and the release checklist. store/ is not published with the site.
Cloud save stays off, with no cloud UI and no requests, until src/cloud/config.js has a project URL and key.
The copy in this repo already points at the project the published game uses. To run your own, follow the
steps below and replace both values. test/serve.mjs serves an empty config, so browser tests never reach
a real project (set SHELF_REAL_CLOUD_CONFIG=1 to serve the real one).
supabase/migrations/0001_accounts_and_saves.sql, then
supabase/migrations/0002_social.sql for Friends, then the later ones in order (0003_postbox_visits.sql
for the postbox, visits, treats, guestbooks and levels, through 0010_kin.sql). Running any of them again is harmless.{{ .Token }}, so players get a code, for example <p>Your Shelf Life code: {{ .Token }}</p>.
Adding an email to an anonymous account uses the change-email template.src/cloud/config.js. It is public by design; row level security keeps each save private. Never paste
a secret or service_role key: a unit test refuses to pass if one is shipped.Before release, verify receipt and successful code entry for both adding an email to a guest account
and signing in on another browser, using an address outside the project team. A successful HTTP
request alone does not prove delivery. See Supabase's SMTP requirements
and email templates. Keep SMTP credentials
in Supabase's server settings, never in src/cloud/config.js or the repository.
Abandoned anonymous accounts can be cleared from the SQL editor:
delete from auth.users u where u.is_anonymous and u.created_at < now() - interval '90 days' and not exists (select 1 from public.saves s where s.user_id = u.id and s.updated_at > now() - interval '90 days');
Old daily scores and summonses can go the same way (the lines are also at the top of 0002):
delete from public.scores where day < current_date - 30; and
delete from public.summons where created_at < now() - interval '60 days';
Reports wait in public.reports (select from it in the SQL editor); nothing in the game reads them.
The optional Mac launcher can use locally installed Daniel (Enhanced). Pages uses the voices provided by each visitor's browser; the local Mac voice is not distributed with the web game.
src/almanac-state.js, src/content/almanac.js, src/engine/almanac.js, src/ui/almanac.js, css/almanac.css | The Almanac: calendar, track, XP, weekly challenges, Back Issues and the screens |
src/engine/streaks.js, src/engine/returns.js, src/engine/claimables.js | Streak freezes, the return chest and one list of everything claimable |
src/engine/collections.js, src/engine/legacy.js, src/ui/collections.js | Sets, Legacy ranks and tokens, and the Collector's Exchange |
src/native.js, capacitor.config.json, android/ | The Android app: native glue, Capacitor settings and the native project |
src/version.js, src/errors.js, src/clock.js, src/diagnostics.js, src/tester-state.js, src/cloud/feedback.js, src/ui/testers.js, src/ui/feedback.js, src/ui/whats-new.js, src/ui/tester-panel.js, src/content/tester-notes.js, css/testers.css | Tools for testers: the version and release notes, caught errors, Report a problem, check-ins, play days, What's new and the tester panel |
src/engine/ | Testable gameplay rules and state transitions |
src/content/ | Traits, writing, activities and creator invitations |
src/art/ | Creature/drawing data, SVG rendering, animation and the studio |
src/ui/ | Views, interaction, navigation and accessible dialogs |
css/ | Shared materials and dedicated game/workspace layouts |
test/ | Domain regressions, synthetic households and browser tests |
icons/src/, store/ | Layered icon sources and Play Store art |
scripts/ | Pages packaging, icon and store art rendering, and the optional local Mac launcher |