You are browsing as a guest. Sign up (or log in) to start making projects!

Pet Productivity

  • 3 Devlogs
  • 41 Total hours

A gamified productivity app for Android. You describe a task you actually did in plain language ("studied 2 hours of calculus") and an AI judge rates its difficulty and pays you in stats that evolve your virtual pet (Egg → Baby → Adult → Master). Neglect it and it dies crystallized — only a difficulty ≥9 task revives it. Includes photo-verified pomodoro sessions (Gemini Vision checks you're actually working), groups of 2–6 that share a pet, real-time "Frenzy" mode when several members work at once, and an isometric room you decorate with gold. .NET 10 MAUI client + ASP.NET Core server on Heroku + PostgreSQL.

Open comments for this post

4h 58m 45s logged

Moved the server to Heroku — and a container that died in silence

Migration week. The server moved from Render to Heroku (student credits, plus Render’s cold starts were the slowest thing in the whole app). It should have been “change the remote, copy the config vars” — instead the dyno crashed on boot in under a second, exit 0, without printing a single byte. Not one log line. I swapped in a trivial entrypoint (worked), added boot breadcrumbs (never printed), pinned –platform=linux/amd64 (not it), and finally reduced the entrypoint to a bare exec printenv — which also produced zero output. That was the tell: nothing that went through sh -c survived. Root cause: my Dockerfile ended with ENTRYPOINT [“/bin/sh”, “-c”, “ASPNETCORE_HTTP_PORTS=${PORT:-8080} exec dotnet …”], and Heroku re-wraps your dyno command in its own sh -c. The inner string loses its quoting, so the outer shell parsed my entire command as just a variable assignment — a perfectly valid shell program that does nothing and exits 0. The fix is almost insulting: make the ENTRYPOINT a single token (/app/start.sh) — one argument can’t be re-split by anyone else’s shell. Cutover is done: config migrated, JWT secret rotated on purpose (all old sessions invalidated), auto-deploy on push wired up, the Heroku support ticket closed with the real root cause, and Render is now suspended. Verified working on a real phone.

A pet died in production and it was my fault. The anti-decay “absence shield” (long absence → your pet sleeps instead of dying) keyed off the last-activity date — which only got set when you logged a task. So: install the app, look around, never log anything, come back a week later → last activity is null, you fall through to the normal decay rule, and your pet starves without ever being protected. Found it with my own account: pet “Mario”, 50 XP, crystallized. Now birth itself stamps the activity anchor — creating an account counts as activity — plus a streak guard so your first-ever task counts as day 1 instead of day 0. The dev-only pet-state diagnostic endpoint I added to investigate is staying; it’s what exposed the whole case.

Furniture now drags like you’d expect. Press a piece and carry it around the room, with a magnet snapping it to the cell under your finger (grab-offset aware, silently refusing invalid cells). Tap-to-move and the D-pad still work. While in there I deleted the entire per-item “scale mod” system — eyeballed multipliers per furniture keyword — because the drawn width now simply is the isometric width of the item’s footprint. The double bed had 36% excess width and was climbing up the wall; now it sits exactly in its cells. Also measured real footprints for 19 more items against their actual sprites (tables, kitchens, piano, fireplace, aquarium…).

Rugs are floors now. They got their own slot: they don’t block furniture, furniture doesn’t block them, and they render in a separate pass so they’re always underneath — you can finally put the sofa on the rug. Also swapped the pretty-but-unreadable glass modal cards for opaque ones (text over a busy animated room was a lottery), fixed the birth ceremony’s name panel overlapping the creature, and the group-creation archetype picker is now a swipeable carousel.

0
0
2
Open comments for this post

9h 5m 32s logged

Wall paintings you can put a sofa in front of, and the app finally explains itself

Big week in three acts, all deployed to Render today.

Art. The pet finally has its full sprite set — 11 poses (idle, happy, sad, sleeping, crystallized…) plus the legacy statues that dead pets leave behind in the room. Best lesson of the batch: two of the three things that “looked wrong” with the new art were the renderer’s fault, not the artist’s. The sunset tint was multiplying over every sprite and drowning the palette, and furniture was drawing at nearly double the intended scale. Fixed the renderer and the exact same PNGs suddenly looked right.

Room editing rework. Decorating used to be “buy it and it lands somewhere”. Now there’s a real edit mode: visible grid, ghost preview anchored to the piece’s front corner, tap the destination cell to move it, red flash if it doesn’t fit. Every catalog item now declares its footprint in its info.json — beds and sofas are proper 2×1 rectangles instead of fat squares, and rotating a piece swaps its footprint. The fun part: wall items. Paintings, windows and clocks now hang on dedicated rails along the two back walls and don’t occupy floor cells, so you can put a sofa in front of a painting like a normal human. Their height on the wall is auto-calibrated per sprite, because a fixed offset that looked fine for a clock launched a bookshelf out through the top of the wall.

The app explains itself now. Fresh-install testing showed the game was unreadable to anyone who isn’t me: what’s “Growth”? why ×1.2? what’s gold even for? Shipped a one-time intro card per screen (replayable from Settings), tap-anything explanations for every stat and mechanic, and a full animation pass — bars tween, gold counts up, +XP/+Gold float off the pet, evolutions flash, and the tic-tac-toe ritual celebrates a line only when you complete one, not when the page loads. Two rules kept it cheap: counters animate the viewmodel value (never the label text), and the Frenzy pulse rides the diorama’s existing frame tick instead of adding another timer.

The autonomous review loop also kept paying rent: it caught the “Level 3” chip contradicting the “Baby” stage shown two lines below it (two different formulas for the same concept), group member statuses hardcoded in Spanish while their English translations sat orphaned in the localization table, cancelling Google login being reported as a login failure, an entire welcome screen nothing navigates to anymore (deleted), and my favorite — when your pet is dead and your task isn’t hard enough to revive it, the client was string-matching a phrase the server never actually sends, so instead of “still crystallized” you got the full confetti celebration with “+0 XP · +0 Gold”.

0
0
3
Open comments for this post

27h 18m 56s logged

Fixed the game feel: pet was levitating, blurry, and out of place

Deep dive into “why does this feel off” today, with actual profiling instead of guessing.

Performance: the Shop tab took 4.9s to open even with everything supposedly cached. Turned out the real cost wasn’t network or sprite loading — it was rebuilding 10 category filter chips by clearing and re-adding them one by one to a bound ObservableCollection, each Add triggering a full CollectionView layout pass. Batching that into a single collection assignment plus caching the catalog for the session dropped it to 11ms. Also found the room diorama’s frame times were fine on average (8ms of a 40ms budget) but spiked to 28ms — turned out to be GC pressure from allocating a new SKPaint + blur filter per shadow, per frame, 25 times a second. Reusing them cut the worst frame to ~12ms.

Visual bug: the pet sprite looked blurry, floated above the floor, and had a system emoji stuck to it that never matched the pixel-art style. Root cause: the pet wasn’t actually part of the room’s canvas — it was a separate XAML overlaid on top, scaled with bilinear filtering (blurry) while the furniture underneath uses nearest-neighbor (crisp pixels), and centered in its box instead of anchored to the floor tile where its shadow already was. Moved the pet into the same Skia canvas as the furniture, anchored to its shadow, and dropped the emoji. Now it reads as actually standing in the room instead of floating over a photo of one.

Also fixed a 10-day false “server is down” alert (uptime monitor was sending HEAD requests, endpoint only accepted GET → automatic 405 → reported as downtime the whole time the server was fine) and wired Sentry into the Android client so crashes on real phones stop being invisible.

Also shipped a live demo page so people can try the core mechanic without installing anything — type what you actually did today, and the real Gemini-powered judge rates its difficulty and shows what your pet would have earned: petproductivity.onrender.com/demo.html

0
0
21

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…