Skip to content

Dog self-play fetch, and the delivery loop that never ended - #22

Merged
JOhnsonKC201 merged 5 commits into
mainfrom
feat/dog-self-play-fetch
Jul 31, 2026
Merged

Dog self-play fetch, and the delivery loop that never ended#22
JOhnsonKC201 merged 5 commits into
mainfrom
feat/dog-self-play-fetch

Conversation

@JOhnsonKC201

Copy link
Copy Markdown
Owner

Self-play was cat-shaped: whatever species you picked, stepping away summoned a butterfly. A dog stalking a butterfly reads as a recoloured cat, and the fetch machinery to do better was already in the renderer, only ever triggered from the tray.

Chasing that down surfaced a real bug in the dog's headline feature, so this branch fixes that first and then builds on it.

The fetch loop never terminated

Delivering set the ball to rest about 6px from the dog, well inside FETCH_GRAB (22px). On the very next frame the rest branch saw a ball within reach, picked it up, found itself already home, and "delivered" it again without moving.

Driving one throw through the overlay VM for 72 simulated seconds:

before after
ball phase flips 511 5
heart + chirp bursts 254 1
pantUntil pushed forward every frame one 4.2s pant
ball forgotten never (45s timer restarted each bogus delivery) on schedule

Only the first fetch ever looked correct, which is why a hand test passes and this shipped. A ball the dog has carried home is now marked delivered and left alone.

A dog left alone starts its own game

Self-play now routes by species. A cat still gets its butterfly; a dog noses its own ball out, chases it down and carries it home. Both run off the same idle gates and the same timers, so neither species feels livelier than the other, and the tray toggle, work mode, reduced motion and low power all suppress both.

Swapping species mid-visit flies the butterfly off rather than abandoning it. It is drawn for as long as bfOn is set, so routing dogs straight to fetch would have frozen it in mid-air on screen for the rest of the session.

Tray said "treat", app threw a ball

pets.js already carried the comment "tray/menu wording differs per species so the app never says treat at a dog that is actually being handed a ball" and that is exactly what shipped: the dog menu read "Give a treat 🦴" while giveTreat() sent the ball channel. The label lived in pets.js, the channel in a separate conditional in main.js, so they drifted.

Each species now owns one giveLabel + giveChannel pair read from the same registry entry, which deletes the conditional. playLabel was dead on arrival (defined, never referenced); it is now playNoun + playToggleLabel and actually wired, so a dog owner sees "Ball to chase" and "Work mode (stay put, no ball)" instead of two hardcoded mentions of a butterfly.

npm run lint was unusable locally

It walked CODEPATH Proj/'s vendored python venv and the _*.js scratch files, reporting 1484 errors and 105 warnings from code this repo does not own. CI never caught it because it checks out a clean tree where those untracked paths do not exist. The flat-config ignore list now mirrors .gitignore.

Test plan

  • npm test - 69 pass, up from 57 on main
  • npm run lint - exits 0 with no warnings (it could not run to completion before)
  • npm run test:boot - real Electron app launches and renders
  • New tests/fetch.test.js: the fly/rest/carry sequence, exactly one delivery per throw, the pant timer going quiet, the ball being cleaned up
  • New tests/self-play.test.js: each species gets its own companion and never the other one, the shared schedule, and every gate that turns it off
  • Worth eyeballing on a real desktop: leave a dog idle for ~30s and watch it start a game unprompted

`npm run lint` walked `CODEPATH Proj/`s vendored python venv and the `_*.js`
scratch files, reporting 1484 errors and 105 warnings that all came from code
this repo does not own. Lint was effectively unusable locally.

CI never caught it because it checks out a clean tree where none of those
untracked paths exist, so the break only ever showed up on a real working copy.

Mirror the .gitignore entries in the flat-config ignore list. Lint now exits 0
with no warnings.
pets.js already carried the comment "tray/menu wording differs per species so the
app never says treat at a dog that is actually being handed a ball", but that is
exactly what shipped: the dog menu read "Give a treat" with a bone while
giveTreat() sent the `ball` channel. The label lived in pets.js and the channel
was chosen by a separate conditional in main.js, so the two drifted.

Give each species one `giveLabel` + `giveChannel` pair and read both from the same
registry entry, which deletes the conditional in giveTreat(). `playLabel` was dead
on arrival (defined, never referenced); it is now `playNoun` + `playToggleLabel`
and actually wired, so a dog owner sees "Ball to chase" and "Work mode (stay put,
no ball)" instead of two hardcoded mentions of a butterfly.

Two tests lock it: every species must expose all four fields and a give channel
the preload really subscribes to, and the wording must match the payload.
Delivering set the ball to 'rest' about 6px from the dog, which is well inside
FETCH_GRAB (22px). On the very next frame the rest branch saw a ball within reach,
picked it up, found itself already home, and "delivered" it again without moving.

That loop ran at frame rate and never terminated. Driving one throw through the
overlay VM for 72 simulated seconds: 511 ball phase flips, 254 heart-and-chirp
bursts, and `pantUntil` pushed forward every frame so the dog panted permanently.
The ball was never forgotten either, because the 45s timer restarted on each
bogus delivery. Only the FIRST fetch looks correct, which is why a hand test
passes and this shipped.

Mark a ball the dog has carried home as delivered and leave it alone. Same throw
now runs 5 phase flips, one burst, one 4.2s pant, and the ball is forgotten on
schedule.

tests/fetch.test.js pins the whole cycle: the fly/rest/carry sequence, exactly one
delivery per throw, the pant timer going quiet, and the ball being cleaned up.
Self-play was cat-shaped: whatever species you picked, stepping away summoned a
butterfly. A dog stalking a butterfly reads as a recoloured cat, and the fetch
machinery to do better was already there, only ever triggered by the tray.

Route self-play by species. A cat still gets its butterfly; a dog noses its own
ball out, chases it down and carries it home, on the same idle gates and the same
timers, so neither species feels livelier than the other. The tray toggle governs
both, and work mode, reduced motion and low power suppress both.

Swapping species mid-visit flies the butterfly off rather than abandoning it: it
is drawn for as long as bfOn is set, so routing dogs straight to fetch would have
frozen it in mid-air on screen for the rest of the session.

tests/self-play.test.js covers the routing (each species gets its own companion
and never the other one), the shared schedule, and every gate that turns it off.
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pixelcat Ready Ready Preview Jul 31, 2026 8:19am

@JOhnsonKC201
JOhnsonKC201 merged commit 6c2999d into main Jul 31, 2026
4 checks passed
@JOhnsonKC201
JOhnsonKC201 deleted the feat/dog-self-play-fetch branch July 31, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant