doc 9 of 10

Stage 07 — What's missing: requirements, no code

You will build: nothing, on purpose. This stage is the fade the almanac does at the end of every curriculum — a skeleton with the bodies missing, then requirements with no code at all. Each item below is a system the architecture doc already designed (saving "built later", the reserved states, the day_advanced wire that doesn't exist yet) and that the next version of the game needs. Write them yourself, against these requirements, and use the shelves they point at as the general treatment.

This is also the stage where you stop being a tutorial's reader. Everything after this line is a project.

The requirements#

Harvest#

The UseItemState currently plants on an empty cell and does nothing on an occupied one (stage 04's guard, _plots.has(cell)return false). It needs to distinguish the two occupied cases:

  • Acting on a cell with a mature crop removes the plot and its view, and the crop goes somewhere (the inventory, below). The removal is Farm.harvest(cell) -> CropData — the data comes back out of the field, the view is freed, and _plots loses the key.
  • Acting on a cell with an immature crop does nothing observable — but it should do nothing legibly: the 2D game's answer was a distinct state, and the state machine's diagram (stage 02) has room for it. Whether "too early" is a state, a warning, or a sound is a design call — make it and record it.
  • The maturity test is plot.stage == plot.crop.stage_meshes.size() - 1 — the same "mature is a state, not a speed" fact from stage 06, now load-bearing.

The honesty test applies: after harvesting every crop on the field, _views is empty and _plots is empty and replanting works — the farm is a machine that returns to zero.

Inventory#

The architecture doc's reserved node (Interfaces → Inventory, an empty Control) is where this lands. Requirements, in the inventory shelf's order — data first, grid last:

  • Inventory extends Resource holding {CropData: int} — the 2D game's stage 06 shape, verbatim. It emits changed on every mutation; it knows nothing about UI.
  • Harvest (above) is the only writer in this version: Farm.harvest returns the data, the player (or the farm, your call — the 2D game's call) adds it to the inventory.
  • The UI is an observer of changed: a list of "Turnip ×3" lines, rebuilt on the signal. The UI never mutates the inventory; it renders it. When a second observer appears (a crafting panel, a sell price), the signal's shape is already right.
  • The never-mutate rule governs this directly: the inventory's keys are the shared CropData definitions. Ten harvested turnips are one key with a count of ten — never ten keys, and never a mutated copy of the definition carrying the count.

A real day cycle#

Stage 06's day is a key press. The game's day is a clock:

  • TimeSystem — the project's first non-debug autoload (the architecture doc's deferral, cashed in): a day length in seconds (a design number, say 120), a running day counter, and the signal the architecture doc reserved — day_advanced — emitted once per day.
  • Farm subscribes: day_advancedadvance_day(). The central tick (stage 06) is now driven by a system instead of a key, and the key staysDebug.register(KEY_F1, ...) now calls the same advance_day as a skip action. Debug and the real clock coexist because they call the same door; that's the payoff of the central tick over the per-crop-listener alternative (the 2D game's decision 3, again).
  • The day counter is savable state (below) — a TimeSystem that rebuilds itself from nothing on load is the save & load shelf's opening audit failing in public.

Saving#

The architecture doc designed this in one paragraph; the requirements are that paragraph, enforced:

  • What's saved: _plots as {cell: {crop id, stage, days_grown}}, the player's position and facing, the day counter, the inventory's contents. A version field from day one — the 2D game's rule, because a save without a version is a migration you'll do at 2 a.m. instead of now.
  • What's not saved: _views, the camera, any tween, the debug registry. Views are rebuilt by walking _plots — the stage 04 proof (delete every view, lose nothing) is the load path.
  • Where: user://save.json (or .tres — the save & load shelf's second part argues the case; JSON keeps the save human-readable, which is worth a lot while the format is still changing).
  • The test: save at day 3 with a mixed field, quit, reload, and the field is identical — same crops, same stages, same days_grown (check the number, not just the mesh; two carrots at stage 1 can have different days_grown and the mesh won't show it). Then the third part's question: what breaks when you add a crop to the catalogue after a save exists? The version field is the answer; make the break first.

Shapeshift — the nested machine#

Stage 02's reserved ShapeshiftState is where the state machine shelf's fourth part earns its 3D form:

  • ShapeshiftState contains a StateMachine child (possible because StateMachine extends State — stage 02's whole point). The inner machine runs the shapeshift's own states (which form? the design is yours — the requirement is only that the outer machine sees one state and the inner machine runs many).
  • Entering shapeshift must suspend the walk/run states' claims on the player's movement — the inner machine's states call player.move with the shapeshift's parameters, and the outer machine's current is the shapeshift, so nothing else ticks. The finished signal's shape (stage 02) is how the inner machine reports "done" back to the outer travel(&"IdleState").
  • SynthesizeState — the stage 02 trap, still script-less in the reference scene — gets a script or gets deleted. The machine's assertion (stage 02) only fires when no states register; a half-built state is the quiet kind. Choose loudly.

The closing self-review#

The survivor curriculum's stage 09 has the four questions; they transfer unchanged. For every script in this project — State, StateMachine, the five player states, Player, Farm, Plot, CropData, Crop, Debug — answer:

  1. What is this file's one job?
  2. What does it know about the rest of the game, and through which doors?
  3. What would break if this file were deleted?
  4. What would break if it were duplicated?

And the project's final honesty test, the one the 2D game's save stage set and this one inherits: delete every Crop node and the player's Interfaces and the camera, and name what information was lost. The answer is "none" — and saying it, with the deletion actually run, is the difference between a farm you built and a farm you assembled.

The code reference is the map of the shipped shape — read it against what you've built, and where it names something you didn't (the Phantom camera's damped follow, the PhantomCameraHost's process priorities), that delta is your next sitting.