From ecf99f0ad18f3c82ba670b59d2fc59ca59ece65a Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Wed, 26 Aug 2026 17:36:23 -0400 Subject: [PATCH] flower eater: she walks again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The girl who eats flowers, drawn in 2015, promised a one-button game in 2017, given a mood in 2019, and sung in 2021, gets her revival lane: CONCEPT.md assembles the four-era canon from the papers platter, the whistlegraph registry and the drawings archive, sets the decree — the game is a pure function of clock and input, and the server never trusts a result it didn't re-simulate — and maps the oskiewar.com pattern onto flower-eater.com (owned, parked) with live Porkbun prices for the defensive grabs. The prototype is an AC piece: she strides on her own through a scrolling ink meadow, the only input is the chomp, and what she eats moves her mood through the song's arc — grieving, searching, hungry, bright, furious — tinting the paper and bending her stride. Stars rain down the margin like the Tall Flower Eater. Six hundred headless frames clean; the real test is a thumb at aesthetic.computer/flower-eater. Claude-Session: https://claude.ai/code/session_01NhnRj8HGzW9Y7fk9doZ67m --- flower-eater/CONCEPT.md | 157 ++++++++++++++ .../aesthetic.computer/disks/flower-eater.mjs | 193 ++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 flower-eater/CONCEPT.md create mode 100644 system/public/aesthetic.computer/disks/flower-eater.mjs diff --git a/flower-eater/CONCEPT.md b/flower-eater/CONCEPT.md new file mode 100644 index 0000000000..6154118eb8 --- /dev/null +++ b/flower-eater/CONCEPT.md @@ -0,0 +1,157 @@ +# Flower Eater + +A revival. The girl who eats flowers has been drawn, coded, painted, and +sung across a decade of Jeffrey's work; this lane brings her back as a +living title at flower-eater.com, run on the oskiewar.com pattern. + +## The four canonical layers + +1. **2015 — the character.** Two drawings on drawings-rey-sc.jas.life: + *Flower Eater* (#3636, pencil — wild-eyed girl mid-bite, stems around + her, one nervous flower with a face) and *Tall Flower Eater* (#3639, + ink — the same figure striding, flowers and stars raining down the + margin). Her line is the art direction: hand-drawn ink, big eye, + flowing hair, striding legs. +2. **2017 — the constraint.** The platter's software history: "One button + web game about a girl who eats flowers," with the domain and a scaffold + repo (`whistlegraph/flower-eater.com` — the game itself never landed + there). The real engine work is the `mood` engine in C, source and + videos in the Dropbox archive (see the excavation note below); BW-Flower + is its native-C sibling from the same year. +3. **2019 — the mood.** A second Flower Eater: the role-painting game + (TikTok 6759484642547518726, 2019-11-15 — the whistlegraph registry + disambiguates it from the song). Role and mood as the mechanic, not + points. +4. **2021 — the song.** Whistlegraph `[flwe]`, 2021-04-09: "I set a flower + down at the foot of the grave of my friend. Looked for so long for the + one that I love… Mystery flower, please don't cover. I am angry now." + Goodiepal session audio in Dropbox; Mystery Flower folded in later. + The verses are already a game loop: searching, grabbing stems, flowers + that cover, anger. + +## The fused design + +Keep 2017's radical constraint and give it 2019's depth: + +- **One button.** She strides on her own. The only input is the chomp. + Timing a bite on a passing flower is the whole physical skill — + playable on a phone with a thumb, on a pad with any button, on a + keyboard with space. +- **Mood is the score.** Each flower species shifts her mood, and mood + bends the world: palette, stride tempo, music, what sprouts next. + Appetite as emotional weather. Chomping air stumbles her. The song's + emotional arc (grief → searching → mystery → anger) is the mood-space + the flowers move her through. +- **Her line is the renderer.** Ink strokes on paper, traced from the + 2015 drawings — the same "the fight speaks" restraint as oskiewar's + stick fighters, but in her hand. +- **The song is the title theme.** The 2021 whistlegraph opens the game; + the mood engine can quote its phrases per mood. + +## Infra: the oskiewar.com pattern + +Everything below is proven in production by the oskiewar lane as of +2026-08-26; this lane clones the shape, not the code. + +- **Domain**: flower-eater.com is owned but parked at GoDaddy. Un-park → + point at lith (see the lith direct-DNS recipe) → Caddyfile block beside + oskiewar.com's. +- **The piece**: one self-contained game source + a web shell. + Deterministic 60 Hz sim, a test suite whose failures are diffed against + a recorded baseline, a monotonic buildVersion whose commit subjects are + the changelog, a social-preview burner gated in deploy. +- **Replays**: `ac.flowerdemo` format, an upload endpoint in + system/netlify/functions, pronounceable round URLs — with a blocklist + in the name seeder (lesson: oskiewar's generator rolled slur-adjacent + names into public URLs). +- **Reels**: an ACCOUNTS entry in toolchain/instagram/ig.mjs when the IG + account exists; changelog captions from day one ("flower eater v1 — …" + over "tell reelboy what to plant."); reelboy routes are already + account-parameterized, so the feedback loop is a `bind` away. +- **Prototype first**: the AC piece `flower-eater` (disks/flower-eater.mjs) + is the lab — hot reload, phone testable, shareable at + aesthetic.computer/flower-eater — where stride, chomp and mood get their + feel before the standalone carve. `i.mjs` was the earlier seed and its + TODO named this exact excavation. + +## The decree: api surface, contract, stack + +Everything below is a consequence of one sentence, learned the hard way in +the oskiewar lane on 2026-08-26: + +> **The game is a pure function of (clock, input) → (pixels, sounds, +> signals, demos) — and the server never trusts a result it didn't +> re-simulate.** + +1. **One host contract, already proven.** The game is one self-contained + `.mjs` file speaking only through injected host functions — the exact + surface oskiewar defined (`runtime`, `gamepad`, `drum`, `synth`, + draw primitives, `gameSignal`, `saveReplay`). That contract is why one + oskiewar file runs identically in the browser shell, the native BIOS, + the offline reel oven, and a `new Function` test harness — and why a + production API bug could be found by simulating a round headlessly and + posting it. Flower Eater inherits the surface verbatim; shells are the + reusable half of the estate. +2. **The demo IS the api.** `ac.flowerdemo` v1: seed, build, and the chomp + ticks. One button means a complete playthrough is a list of integers — + a run is literally a rhythm, tweet-sized. So the server-side contract + collapses to: `POST /api/flower-runs` receives {seed, build, chomps[]}, + **re-simulates them with the same .mjs sim in Node**, derives the + score/mood-arc itself, and stores the canonical result. No trusted + client scores, no row-length regexes to drift (the 26→32 checkpoint + lesson): the validator and the game import ONE shared contract module + (`shared/` exists for exactly this) and the ultimate validator is the + sim itself. +3. **Surface, whole:** + - `POST /api/flower-runs` — submit chomps; server resims; returns canon. + - `GET /api/flower-runs?day=…` — the day's garden (leaderboard feed). + - `GET /api/flower-stats` — the oskiewar-stats twin. + - `flower-eater.com/` — watch any run re-simulated in place + (pronounceable names, blocklisted). + - **The daily meadow**: seed = date hash, everyone eats the same field; + ghosts come free (any run's chomp-rhythm can walk as a translucent + girl in your meadow — replay overlay, no multiplayer server at all). +4. **Stack: JavaScript all the way down, no exceptions.** The estate's + native tongue is dependency-free `.mjs` — the piece runtime, lith's + Express functions, the test harness, the reel oven, reelboy. The same + sim file is the client, the server validator, and the replay renderer; + introducing a second language would split the decree's one truth into + two. The 2017 mood engine's C ideas port INTO the sim; a native C++ + BIOS host can carry her to console later exactly as it carries + oskiewar, without changing a line of the game. +5. **Storage**: MongoDB collection beside oskiewar-replays; no Redis, no + session-server in v1 (one button, single player, ghosts are replays). + +## Domains (checked 2026-08-26 via registry RDAP) + +flower-eater.com is already owned (parked at GoDaddy). Around it: + +| domain | status | +| --- | --- | +| flowereater.com | taken | +| flowereater.net / .org | **available** | +| flower-eater.net | **available** | +| flowereater.game / flower-eater.game | **available** | +| flowereater.gg | **available** | +| floweater.com | **available** | +| flwe.co | **available** (registry code, short-link material) | +| eatflowers.com, mysteryflower.com | taken | + +Porkbun prices (live via `npm run domain check`, 2026-08-26): +flowereater.org $7.98 · floweater.com $11.08 · both .nets $12.52 · +flowereater.gg $51.80 · flwe.co $109.47 (premium) · both .game forms +$309.47 (premium). The primary move is still un-parking the owned .com; +.org and floweater.com are the cheap defensive grabs, .game is a vanity +tax. Keys live in `aesthetic-computer-vault/.env` (PORKBUN_API_KEY / +PORKBUN_SECRET_API_KEY — if the tool says they're missing, the decrypted +.env is stale against .env.gpg; re-decrypt). Registration + DNS + Caddy +wiring is `npm run domain` (toolchain/domains/). + +## Excavation still owed (needs @jeffrey) + +- The `mood` engine C source + videos in the Dropbox archive — worth + reading before the standalone build, in case the 2017 engine holds + mechanics the fusion should honor. +- Un-parking the domain at the registrar. +- Deciding whether the 2021 song's stems (Goodiepal session) can be cut + into the title loop, and whether the reissue wants its own IG handle. diff --git a/system/public/aesthetic.computer/disks/flower-eater.mjs b/system/public/aesthetic.computer/disks/flower-eater.mjs new file mode 100644 index 0000000000..0d7c59936c --- /dev/null +++ b/system/public/aesthetic.computer/disks/flower-eater.mjs @@ -0,0 +1,193 @@ +// Flower Eater, 2026.8.26 +// One button game about a girl who eats flowers — the 2017 concept walking +// again. She strides on her own through a scrolling meadow; the only input +// is the chomp, and timing a bite on a passing bloom is the whole physical +// skill. What she eats moves her MOOD (the 2019 role-painting idea), and +// mood is the score: it tints the paper, bends her stride, and names +// itself after the 2021 whistlegraph's arc — grieving, searching, hungry, +// bright, furious. Art direction traces the 2015 drawings: ink line, big +// eye, flowing hair, striding legs, stars raining down the margin. +// Lineage + lane plan: flower-eater/CONCEPT.md at the repo root. + +const { sin, cos, abs, min, max, floor, random, PI } = Math; + +// The stride is the clock. Everything else phases off how far she has +// walked, so speed changes (mood!) ripple through flowers, stars and hair +// without a second timeline. +let walked = 0; +let stride = 1.6; +let chompAt = -99; // frames since bite, for the jaw arc +let stumbleAt = -99; // frames since a bite of plain air +let frame = 0; + +// Mood runs -1 (grieving) .. 1 (furious) and drifts home toward searching. +// Sweet blooms warm it, bitter blooms cool it, a mystery flower jolts it +// somewhere she didn't choose. +let mood = 0; +let eaten = 0; +const moodWord = () => + mood < -0.6 ? "grieving" + : mood < -0.2 ? "searching" + : mood < 0.3 ? "hungry" + : mood < 0.7 ? "bright" : "furious"; + +// Flowers live in world-x; the meadow scrolls past her fixed screen spot. +// Each is a stem with a bloom, and the rare mystery one grows a face. +let flowers = []; +let nextSprout = 220; +const SPECIES = [ + { name: "sweet", petals: 6, size: 9, moodShift: 0.18, weight: 5 }, + { name: "bitter", petals: 5, size: 8, moodShift: -0.22, weight: 3 }, + { name: "mystery", petals: 8, size: 11, moodShift: 0, weight: 1 }, +]; +function sprout(worldX) { + const roll = random() * 9; + const species = roll < 5 ? SPECIES[0] : roll < 8 ? SPECIES[1] : SPECIES[2]; + flowers.push({ x: worldX, species, height: 26 + random() * 22, + sway: random() * PI * 2, eaten: 0 }); +} + +// Falling margin stars, after the Tall Flower Eater's border rain. +let stars = []; + +function boot({ screen }) { + walked = 0; mood = 0; eaten = 0; flowers = []; stars = []; + chompAt = -99; stumbleAt = -99; frame = 0; + nextSprout = 200; + for (let index = 0; index < 12; index++) + stars.push({ x: random(), y: random(), fall: 0.2 + random() * 0.5 }); + for (let ahead = 160; ahead < screen.width + 300; ahead += 90 + random() * 90) + sprout(ahead); +} + +function sim({ screen }) { + frame += 1; + // Mood decides the pace: fury runs, grief drags. It also decays home. + stride = 1.2 + (mood + 1) * 0.55 + (stumbleAt > frame - 20 ? -0.8 : 0); + stride = max(0.5, stride); + walked += stride; + mood *= 0.9993; + // The meadow keeps growing just past the right edge of the world. + if (walked + screen.width + 120 > nextSprout) { + sprout(nextSprout + screen.width); + nextSprout += 80 + random() * 130; + } + // A bitten flower wilts over a second, then the meadow forgets it. + for (const flower of flowers) if (flower.eaten) flower.eaten += 1; + flowers = flowers.filter((flower) => + flower.x > walked - 60 && flower.eaten < 60); + for (const star of stars) { + star.y += star.fall / 240; + if (star.y > 1) { star.y = -0.05; star.x = random(); } + } +} + +// The one verb. Any tap, any key: she bites whatever bloom is at her +// mouth, and biting nothing at all costs her the stride for a beat. +function chomp() { + chompAt = frame; + const mouthWorld = walked + 34; // just ahead of her face + const bloom = flowers.find((flower) => !flower.eaten && + abs(flower.x - mouthWorld) < 16); + if (!bloom) { stumbleAt = frame; return; } + bloom.eaten = 1; + eaten += 1; + const { species } = bloom; + mood = species.name === "mystery" + ? max(-1, min(1, mood + (random() * 2 - 1) * 1.2)) + : max(-1, min(1, mood + species.moodShift)); +} + +function act({ event: e }) { + if (e.is("touch") || e.is("keyboard:down:space") || + e.is("keyboard:down:enter")) chomp(); +} + +function paint({ wipe, ink, line, circle, box, write, screen }) { + // Paper tinted by mood: cold grey-blue grief through warm paper to an + // angry blush. Ink stays ink. + const warm = (mood + 1) / 2; + wipe(228 + warm * 20, 222 - abs(mood) * 8 + warm * 6, 216 - warm * 36); + const inkColor = [28, 26, 32]; + const ground = floor(screen.height * 0.78); + const girlX = floor(screen.width * 0.3); + + // Margin star rain. + for (const star of stars) { + const x = floor(star.x * screen.width); + const y = floor(star.y * (screen.height - 8)); + ink(90, 86, 96); + line(x - 2, y, x + 2, y); line(x, y - 2, x, y + 2); + line(x - 1, y - 1, x + 1, y + 1); + } + + // Ground: a hand-ruled line with a scribble every few steps. + ink(...inkColor); + line(0, ground, screen.width, ground); + for (let x = -(floor(walked) % 24); x < screen.width; x += 24) + line(x, ground + 2, x + 7, ground + 5); + + // Flowers scroll by in world space. + for (const flower of flowers) { + const x = floor(flower.x - walked + girlX); + if (x < -20 || x > screen.width + 20) continue; + const wilt = flower.eaten ? min(1, flower.eaten / 40) : 0; + const sway = sin(flower.sway + walked * 0.02) * 3 * (1 - wilt); + const top = ground - flower.height * (1 - wilt * 0.5); + ink(...inkColor); + line(x, ground, x + sway, top); + const { species } = flower; + if (!flower.eaten) { + for (let petal = 0; petal < species.petals; petal++) { + const angle = (petal / species.petals) * PI * 2 + walked * 0.01; + ink(...inkColor); + line(x + sway, top, x + sway + cos(angle) * species.size, + top + sin(angle) * species.size); + } + if (species.name === "mystery") { + ink(...inkColor); + circle(x + sway - 2, top - 1, 1); + circle(x + sway + 2, top - 1, 1); + line(x + sway - 2, top + 3, x + sway + 2, top + 3); + } + } else { + ink(120, 116, 122); // petals gone: a bare, greying crown + circle(x + sway, top, 2); + } + } + + // The girl: head, one big eye, flowing hair, leaning torso, scissor + // legs phased off the walk, one arm reaching for what's next. + const phase = walked * 0.11; + const bob = abs(sin(phase)) * 2; + const headY = ground - 44 - bob; + const jaw = max(0, 8 - (frame - chompAt)) / 8; // bite arc decays fast + const stumble = stumbleAt > frame - 20; + ink(...inkColor); + circle(girlX, headY, 8); // head + circle(girlX + 3, headY - 1, 2.5); // the big eye + ink(...inkColor); + // mouth: open with the bite + line(girlX + 6, headY + 3, girlX + 10 + jaw * 5, headY + 3 - jaw * 4); + line(girlX + 6, headY + 3, girlX + 10 + jaw * 5, headY + 3 + jaw * 3); + // hair, flowing back harder the faster she goes + for (let strand = 0; strand < 3; strand++) + line(girlX - 4, headY - 6 + strand * 3, + girlX - 14 - stride * 5 - strand * 3, + headY - 4 + strand * 4 + sin(phase + strand) * 2); + // torso leans into the walk (and buckles on a stumble) + const hipY = ground - 22; + line(girlX, headY + 8, girlX - (stumble ? 4 : -2), hipY); + // reaching arm + line(girlX - 1, headY + 14, girlX + 12, headY + 12 + sin(phase) * 3); + // scissor legs + const kick = sin(phase) * 9; + line(girlX - (stumble ? 4 : -2), hipY, girlX + kick, ground); + line(girlX - (stumble ? 4 : -2), hipY, girlX - kick, ground); + + // Two quiet words of HUD; the meadow speaks otherwise. + ink(...inkColor); + write(`${eaten} · ${moodWord()}`, { x: 6, y: 6 }); +} + +export { boot, sim, act, paint }; -- 2.51.2