garden
The growth grammar of Verdigris Cloister. Five plants, three stages each, and a Blight that grows by the same rules the garden does.
game.garden — the floor takes its own turn
The growth grammar of Verdigris Cloister. Five plants, three stages each, and a Blight that grows by the same rules the garden does.
This module is pure state: it reads and writes the tile half of Core.state()
and hands back a list of what changed. It draws nothing and it ticks nothing —
growth happens because a round closed, never because time passed.
local Garden = require("game.garden")
The five plants, and what each round does to them
| id | On its turn | What the tile becomes |
|---|---|---|
creepvine | grows a stage; from stage 2 throws a runner into an empty neighbour | half cover from stage 2 |
rootknot | grows a stage; at stage 3 heaves the tile up one elevation step, once | high ground |
thornbed | grows a stage; from stage 2 spreads into an empty neighbour | 1/2/3 damage to whoever ends a turn on it; half cover at stage 3 |
bloomstalk | opens over two rounds | from stage 3, mends adjacent units 2 a round |
lightwood | grows a stage | half cover at 2; full cover AND blocks line of sight at 3 |
The Blight uses the same table shape (Garden.BLIGHT_RULE): it hardens a stage,
creeps into what it touches — taking the plant there with it — and at stage 3
throws a spire that is full cover, blocks sight, and heaves its own tile.
Garden.blightBudget() is how many tiles it may take this round: 1 + (round-1)//2.
Garden.BLIGHT_THRESHOLD (0.45) is the share of the greenhouse that loses the
mission.
The turn
local changes = Garden.growPlants() -- the garden's turn
local effects = Garden.gardenEffects()-- what it did to whoever is standing in it
local changes = Garden.growBlight() -- then the Blight's
Both resolve against a snapshot taken when the round closed, so growth put down this round does not grow again in the same round. Both return an array of records the presenter animates:
{ kind = "grew" | "spread" | "heaved" | "blightGrew" | "blightSpread"
| "fireOut" | "waterGone",
x, y, plant, from, to, source?, killed? }
gardenEffects() returns { unitId, amount, source, heal, x, y } and applies
nothing. Units belong to the referee; the garden reports and lets it decide.
Growth draws from state.gardenSeed, not from math.random, so the same board
resolves the same way twice.
The gardening actions
Each returns ok, reason. The reason is written to be shown to the player as
it is, so a refusal says why in plain words.
Garden.sow(x, y, kind) -- "Something is already growing there."
Garden.burn(x, y) -- clears growth, knocks the Blight back a stage, lights the tile
Garden.flood(x, y, rounds) -- holds growth off, puts a fire out
Garden.cut(x, y) -- Anselm's billhook: strips it back, no fire
Garden.floodRow(y) -- Cassian's Open the Line
Garden.sowMany(cells, kind)-- Meridia's Broadcast
Garden.seedBlight(x, y, stage)
Reading the floor
Garden.coverAt(x, y) -- 0 none / 1 half / 2 full
Garden.blocksSight(x, y) -- true when a sightline dies here
Garden.hazardAt(x, y) -- damage for ending a turn here, and its source
Garden.heightOfTile(tile) -- world height of whatever is standing there
Garden.describe(x, y) -- the tile inspector's sentence (below)
Garden.snapshot() -- every occupied tile, plus blightShare and threshold
describe is what the HUD reads out. It names the terrain AND what it becomes
next round:
{ terrain = "Lightwood",
detail = "a young trunk with the first boughs",
nextStage = "grows TALL: full cover, and the sightline goes with it",
cover = 1, coverName = "half cover", blocksSight = false,
elev = 0, stage = 2, plantKind = "lightwood",
marks = { "flooded — growth is held off here" } }
Garden.refresh(tile) / Garden.refreshAll() recompute the derived fields
(cover, blocksSight, growHeight) and Garden.recount() republishes
state.blightTiles and state.blightShare. Every mutating call above does
this for you.
The sounds it asks for
The garden makes no sound itself; it emits playSfx through Core.sfx(name, at)
and lets the audio slice own it. Five names, all fire-and-forget:
| name | when | position |
|---|---|---|
garden_sow | a seed goes into a tile | the tile |
garden_burn | a tile is burned back | the tile |
garden_flood | the brass line opens onto a tile | the tile |
garden_grow | the garden's turn resolved with at least one change | — |
blight_grow | the Blight's turn resolved with at least one change | — |
Laying a room out
Garden.LAYOUT carries an opening garden for each of the four rooms —
propagation_hall, brass_nave, fern_vault, heart_chamber.
Garden.layout(mission) clears the floor and plants it.
Garden.clearGrowth() strips the board without touching units.
Scoped to this part · feeds back into the world's score.