Game
Grass: the lawn itselfGR
The lawn itself, cell by cell. How it bends and stands back up, how weather, sun and the way you mow change it over days, and what each condition does to a mower.
The grass is alive. It bends under your feet and the mower and stands back up, it shows stripes that flip as you walk round, and every 0.3 m cell has its own condition: wet, lush, shady, scorched or flattened by rain. Two cells a metre apart can mow completely differently, and the lawn remembers how you treated it.
The player's view
A morning job. The lawn is silvery with dew and your steps leave dark green trails where the dew was knocked off. Push the mower in and the deck shoves a bow wave of grass ahead of it, the wheels press two lines, and the grass shivers round the deck in the blade's wind until you let go of the bar. At 8:00 the wet grass is heavy and clumpy; come back at noon and the same lawn mows light.
Stripes. Walk to the other end of the lawn you just striped and every band flips, light to dark. They're boldest when you look low across the lawn and almost vanish from straight above. A diagonal pattern keeps clean edges, and if you come back the same day the bands are still crisp.
A week later. The sunny side has grown taller than the shady corner. After a storm, the long grass lies flat with the wind, and a pass against the lay catches what a pass along it misses.
A heat wave. The strip along the drive browns first, then the mound, while the strip in the house's shadow stays green and the grass under the tree goes thin and dry. Your footprints stay in the brown grass: the lawn needs water. Mowing high helps the next day; mowing short makes it worse, and three weeks of it kill the turf.
Every patch sounds and looks different. A dewy patch squelches and spits clumps, a scorched one crackles and puffs dust, weeds whip and shed fluff. Tall grass carries seed heads, a sunny dewy morning glitters, and yesterday's clippings have turned straw-coloured.
Ideas
GR1: grass that moves
Cosmetic, so the same on every preset and nothing to sync. The lawn reacts to whatever moves through it, then recovers.
The trample field
- A top-down texture holding a push direction, an amount and an age for the grass round you. Anything moving
stamps into it every frame; a fade pass brings it back to zero.
Grass.shaderreads it next toGM_WindOffsetand bends each tuft away from the push, its shadow too (or bent grass casts upright shadows). No chunk rebuilds, one texture read per vertex. - It is one 51 m window round the local player, ~10 cm a texel, that wraps round as you walk. It covers every lawn
near you: client gardens, town front lawns and grandma's lawn seen from the hub (
YardGrassreads it too). - Recovery by height: short grass (≤ 5 cm) is back in ~3 s, 8 cm in ~10 s, tall grass (15 cm+) in ~40 s. A wet lawn recovers ×1.5 slower; a drought-stressed one ×(1 + 3 × scorch) slower (the footprint test, GR2). Each tuft works out its own recovery from its height, so the field never needs to know what grows where.
- The feel: a push eases in over ~0.15 s instead of snapping (a foot stays down 0.2 s, a deck holds its push); the field is blurred a little round each tuft and stamps have soft edges; the blow's lean and the trample add up as vectors. A tuft that gets cut shrinks from its old height to its cut height over 0.45 s instead of popping.
- Who stampsYour feet (a print each stride, pushed the way you walk, harder when running), the other sibling and strolling clients the same way; the mower's bow wave ahead of the deck, the deck and its two wheel lines; a rolling ball. The dog and the cat get the same stamper once they roam the garden (for now they sit outside the fence).
- Dew trailsWhile the lawn is wet (dew until ~11:00, longer in shade; DY3, WE3), the dewy lawn has a silvery sheen and a trampled cell shows the plain green where the dew was knocked off. The trail fades as the lawn dries. A morning lawn shows where everyone walked.
- Tall grassA rustle that grows with height and speed; a slower walk on foot, ×0.9 at 20 cm down to ×0.75 from 35 cm; a puff of seeds from tall grass you brush past (from 20 cm).
- Spring-back plugs inMowing.md MO6's laid cells write into the same field with a 2-5 minute hold instead of the normal fade, then pop up as tufts. One system for the look; "laid" stays the gameplay rule.
- The limitOne tuft per 0.3 m cell, so a footprint bends about one tuft rather than drawing a crisp print. It reads fine in the chunky style.
The blade's blow
The air leaves the deck through its openings, like the stones (mowing.md MO3), and makes the grass round the deck lean out and flutter.
- A ring round each running deck (up to 4), worked out in the grass shader: no field channel, so no shimmer trail behind the deck. It is stronger at the front gap on the discharge side with a bag fitted (most of the air goes into the bag: a soft ripple), out of the discharge without one (a blast straight behind), a long jet to the right from a side chute, and the grass ahead blows flat when the deck is tipped up.
- The grass leans out from the deck with the air (up to ~30° at full blow) and flutters a little on top, showing the paler undersides of the blades.
- Strength = (blade speed)², from
MowerVisual.BladeSpeed01(~0.9 s to spin up, ~2.2 s to run down; guests run it from the blades-on flag). It builds as the blade spins up and dies away as it runs down: letting go of the bar fades it over the 2-3 s run-down (engine-stop bar) or in well under a second (brake clutch), and a bogging engine weakens it. ×1.4 with the high-lift blade, ×0.6 on slow throttle.
Mowing stripes
Stripes are light, not geometry. Grass bent away from you shows the long sides of its blades, which catch the sky (a light band); grass bent towards you shows tips and shaded undersides (a dark band). Walk to the other end and every band flips. No new model is needed: it's shading plus a fine record of which way the grass lies.
Why a stripe map, and not the old per-cell lie (each cut cell scaled its colour by 1 + dot(lie, view) × 0.24):
- Blocky band edges: a deck is ~1.5 cells wide, so edges followed the grid and a diagonal pass became a staircase.
- Same contrast from every height: real stripes nearly vanish looking straight down and are strongest looking low across the lawn.
- Flat brightening: real bands get their glow from the sky on the blades and their darkness from shaded undersides, so they read best with a low sun and soften under cloud.
- A roller looked like a rotary, while real roller stripes are much bolder.
- The stripe map (
StripeMap): a top-down texture at 7.5 cm a texel (a 4 × 4 record per cell), direction + amount, written as you push by the deck's cut circle swept along your path. Band edges are straight lines along your real path, diagonals stay clean, an overlap takes the last pass's direction. The grass and the stripe mat sample it per pixel. The per-cell lie inLawnGridstays the sim's truth (streak, grain, memory); the map is the look. - Shading, with
d= how much the grass lies away from the camera (−1..1) andg= how low you look ((1 − |view.y|)^0.8, floor 0.15):- brightness
1 + k × g × light × d(k = 0.30 rotary, 0.60 roller); - a sky sheen on light bands (a pale sky tint × 0.12 × d × g) and shaded undersides on dark ones (−10% × |d| × g);
light: ×1.2 with a low sun, ×1 at midday, ×0.6 overcast (LawnWeather, DY3's sun).
- brightness
- Roller ×2 contrast, lasting 4 cm of regrowth.
- Patterns (garden.md GA6: checkerboards, diagonals, circles) come out crisp for free.
- Co-op: each machine draws the stripes of every mower it sees: its own through the cut, the others from their avatars. Cells changed some other way (a merge, the trimmer, lawn care) are brought back in line with the grid after 0.4 s.
- Coming back crisp: the bands are usually gone by the next visit (a rotary's after ~1.5 cm of regrowth, about a day; a roller's after ~4 cm, about three days), but a quick return would show blocky bands rebuilt from per-cell values. A band edge always runs parallel to the stripe, so each cell only needs where the edge crosses it: a 3-bit band-edge offset in lawn memory (eighths of a cell, 0 = no edge). On load the map is rebuilt from the cell directions plus the offsets, with straight edges (bit layout in Tech).
Needs: MO1 · Done when: the bands flip as you walk round a freshly striped lawn, are strongest looking low across it and faint from straight above, a diagonal pattern has clean edges, and leaving and coming back the same day shows the same crisp bands; walking through 15 cm grass leaves a trail that stands back up over about 40 s, the mower pushes a bow wave, the grass shivers round the deck until the blade stops, a morning lawn shows dark dew trails, and the guest's footsteps bend the grass on the host's screen.
GR2: grass condition and scorch
Every cell has a condition, built from the weather, the cell's own grass and its place in the garden. The mower reads the condition, never the weather.
Pluggable to weather: three layers
- Lawn weather (
LawnWeather, per hour, the whole garden): the values the lawn reads from weather.md, nothing computed here. Wetness, soil moisture, temperature, heavy rain in the last 24 h, the wind's direction, and frost, heat and leaf fall. - Grass condition (
GrassCondition, per cell, derived, never saved): the lawn weather + the cell's base (seeded patch, thickness, GA2 health) + its lasting conditions from lawn memory + its site. Recomputed per chunk when the hour or the cell changes. - The mower's response (
GrassResponse): a data table, one row per condition (the response matrix below).MowerSimandLawnGridread the cell's combined response.
- Until weather.md WE3, the lawn weather comes from the calendar: wetness from dew and rain, soil moisture from the last ten days' rain, heat 0.35 in a dry streak of 4+ days in spring and summer, heavy rain on half the rain days, a wind direction per day. The sandbox can override it.
- New weather later (a cold snap WE10, a drought WE11) = weather.md fills a lawn-weather value and the matrix gets or tunes a row. No mower code. Heat waves wait for WE11.
- Wet keeps loading the deck through the mower's humidity, as before GR2: the matrix's wet row only drives grip, clog, sound and spray, so job balance doesn't move. "Lush" comes from feed and soaked soil only (thickness already sets the mass).
Every cell has its own condition
- InstantWet, frost, leaf cover. Recomputed from the hour's lawn weather, gone when the weather changes.
- LastingScorch, days fully brown and the usual height (this idea); moss and thatch (GA2); frost marks; scalps
and scuffs (MO1); smothered (MO6). They build up over days, live in lawn memory, and a daily tick (
LawnDay) updates them from the day's lawn weather. - The site mapEach cell's sun exposure, paving edge, mound or dip, and tree roots. Worked out from the garden's layout, never saved.
The site map, per cell (Garden/GardenSite.cs):
- Sun exposure (0-1, the share of the day in direct sun): six positions of
DayCycle's sun arc, a raycast per cell, past the house, trees, hedges, fences and sheds. Tree shade is dappled: a canopy lets ~25% of the light through (deciduous trees far more when bare, with seasons); hedges, walls and houses block it all. Neighbours count: the houses next door and street trees within ~15 m. Shade keeps dew and frost longer and slows growth; sun scorches; damp shade grows moss (GA2). - Paving edge: lawn within 0.6 m of a hard surface (mowing.md MO2: drive, path, patio, slabs).
- Mound or dip: from the grid's bumps and wet dips.
- Tree roots: within 1.5× a tree's canopy radius.
Scorch: sun-exposed grass browns in dry heat
Real turf lore, kept close: grass browns first where it's hottest and driest, and long grass survives heat best (its blades shade the crown and the soil, and it grows deeper roots). Lawns usually go dormant, not dead: brown, then green again after rain.
Each day, in the daily tick:
scorch += 0.12 × heat × site heat × exposure × (1 − moisture) × shade(height now) × roots(usual height) × torn tips
- Heat1 in a heat wave, 0.35 in a dry streak (4+ dry days), else 0.
- MoistureThe lawn weather's soil moisture, times the site: mound top ×0.6, dip +0.3, under a tree ×0.5 (its roots take the water: dry shade), paving edge ×0.7 (thin soil). Watered: at least 0.7.
- Site heatPaving edge ×1.4 (heat comes off the stone). Brown strips along the drive come first.
- Height now: shadeThe height after the last cut. 3 cm ×1.5, 5 cm ×1, 8 cm ×0.7, 10 cm+ ×0.6 (linear between). Raising the blade helps from the next day.
- Usual height: rootsThe running average of the height after the last 4 cuts. 3 cm ×1.3, 5 cm ×1, 8 cm ×0.8, 10 cm+ ×0.75. Raising the blade once helps the shade at once, the roots only over weeks. It starts at the client's height and is kept in whole cm.
- Torn tips (0-3, from a dull blade, MO1): ×(1 + 0.15 per level). Ragged tips lose water.
- Heals with soil moisture:
−0.08 × clamp((moisture − 0.3) / 0.4)a day (−0.08 in rain, about −0.04 on a normal day), −0.2 a day watered; never below 0. - Caps: 0.3 in a dry streak (the mild version, possible now in late spring); 1 in a heat wave.
- Dormant from 0.6: brown but alive, green again 1-3 weeks after rain or watering.
- Dead: lawn memory counts the days fully brown (scorch 1) in dry heat. After 12, the crowns die and the cell goes bare (the existing bare patches): only seed brings it back (GA2 overseeding, the seed shaker). A scalp on dormant turf leaves bare soil too.
- What it adds up to: a week of heat wave in full sun on dry soil reaches ~0.6-0.8 (dormant, recovers). Three weeks at a normal height kill only the hottest strip (the drive edge); cut short, most of the sunny lawn dies; kept long, almost none. The strip in the house's shadow stays green throughout.
- The footprint test (a real gardener's test): on drought-stressed grass, footprints and wheel tracks stay. The trample field recovers ×(1 + 3 × scorch) slower, so at 0.6 your prints last three times as long (the field remembers 180 s). If your footprints stay, the lawn needs water: a hint with no HUD.
- Look: the shader's per-cell dry value is
max(dry patch, scorch): straw from ~0.4, brown past 0.75; dormant tufts droop a little; dead cells draw as bare soil. - Work it creates: clients notice brown patches (and blame the mower if you scalped in the heat); watering (a sprinkler, a hose job: WE14); reseeding dead cells (GA2); feed on scorch above 0.3 burns (GA2).
Sun, shade and growth: an uneven lawn
- Growth follows the sun: each cell's daily growth ×
(0.6 + 0.4 × exposure), on top of its own vigour and patch growth (dandelions faster, dry patches slower). Density too: thickness ×(0.7 + 0.3 × exposure), and the shader pales and thins shady tufts. - So heights go uneven between visits: on a normal week, ~10 cm of regrowth in full sun against ~7 cm in deep shade, varying smoothly, and every cut levels it again. The mower feels it (load follows the mass), shady corners need mowing less often but look patchier, and moss sets in there (GA2).
Rain-flattened grass (lodging)
- After heavy rain in the last day, uncut grass taller than ~12 cm lies flat in patches (seeded noise), wet and lush cells most, laid the way the wind blew.
- It mows like spring-back's laid grass (mowing.md MO6): each pass along the lay leaves 40-60% of the excess (one try per pass, so a second pass along catches more), tufts popping up minutes later; a pass against the lay takes it all. It stands back up once the rain is two days old.
- Derived from the recent weather and the cell's height: nothing saved. Drawn through the trample field's hold. Storm clean-up work comes with it (WE8).
The response matrix
Each condition has a strength 0-1 (wetness, scorch, weed cover…); a factor f applies as 1 + (f − 1) × strength.
Starting numbers, tuned in the sandbox. - = no effect.
| Condition (strength) | Mass | Bag fill | Clog | Dust | Grip on turns | Deck bob | Left standing | Scalp harm | After-mark | Cut sound | Off the deck |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Wet (wetness) | ×1.6 | ×1.5 | ×1.8, clumps above 0.5 | none | −40% | - | - | ×1 | clumps smother | squelch, wet slap | wet spray, clumps |
| Dry patch | ×0.6 | ×0.8 | ×0.8 | ×1.5 | - | - | - | ×2 | - | crisp | dust puffs |
| Scorch (dormant from 0.6) | ×0.5 | ×0.7 | ×0.7 | ×2.5 | - | - | - | ×3; above 0.6 bare | straw / brown tint | dry crackle | dust, straw bits |
| Dead (bare) | ×0 | - | - | ×2 | - | - | - | - | needs seed | gritty scrape | dust, soil |
| Lush (feed, soaked soil) | ×1.4 | ×1.3 | ×1.2 | - | - | - | - | ×0.8 | - | thick, the engine labours | dense green spray |
| Shade (1 − exposure) | ×0.7 | ×0.8 | - | - | - | - | - | ×1 | paler, thinner | soft, sparse | light spray |
| Clover | ×0.85 | ×0.9 | - | - | −20% when wet | - | - | ×1 | - | soft | leaf bits, flower heads |
| Coarse weeds | ×1.8 | ×1.2 | ×1.3 | - | - | - | stalks (dull blade, sharpness < 0.5) | ×1 | - | stringy whip | stalk bits, dandelion fluff |
| Seed heads | ×1.1 | - | - | - | - | - | 30% spring back (sharpness < 0.6) | - | - | tick of stalks | seed fluff |
| Lodged (rain-flattened) | ×1.1 | - | ×1.2 | - | - | - | 40-60% of the excess, any blade; stands back up | - | - | uneven swish | patchy spray |
| Thatch | ×1.2 | ×1.1 | ×1.2 | - | - | ±1 cm | - | ×2 on soft spots | - | muffled thud | brown fibres |
| Moss | ×0.4 | ×1.3 (bulky) | - | - | −35% | - | - | ×1 | - | soft hush | moss crumbs |
| Frost | ×1 | - | - | - | −25% | - | - | - | black prints ~3 days | crunch | ice glitter |
| Leaves (cover) | ×1.5 | ×2 | ×1.5 (×2.5 wet) | - | −15% | - | - | - | - | rustling shred | leaf confetti |
| New turf | ×0.8 | - | - | - | - | - | - | scalp = bare | ruts ×2 | soft | fine green |
- Stacking: mass, bag fill, clog, dust and scalp harm multiply; grip multiplies what's left (−40% and −20% = ×0.48); deck bob takes the largest; wet over 0.3 cancels dust.
- Clean speed isn't a column: it comes from the combined mass (
CleanSpeed = optimal / √mass). A dewy, lush, weedy cell at 8:00 (wet 0.6, lush 0.5, weeds 1): mass ×2.9, clean speed ×0.58. - Mass is per cm of grass removed, so the cell's height already counts. Load and fuel follow the mass; dust wears the air filter (mowing.md MO5); grip feeds the slip on turns; scalp harm scales how long a scalp stays brown.
- Cut sound and off the deck feed
CutAudio(its brightness) andMowerVfx(the spray colour), layered by the conditions under the deck, weighted by strength, plus a dust haze over dusty grass. Thatch, moss, frost, leaves and new turf are neutral rows until their ideas come (GA2, seasons, GA5).
The conditions
| Condition | Comes from | Under the mower | Afterwards | Comes with |
|---|---|---|---|---|
| Wet (dew, rain) | wetness, longer in shade | heavier, slower clean speed, bag fills ×1.5, clumps above ~50%, deck caking, slips on turns, ruts on soft ground | clumps smother (MO6) | GR2 |
| Dry, drought | dry patches (seeded), low soil moisture, heat | light (×0.6), cuts easily, dust: the air filter clogs sooner | cut low, it browns twice as long | GR2 |
| Scorched, dormant (lasting) | sun × heat × site × height, day by day | very light (×0.5), very dusty, fragile; footprints stay | straw to brown; above 0.6 a scalp leaves bare soil | GR2 (mild); full with heat waves |
| Dead (lasting) | 12 days fully brown in dry heat | nothing to cut, dust | bare until reseeded | GR2 |
| Lush (fed, a wet week) | feed (GA2), soaked soil | heavier, bag fills sooner, slower clean speed | - | GR2 |
| Shady | low sun exposure (house, trees dappled, neighbours) | lighter, sparser; grows ~30% slower | paler, thinner; moss later (GA2) | GR2 |
| Lodged | heavy rain on grass over ~12 cm, laid with the wind | a weak cut, tufts pop up; a pass against the lay | stands back up in two days | GR2 |
| Clover | seeded patch | a little lighter; slippery when wet | - | before GR1 |
| Coarse weeds (plantain, dandelion stalks) | seeded, GA2 weeds | heavy (×1.8), stalks stand back up behind a dull blade | - | GR2 |
| Seed heads | late spring | wiry stalks a dull blade only bends: they spring back like laid grass | - | GR2, sharpness from MO5 |
| Thatch | GA2 | the deck bobs, an uneven cut, scalps on the soft spots, more load; roots dry fast | - | GA2 |
| Moss | GA2 (damp shade) | wheels slip, little to cut, the bag fills with moss bits | - | GA2 |
| Frost | frost mornings (WE10: no mowing until it thaws) | mowing or walking on it anyway turns the crushed blades black (the trample field shows the prints) | black for ~3 days (lawn memory); clients notice | seasons |
| Leaves | leaf fall (autumn); LeafField | mulched or bagged: the bag fills fast, wet leaves clog | - | seasons |
| New turf, new seed | GA5 new lawns, sown cells | first cut high only; soft ground ruts | a scalp kills it | GA5 |
Feedback, all in the world: dew shows in the dark trails and the squelch; dust as a haze behind the deck; frost as a white sheen; scorch as the straw-brown colour of the cells; drought as footprints that stay.
Needs: GR1, MO2 (paving edges) · Done when: the same lawn mows heavy and clumpy at 8:00 and light at noon by changing only the sandbox's weather, and a sandbox heat wave browns the drive edge and the mound first, leaves the strip in the house's shadow green, thins the grass under the tree, and keeps your footprints; a week after mowing, the sunny side is visibly taller than the shady corner; after a storm, the long grass lies flat with the wind; and a dewy patch squelches while a scorched one crackles.
GR3: the grass look
The chunky tuft matches the art direction (ArtSource/STYLE_GUIDE.md) and the budget, so the style stays. These are
targeted additions, each picked by the cell's hash, its state and the chunk's distance (LawnRenderer; YardGrass
picks variants by hash and the far tuft by distance, so town front lawns get them too).
| Model | What it is | Why |
|---|---|---|
| A mid joint on every blade (GR1) | 9 wedge blades, 81 triangles | trample, laid grass and the blow bend each blade as a curve instead of tilting stiff sticks |
| Tuft variants | GrassTuft_B (81 triangles), GrassTuft_C (ten blades, 90) | a cut lawn seen from above showed the repeat of one mesh; random yaw and scale hid it only partly |
| A seed-head tuft | GrassTuft_Seed: six blades + three wiry stalks with heads, for uncut grass from 18 cm in spring and summer | the seed heads condition and the seed puff need something to see |
| A blunt cut tuft | GrassTuft_Cut: squared-off tips, on every cut cell | cut and uncut read by shape, not only by colour |
| A far tuft | GrassTuft_Far: three blades, 27 triangles, for distant chunks | a 4,000 m² estate is 44,000 tufts: ~3.6 M triangles at 81 each if all were near |
- Shader touchesDormant droop, scorch tint and shade paling (GR2), flutter undersides (GR1). Colour and a little droop from per-cell data, no mesh.
- Dew glintTiny sparkles on wet tips (not where the dew was knocked off), stronger with a low sun, none under cloud, twinkling as you move. A morning lawn says "still wet" without a HUD.
- Clippings dry outLeft on the lawn they go from green to straw over ~20 real minutes (about half a game day); on paving they stay until swept.
- Mulched grass: no modelA mulching mower's clippings are meant to vanish. A faint fine speckle (the clippings mesh, tiny, darkened) shrinks and sinks over 60 s.
- Clumps live elsewhereWet clumps and an overloaded mulcher's ragged trail use the clump model from mowing.md MO6.
Performance target and the grass detail setting
- Target: 60 fps at 1080p with a 4,000 m² lawn in view on the minimum-spec machine (not defined yet; until it is, the dev Mac with the setting on Low).
- Grass detail in Settings (Low / Medium / High; per machine, display only, nothing synced in co-op; older settings read Medium):
| Low | Medium | High | |
|---|---|---|---|
| Full tufts up to (then the far tuft) | 15 m | 30 m | 60 m |
| Trample field texel | 20 cm | 10 cm | 7.5 cm |
| Tuft variants | 1 | 3 + seed heads | 3 + seed heads |
| Dew glint, flutter undersides, bent shadows | off | on | on |
- The stripe map stays 7.5 cm on every setting: it's the lawn's look of the sim, and small. The sim never changes with the setting: the same cells, conditions and responses on every machine.
Needs: GR1 · Done when: a freshly cut lawn seen from above shows no repeating pattern, tall grass in spring carries seed heads, a sunny dewy morning glitters, yesterday's clippings look straw-coloured, and a 4,000 m² estate holds 60 fps at 1080p on the min-spec machine (or the dev Mac on Low until min spec is defined).
GR4: soil, species and other scorch factors
More of the turf lore, when seasons or GA2 need it: soil type per garden (sandy dries fast, clay holds water but compacts), grass species per garden (some brown fast, some tolerate drought), a drying wind, and dog spots (a brown spot with a dark green ring: not heat, a lawn mark of its own).
GR5: the rest of grass condition
Three pieces of GR2 left for later: the one-third rule (cutting more than a third of the height on a heat day: scorch ×1.5 that day), scalp-harm durations from the matrix (a scalp stays brown 7 days × its scalp harm), and the grass-condition and site maps as overlays in the sandbox (today it shows the cell under the deck).
GR6: grandma's lawn from the hub remembers
The hub's view of grandma's lawn reads that lawn's memory file, so scorch, uneven growth and stripes there match what you left (it shows the height and the look today). Town front lawns stay decorative (nobody mows them), maybe with a seasonal tint from the same lawn weather.
- What it shows, per cell: the height (a missed strip stands tall, a fence fringe grows shaggier), the stripes (direction and strength, fading with the regrowth as in MO1), scalps, scuffs and scorch in brown, and bare cells. Torn tips, ruts and laid grass are too fine to read from the house and stay out.
- How: grandma's lawn is rebuilt the way a visit builds it (the seed, the remembered lawn,
AgeDaysup to today) without the renderer, then eachYardGrasstuft takes its nearest cell's height and look. The coarse looks of step 4 (HomeLawnLook: striped, neat, shaggy, jungle) keep driving the story (the doormat notes, the street's standing); the drawing comes from the cells. - Before the first mow there's no memory: the lawn is drawn from the seed at its start height, the jungle you wake up to.
- When it refreshes: when the hub loads, each morning (a day's growth), and when the mow ends (the memory was just written). Never per frame: the rebuild is under a few milliseconds, and the hub's frame budget (GR3) doesn't move.
- With J37: the resting view of every mowed lawn works this way; grandma's is the first.
- Co-op: guests don't have the memory files. The host sends grandma's lawn blob (1-15 KB, chunked to 900 bytes
like
NetCompany) when a sibling joins and after each write, so both draw the same cells.
Needs: MO1, GR3 · Done when: mow grandma's lawn leaving a missed strip and a scalp, go back to the hub: the strip stands taller and the scalp shows brown where you left them, and the stripes show; the next morning it has grown and the stripes have faded; a guest sees the same cells; the hub's frame rate is unchanged.
GR7: mushrooms and fairy rings
After wet spells, mushrooms pop up, sometimes in a ring. The mower shreds them with a puff of spores.
GR8: worm casts
After rain, little mud piles on the lawn that smear under the deck.
GR9: worn paths
Where people walk every day, the grass goes thin, then bare, and the soil compacts. Aeration fixes it (GA2).
GR10: the lawn creeps
Over weeks, the lawn creeps into beds and over path edges, which gives the trimmer and edging page its work.
Tuning
Starting numbers, tuned in the sandbox. The response matrix (GR2) is its own table.
| Name | Value | Idea | What it does / why |
|---|---|---|---|
| Recovery by height | 3 s ≤ 5 cm, 10 s at 8 cm, 40 s from 15 cm | GR1 | how long trampled grass takes to stand back up |
| Wet recovery | ×1.5 | GR1 | wet grass stays down longer |
| Drought recovery | ×(1 + 3 × scorch) | GR2 | the footprint test |
| Push ease-in / foot hold | 0.15 s / 0.2 s | GR1 | pushes don't snap |
| Cut shrink | 0.45 s | GR1 | a cut tuft shrinks instead of popping |
| Footprint size | 22 cm | GR1 | tufts stand 30 cm apart, so a smaller print misses most of them |
| Bend at full push | 72° | GR1 | how far trampled grass lies |
| Blow lean | up to ~30° | GR1 | grass leaning out of the deck at full blow |
| Blow: high-lift blade / slow throttle | ×1.4 / ×0.6 | GR1 | more air, less air |
| Walk in tall grass | ×0.9 at 20 cm to ×0.75 from 35 cm | GR1 | gentle, on foot only |
| Seed puff | from 20 cm | GR1 | brushing past tall grass |
| Stripe contrast k | 0.30 rotary, 0.60 roller | GR1 | band strength |
| Stripe view floor / exponent | 0.15 / 0.8 | GR1 | faint from above, strong looking low |
| Sky sheen / shaded undersides | 0.12 / −10% | GR1 | light and dark bands |
| Stripe light | ×1.2 low sun, ×1 midday, ×0.6 overcast | GR1 | bands read best with a low sun |
| Band life | ~1.5 cm regrowth rotary, ~4 cm roller | GR1 | when stripes are gone |
| Scorch rate | 0.12 a day | GR2 | how fast heat browns |
| Heat | 1 heat wave, 0.35 dry streak (4+ days) | GR2 | |
| Site moisture | mound ×0.6, dip +0.3, tree ×0.5, paving ×0.7, watered ≥ 0.7 | GR2 | |
| Paving heat | ×1.4 within 0.6 m | GR2 | brown strips along the drive first |
| Shade by height now | 3 cm ×1.5, 5 cm ×1, 8 cm ×0.7, 10 cm+ ×0.6 | GR2 | |
| Roots by usual height | 3 cm ×1.3, 5 cm ×1, 8 cm ×0.8, 10 cm+ ×0.75 | GR2 | over the last 4 cuts |
| Torn tips | ×(1 + 0.15 per level) | GR2 | |
| Healing | −0.08 × clamp((moisture − 0.3) / 0.4) a day; −0.2 watered | GR2 | |
| Caps | 0.3 dry streak, 1 heat wave | GR2 | |
| Dormant / dead | from 0.6 / after 12 days fully brown | GR2 | |
| Canopy light | ~25% | GR2 | dappled tree shade |
| Neighbours' shade | within ~15 m | GR2 | |
| Growth by sun | ×(0.6 + 0.4 × exposure) | GR2 | ~10 cm a week in sun, ~7 cm in deep shade |
| Density by sun | ×(0.7 + 0.3 × exposure) | GR2 | |
| Lodging | grass over ~12 cm; 40-60% left per pass along the lay; up after 2 days | GR2 | |
| Seed heads | uncut grass from 18 cm, spring and summer | GR3 | |
| Clippings dry | ~20 real minutes | GR3 | |
| Mulch speckle | sinks over 60 s | GR3 |
Co-op
- State: the trample field is local and cosmetic (each machine stamps from the positions it already has); grass conditions are derived from the seed, the hour and the host's weather; lasting conditions live in the lawn memory blob (the host's save).
- Ownership: the host runs the daily
LawnDaytick. - Host-only: the memory write when the lawn is left.
- Randomness: none new (conditions are derived; tuft variants come from the cell hash).
- Channel: nothing new; lasting conditions travel in the memory blob sent when the lawn loads. The host's lawn
snapshot carries the band-edge offsets (
CellSync.bandEdge). - Late joiners: the blob, as for MO1.
- Checks: the guest's footsteps bend the grass on the host's screen; a heat wave browns the same cells on both.
Tech
Who owns what
garden.md GA2 owns the lawn's health and its treatments (weeds, thatch, bare, colour, feed, seed); weather.md owns the weather (wetness, soil moisture, temperature, seasons, events); day.md owns the sun and dew (DY3); mowing.md owns the mower. This page owns what grass does with all of that.
Where it applies
| Lawn | Drawn by | Gets |
|---|---|---|
| Client gardens, grandma's lawn when you mow it (the family mission), the sandbox | LawnGrid + LawnRenderer | everything on this page |
Grandma's lawn seen from the hub (Garage/HomeLawn.cs) | YardGrass at the mission's height (decorative) | the look only: trample, stripes look, variants, far tuft, dew glint (GR6: what you left there) |
| Front lawns in town | YardGrass | the look only |
The per-cell parts (condition, scorch, growth by sun, lodging, the response matrix, memory) need a LawnGrid, which
every lawn you mow has.
The code
- The grid (
Sim/Mowing/LawnGrid*.cs): 0.3 m cells, grass height per cell, thickness (noise), patches (clover, dry, weeds) whose mass factors live inGrassResponse, wet dips, bumps. - Lawn weather and condition:
Sim/Mowing/LawnWeather.cs,GrassCondition,GrassResponse.cs(the matrix as data), the daily tickLawnDay; the site map inGarden/GardenSite.cs. - Drawing (
Lawn/LawnRenderer.cs,Shaders/Grass.shader): one instanced tuft per cell (ArtSource/scripts/models.pybuild_grass_tuftand its variants, rebuilt through the Blender MCP), a flat stripe mat under cut cells, a clippings mesh, weed, daisy and clover meshes. Wind sway in the vertex shader (GM_WindOffset). Per-cell shader data: cut time, lean, scalped, clover, dry, wet, feed, care (_GrassCare), patch (_GrassPatch). Front lawns in town:Town/YardGrass.cs. - Grass that moves: the trample field (
Lawn/GrassField.cs+Shaders/GrassField.shader, numbers inSim/Mowing/GrassMotion.cs), footprints (GrassStamper), the bow wave, wheel lines and blow (MowerGrass), dew trails, tall grass (TallGrass); the stripe map (StripeMap). - Mulching mowers draw only the speckle.
Lawn memory: what grass adds
MO1's memory (LawnGridMemory.cs) stores one byte plane per field over the lawn cells, each deflated on its own,
with small values bit-packed.
| Plane | Format 1 (MO1) | Format 2 (GR1) | Format 3 (GR2) |
|---|---|---|---|
| height | 1 byte | = | = |
| stripe | direction 4 bits · amount 3 bits · roller 1 bit | = | = |
| marks | scar 3 bits · scuff 3 bits · torn 2 bits | = | = |
| misc | streak 2 bits · bare 1 bit · 5 spare | + band-edge offset 3 bits (bits 3-5), 2 spare | = |
| feed, sown | 1 byte each | = | = |
| scorch | - | - | scorch 4 bits (16 levels) · days fully brown 4 bits (0-12) |
| usual height | - | - | 1 byte (whole cm) |
| edge cells | fringe height, coverage | = | = |
Format 4 (mowing.md MO6c) appends a habits plane: rut 2 bits · stress days 3 bits · 3 spare. Spilt fuel writes the misc plane's bare bit.
- Older formats load: missing bits read as 0 (no band edge, no scorch, usual height = the current height). The
reader accepts formats 1 up to the current one and refuses newer ones. The layout fingerprint uses a fixed
LayoutVersion, not the format number: with the format in it, any bump would make every saved lawn look like another garden and drop it. - Room for more: the misc plane keeps 2 spare bits, and header byte 3 is written as 0 and unused (a free per-lawn flags byte). Beyond that, new data = a new length-prefixed plane appended after the others + a format bump. An all-zero plane deflates to a few bytes.
- Size: the 4,000 m² lawn stays under the 16 KB budget (15.4 KB with format 3; MO1's size test).
AgeDaysruns the daily tick for the days between visits.
Sandbox pieces (docs/dev/sandbox.md)
- GR1: a "Grass that moves" section: Long-grass strip (cm) (part of the spec, so a co-op guest builds the same:
a 1.5 m band of uncut grass across the middle of the lawn), Wet lawn (this machine only: forces the morning dew, to
see the trails), and a live view of the trample field round you (red / green the push, blue how fresh, alpha the dew
knocked off) with the stamps a frame and the dew. From
eval:GrassField.Push(position, radius, direction, strength)stamps by hand; Next visit in 0 days checks that the stripe bands come back crisp. - GR2: a "Grass condition" section: the
LawnWeatheroverride (heat wave, heavy rain, soil moisture), skip days (the daily tick), a scorch brush at the crosshair, a readout of the cell under the deck (weather, sun, tree, paving edge, mound, scorch, brown days, lodging, the response); the condition and site overlays are that readout for now (GR5). - GR3: "Grass gallery": every tuft, uncut and cut, in a row in front of you (plain renderers: no stripes or bend).
Tests and checks
- GR1 (
GrassTests): recovery per height and wetness; the field's wrap-round; the walk factor; stripe shading by view height and roller; a diagonal pass leaves a straight band edge; a stamp outside the lawn is ignored; a merged cell comes back in line; a memory round trip keeps the band edges, the host's snapshot carries them, format 1 blobs load, a newer format is refused. Checked in the sandbox and in co-op (editor host + dev build guest): diagonal bands clean; tall grass laid flat by a push; a dewy lawn with a green trail; the bands back after a same-day return; the guest's mower stamping the host's field and laying sub-cell stripe edges in the host's map; guest mismatches 0. - GR2 (
ConditionTests, 8): wet at 7:00, dry at 13:00; stacking (mass ×2.9); a heat wave browns the sun, not the shade, the paving edge first, short grass worst; a mound dries before a dip; twelve brown days kill, rain revives dormant; dappled shade grows ~30% less; lodging along vs against the lay; memory keeps scorch, older formats read none. Checked: grandma's lawn has exposure 0..1 (the house's shadow) and 36 paving-edge cells; ten sandbox heat-wave days scorch it to a mean of 0.38 (25 cm grass shades its roots); the guest's snapshot matches the host's exactly (825 scorched cells). - GR3: the detail setting (distances, texels, Low's cuts) and old settings reading Medium. Measured on the dev Mac (editor, Medium, a 66 × 60 m sandbox estate, 43,679 cells): 50 fps with ~37,000 instances drawn. The editor's unfocused play runs at 3-10 fps, so that is the only clean reading so far.
Open questions
- The minimum-spec machine isn't defined anywhere yet; the grass performance target (and every other one) needs it.
- Numbers to tune in play: footprint size, bend angle, flutter strength, dew sheen strength, recovery times, matrix factors, scorch rates, site factors, dormancy length.
- Hear the other sibling's steps in tall grass? The rustle plays for your own steps only.
- When is "late spring"? Seed heads show from 18 cm in spring and summer until the calendar has a real late-spring window (seasons).
Rejected
- One trample texture per lawn: replaced by one window round the local player, which covers every lawn near you (town and the hub included) with one texture.
- The blade's blow as a channel of the trample field: it left a 0.5 s shimmer trail behind the deck. Worked out in the shader as a ring round the deck instead.
- A hard shake for the blow: the first version shook the grass hard; too much in the user's playtest. It leans out and flutters a little now.
- Reworking the grass style: the chunky tuft fits the art direction and the budget.
- A mulch model: mulch is meant to vanish into the turf.
- Crisp footprint shapes: they'd need denser grass than one tuft per cell, a much bigger call than the look is worth.