Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d2ac525
Give Angloop its own bow (the lure) and its own special move
claude Oct 19, 2026
70b17cf
Draft: Gemperor's own flip (the slam) and its own special move
claude Oct 19, 2026
1eba597
Purrloop's own bow (a cat's stretch) and paw prints as its special move
claude Oct 19, 2026
cac10d4
Lanternling swings like a lantern, and moths come to its light as its…
claude Oct 19, 2026
9caf20e
Flarelet's flame shoots up out of its bowl, and tongues of flame as i…
claude Oct 19, 2026
82b4afe
Roamtail chases its own tail
claude Oct 19, 2026
32c0eb5
Filchfox vanishes in a puff of smoke and pops up further on
claude Oct 19, 2026
8dd9851
Rumblefluff scrapes the ground and charges with sparks off its horns
claude Oct 19, 2026
39cf8ca
Prismist's spin sets the colors of its rainbow running
claude Oct 19, 2026
063138a
Twiggle's spin whirls leaves off its crown
claude Oct 19, 2026
1dce244
Needlenub closes into a bud and blooms
claude Oct 19, 2026
8f5922e
Captain Quill dances the hornpipe and fires cannonballs as its specia…
claude Oct 19, 2026
ec98ac0
Drakwing glides in a long arc with a puff of fire, and breathes fire …
claude Oct 19, 2026
36973c5
Lillypot sinks low and blows bubbles
claude Oct 19, 2026
fe64182
Steamo does a sauna dance in clouds of steam, and steam clouds as its…
claude Oct 19, 2026
827ff71
Parcelbeak bows and a letter flies out of its pouch
claude Oct 19, 2026
912f864
Rapierre fences; docs: every adult has a trick and a special move of …
claude Oct 19, 2026
4e151a2
Docs: every adult form has a trick and a special move of its own
claude Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion app/pet/assets/angloop_body.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,33 @@
"h": 32
},
"duration": 500
},
{
"filename": "angloop_body lure 11",
"frame": {
"x": 352,
"y": 0,
"w": 32,
"h": 32
},
"duration": 150
},
{
"filename": "angloop_body lure 12",
"frame": {
"x": 384,
"y": 0,
"w": 32,
"h": 32
},
"duration": 150
}
],
"meta": {
"app": "tools/build_blob_sprites.py",
"image": "angloop_body.png",
"size": {
"w": 352,
"w": 416,
"h": 32
},
"frameTags": [
Expand Down Expand Up @@ -160,6 +180,12 @@
"from": 9,
"to": 10,
"direction": "forward"
},
{
"name": "lure",
"from": 11,
"to": 12,
"direction": "forward"
}
],
"faceTop": 9,
Expand Down
Binary file modified app/pet/assets/angloop_body.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion app/pet/assets/purrloop_body.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,23 @@
"h": 32
},
"duration": 500
},
{
"filename": "purrloop_body stretch_low 11",
"frame": {
"x": 352,
"y": 0,
"w": 32,
"h": 32
},
"duration": 1000
}
],
"meta": {
"app": "tools/build_blob_sprites.py",
"image": "purrloop_body.png",
"size": {
"w": 352,
"w": 384,
"h": 32
},
"frameTags": [
Expand Down Expand Up @@ -160,6 +170,12 @@
"from": 9,
"to": 10,
"direction": "forward"
},
{
"name": "stretch_low",
"from": 11,
"to": 11,
"direction": "forward"
}
],
"faceTop": 14
Expand Down
Binary file modified app/pet/assets/purrloop_body.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
224 changes: 218 additions & 6 deletions core/src/scene.ts

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions docs/gameplay.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ the more often it works. Once it knows a trick, it shows it off by itself now
and then when it is in a good mood. While nobody is there, it practices a
little on its own (see [While nobody is there](#while-nobody-is-there)).

Some forms do a trick in their own way. It looks different on the panel, but
the gesture and what it does are the same.
Every adult form, and some children, do one trick in their own way. It looks
different on the panel, but the gesture and what it does are the same.

## The guessing game

Expand Down Expand Up @@ -746,7 +746,8 @@ A new creature should make you think "that one is different".
They show in a trick done in its own way (`OWN_TRICKS`: a creature with
wings flutters through its flip; the gesture and the effect stay the
same), in its special move in a scuffle, or now and then while it stands
around.
around. Every adult form has one trick of its own and a special move
(the particles in `hit`) that no other form uses.

#### How to work

Expand Down
17 changes: 17 additions & 0 deletions docs/pets.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ over its head, and the bulb hangs in front of its brow as a light that swings
when it breathes and glows at night. Its sides have grown into fins it uses as
arms, and bubbles float in its jelly. It has the child's round eyes and a wide
grin with two little fangs, and that big mouth makes it a little hungrier.
For its bow it casts out its lure, lets the bulb dangle and flash, and snaps; in a scuffle its special move is a swarm of little lights like its bulb.

<details>
<summary>Show how to get it</summary>
Expand All @@ -57,6 +58,7 @@ into a crown of crystals, one of them broken off, and the bulb of the lure has
sunk into its belly, where it glows as its core. One crystal fist is planted
on the ground, the other raised, and it looks out from under brave brows. It
rarely gets sick. At night its crystals and its core glow.
A king doesn't somersault: for its flip it leaps up and lands so hard that crystals shoot up out of the ground, and crystal shards are its special move.

</details>

Expand All @@ -80,6 +82,7 @@ The child was well cared for at least 85 % of the time.
heart where the bulb was, whiskers and two little paws that knead. Its amber
cat eyes mostly give you the slow, content blink of a cat that loves you.
Hearts mean more to it, and it stays clean longer.
Its bow is a cat's stretch, front down with a yawn, and its special move leaves paw prints.

</details>

Expand Down Expand Up @@ -233,13 +236,15 @@ With a fire of its own, nothing of this line ever shivers on a cold morning.
its mood drops more slowly, it gets sick less often, and in the rain it needs
no umbrella. Its face is calm, with soft round eyes and rosy cheeks. Asleep,
it burns low.
For its spin it swings to and fro like a lantern on its handle, and its special move is a flight of moths drawn to its light.

![Flarelet](images/pets/flarelet.png)

**Flarelet**, a flame that has burst out of its crust and burns in the open;
what is left of the crust is the bowl it sits in. It burns through its food
and hardly gets tired, and it throws sparks. It looks eager: wide eyes under
brows that flick up, and a big grin with a fang.
For its flip its flame shoots up out of the bowl and rains embers, and tongues of flame are its special move.

</details>

Expand Down Expand Up @@ -349,6 +354,7 @@ knotty walking stick with a ribbon of the scarf tied round it. It needs no
backpack of its own: on a trip it wears the one everybody gets. Used to long
roads, it hardly tires. It looks calm and friendly, under lids lowered in
peace, with a gentle smile.
For its spin it chases its own tail.

![Filchfox](images/pets/filchfox.png)

Expand All @@ -357,6 +363,7 @@ at the back, one ear is bent, its fur is ruffled and the tip of its tail is
ragged. It is always after something to eat and up for any game. Its look is
cheeky: one eye narrowed, the other with a glint in it, and a sly grin with a
fang.
For its spin it vanishes in a puff of smoke and pops up a little further on.

</details>

Expand Down Expand Up @@ -405,6 +412,7 @@ high, and its horns are two golden curls of lightning that end in a bolt; the
right one is bigger and forks. In a real thunderstorm they flash now and then.
It looks stern, under heavy brows, and even a bad day hardly makes it grumpier
than it is.
For its bow it lowers its head, scrapes the ground and charges, sparks flying off its horns.

![Prismist](images/pets/prismist.png)

Expand All @@ -414,6 +422,7 @@ rainbow fades; when the sun comes out after it, its colors run along the arch
for half an hour. It still wears
Nimbit's bell. It looks kind, with lashes and a small smile, loves being
petted, and dirt hardly weighs it down.
When it spins, the colors of its rainbow run round as they do after the rain.

</details>

Expand Down Expand Up @@ -463,13 +472,15 @@ leaves like clouds sit on a gnarled trunk, one reaching far to the right, and
the pot stands on its roots. It has a calm old face with half-shut eyes and a
beard of moss, and it is patient: it gets tired more slowly. In a scuffle,
leaves fly off its crown.
When it spins, leaves whirl off its crown.

![Needlenub](images/pets/needlenub.png)

**Needlenub**, a round cactus in the same pot that wants nothing more than to
be petted. Its little arms are always up for a hug, petting means more to it
than to anyone, and the one flower on its head only opens when it is happy;
otherwise it is a bud. It stays in its pot rather than walking.
For its bow it closes into a bud and blooms with a pop, petals flying.

</details>

Expand Down Expand Up @@ -515,12 +526,14 @@ duck likes the rain, so nothing of this line needs an umbrella.
horn where the curl was, its wide orange bill puffs little flames, it has
small bat wings and a spiky tail, and its belly is still duckling yellow. A
fire needs feeding: it gets hungry faster. In a scuffle, it breathes embers.
For its flip it flaps up and glides down in a long arc with a puff of fire, and its special move is a burst of fire.

![Captain Quill](images/pets/quill.png)

**Captain Quill**, an old sea duck in a captain's cap with the curl sticking
out under it, a sailor collar and a red scarf. It loves a good game, and in a
scuffle it blows bubbles.
Its dance is a sailor's hornpipe, and in a scuffle it fires cannonballs.

</details>

Expand Down Expand Up @@ -567,13 +580,15 @@ The stages last as long as the slime's. What the adults keep is the towel.
is tucked into the towel on its head. It is calm and half asleep anyway: it
gets tired more slowly, and it is wet enough not to need an umbrella. In a
scuffle, it blows bubbles.
For its bow it sinks low, as into its pond, and blows bubbles.

![Steamo](images/pets/steamo.png)

**Steamo**, the sauna master. Pink from the heat, eyes closed in bliss, a
towel wound round its head like a turban and steam rising off it. It is
always fresh from the sauna: it stays clean longer and gets sick less often.
In a scuffle, it lets off steam.
Its dance is a sauna dance in clouds of steam, and clouds of steam are its special move.

</details>

Expand Down Expand Up @@ -620,13 +635,15 @@ crest; only the beak tells them apart.
long needle beak like a rapier, the crest swept back like a musketeer's plume.
It never lands: it hovers, one wing a blur. A hummingbird burns through
everything: it gets tired and hungry faster.
Its spin is a fencer's lunge, twice, with a flash at the tip of its beak, and in a scuffle its blade flashes.

![Parcelbeak](images/pets/parcelbeak.png)

**Parcelbeak**, a pelican mail carrier with a postman's cap, the crest
sticking through it, a huge orange bill and a mail bag across its chest. With
a letter for everyone, it is always in a good mood: its mood drops more
slowly. In a scuffle, letters fly out of its bag.
For its bow a letter flies out of its pouch.

</details>

Expand Down
36 changes: 36 additions & 0 deletions tools/build_blob_sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,26 @@ def wax_arms(candle):
ANGLOOP_B = ANGLOOP[:4] + [r[:20] + r[21:27] + "." + r[27:] for r in ANGLOOP[4:9]] + ANGLOOP[9:]
ANGLOOP_FACE_TOP = 9 # counted from the bulb
ANGLOOP_TAGS = species_tags([ANGLOOP, ANGLOOP_B], 20, 14)
# Its own bow, the lure: it casts its line out in front and lets the bulb dangle, flashing (the
# bulb light, then bright), before it snaps. The stalk arcs further out, the rest stays the same.
ANGLOOP_LURE_TOP = [ # columns 17 on; a space keeps what is there
"..ooooooo....",
".obbbbbbbo...",
"obooooooobo..",
"obo.....obo..",
"obo.....obo..",
"obo.....obo..",
"obo.....obo..",
"bbo.....obo..",
"bbo......o...",
" ggg.",
" ggwgg",
" ggggG",
" GGG.",
]
ANGLOOP_LURE = [r[:17] + "".join(o if t == " " else t for o, t in zip(r[17:], top))
for r, top in zip(ANGLOOP, ANGLOOP_LURE_TOP)] + ANGLOOP[len(ANGLOOP_LURE_TOP):]
ANGLOOP_TAGS["lure"] = [(ANGLOOP_LURE, 150), ([r[:25] + r[25:].replace("g", "w").replace("G", "g") for r in ANGLOOP_LURE], 150)]

# Purrloop: what the slime becomes when it is petted a lot – a cat of jelly. Ears of jelly (the
# right one folded over at the tip), the bulb of the lure turned into a heart, whiskers, and two
Expand Down Expand Up @@ -603,6 +623,22 @@ def wax_arms(candle):
PURRLOOP_FACE_TOP = 14 # counted from the heart
PURRLOOP_TAGS = species_tags([PURRLOOP, PURRLOOP_B], 20, 14)


def cat_stretch(grid, row, drop):
"""A cat's stretch: the front (on the right) sinks and the back stays up; column x loses
round(drop * x / width) rows at `row` and moves down by as much."""
width = len(grid[0])
cols = []
for x in range(width):
col = [r[x] for r in grid]
n = round(drop * x / (width - 1))
cols.append(["."] * n + col[:row] + col[row + n:])
return ["".join(c[y] for c in cols) for y in range(len(grid))]


# Its own bow: front down in a long stretch with a yawn
PURRLOOP_TAGS["stretch_low"] = [(cat_stretch(PURRLOOP, 10, 7), 1000)]

# Gemperor: the slime grown into a king. The jelly on its back has hardened into a crown of
# crystals, one of them broken off, and the bulb of the child's lure has sunk into its belly,
# where it glows as its core. One crystal fist planted on the ground, the other raised. It still
Expand Down
17 changes: 16 additions & 1 deletion tools/pet_preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ const NEW_SCENES = (["egg", "hatch", "baby", "child", "hint", "evolve"] as const
NEW_LINES.map(([line]) => `${line}_${what}`)).concat(NEW_LINES.flatMap(([, , a, b]) =>
[a, b].flatMap((adult) => ["", "_sad", "_sleep", "_trick", "_rain"].map((what) => `${adult}${what}`))),
["wild_forest_leaf", "wild_stream_hippo", "visit_beak", "visit_duck"]);
// The adult forms with a trick of their own (OWN_TRICKS in core/src/scene.ts) and that trick; each
// gets a scene with the trick ("<form>_own") and one with its special move in a scuffle ("<form>_special")
const OWN_TRICK_FORMS: Record<string, string> = {
angloop: "bow", gemperor: "flip", purrloop: "bow", lanternling: "spin", flarelet: "flip", roamtail: "spin",
filchfox: "spin", rumblefluff: "bow", prismist: "spin", twiggle: "spin", needlenub: "bow", quill: "dance",
drakwing: "flip", lillypot: "bow", steamo: "dance", parcelbeak: "bow", rapierre: "spin",
};
const OWN_SCENES = Object.keys(OWN_TRICK_FORMS).flatMap((adult) => [`${adult}_own`, `${adult}_special`]);
// The spoiler-free overview shows one adult form only, the one docs/pets.md shows openly. It
// leaves out the scenes that are only there to show another form, another line or what the lure
// gives away, and plays the remaining scenes of an adult with that form. Of the trips it shows
Expand All @@ -77,7 +85,7 @@ const SPOILERS = ["hint", "evolve", "cyclopuff", "angloop_hint", "sneezle", "fli
"prismist_hat", "prismist_sad", "prismist_sleep", "prismist_trip", "prismist_rain",
"gemperor", "gemperor_night", "gemperor_sad", "gemperor_hint",
"angloop", "angloop_night", "angloop_hungry", "angloop_hint",
"purrloop", "purrloop_happy", "purrloop_night", "purrloop_hint", ...NEW_SCENES];
"purrloop", "purrloop_happy", "purrloop_night", "purrloop_hint", ...NEW_SCENES, ...OWN_SCENES];
const PETS_DIR = "docs/images/pets";
const DAY = new Date(2026, 9, 12, 13, 0);
const NIGHT = new Date(2026, 9, 12, 23, 30);
Expand Down Expand Up @@ -460,6 +468,13 @@ const SCENES: Record<string, Scene> = {
visit_beak: [{ stage: "adult", species: "needlenub", visitor: { species: "rapierre", age: 60, left: 500 } }, DAY, null],
visit_duck: [{ stage: "adult", species: "parcelbeak", visitor: { species: "drakwing", age: 60, left: 500 } }, DAY,
null],
...Object.fromEntries(Object.entries(OWN_TRICK_FORMS).flatMap(([adult, trick]) => [
[`${adult}_own`, [{ stage: "adult", species: adult, activity: "trick", activity_item: `${trick}:ok` }, DAY, null]],
[`${adult}_special`, [{
stage: "adult", species: adult,
trip: trip("stream", { event: "battle", foe: "puddlin", outcome: "win", rounds: [["charge", "flip", "special"]] }),
}, DAY, null]],
])),
};

const assets = readAssets(BDF_FONT_DIR);
Expand Down
Loading