Skip to content

jengine-homestead

A homestead’s rules as plain data: a plot to till, plant and build on, crops grown from timestamps (offline included), inventory and crafting, resource nodes that respawn on a clock, and a community project everyone builds.

KindProject package: linked into the builds of the projects that list it
Sourcepackages/jengine-homestead
Version0.1.0
Entry pointjengine_homestead::HomesteadPackage

List it in the top-level "packages" array of project.jengine:

{
"packages": [
{ "name": "jengine-homestead", "source": { "type": "path", "path": "../../packages/jengine-homestead" } }
]
}

Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the editor’s Rebuild viewport banner). Native and web builds link it on their own.

From packages/jengine-homestead/README.md.

A homestead’s rules as plain data: a plot of ground to till, plant and build on, crops that grow from timestamps (so a field grows while its farmer is away), bags of stacked items, crafting from recipes, resource nodes everyone shares that respawn on a clock, and a community project a whole server builds together.

It was written for Hearthwood (projects/hearthwood), a cozy village MMO whose server rooms run these rules for every player’s homestead, the shared woods and the village bridge. Nothing in it knows a server, a scene or a particular game, so another farming game can use it as it is - Thornwick’s fields could be Plots and its barley a CropDef; a single-player farm could keep one Plot in a save.

ModuleHolds
catalogthe data: ItemDef, CropDef, ObjectDef, RecipeDef, NodeDef in one Catalog, read from JSON (Catalog::from_json) and checked whole - a recipe naming an item that does not exist is refused at load, with what is wrong
inventoryInventory: slots of Stacks. Every change is all or nothing - add puts in everything or nothing, take takes everything asked or nothing - so a craft or trade that fails half way cannot eat half its inputs
growthGrowthRules, Wet, Planting: how far a crop has grown at any time t, worked out from when it was planted and when its soil was wet. Full speed while wet, dry_rate while dry; wet for water_secs after a watering, for as long as a sprinkler stands next to it, and all of any day it rains (rains_on(day), a hash of the day and a seed - the same weather everywhere)
plotPlot: a grid of grass / tilled / path, fixed cells for the game’s own layout, crops by cell, Placed objects with footprints. till, plant, water, harvest, can_place, place, pick_up, state (grass / tilled / watered / path at a moment), solid_cells. Every refusal is a PlotError that says why
craftingcraft (exactly the inputs, or nothing), start + Job + collect for recipes that take time - the output is due at a timestamp, so it is ready when its maker comes back
nodesNode: a tree, rock or bush. hit gives what it gives after its hits and is then gone for everyone until respawn_secs later; refresh says the moment it is back, once
projectProject: a shared build. contribute takes no more than is wanted and finishes the project exactly once - the gift that fills the last need is told so, every later one is refused - so the moment of completion can be an event without two players both setting it off

Time is a number the caller passes in. Every rule takes at in seconds and nothing reads a clock: ask about a crop a week later and the answer is the same as watching it all week (a test sums a crop’s growth second by second against one question an hour later). That is what lets a server keep one clock for its world - wall time in production, a clock a test moves by hand.

Everything serializes. A whole Plot is one JSON document (Hearthwood stores one per player), Nodes and Project likewise, and Inventory and Job live in a player’s saved state.

As a library - a server room or a game’s own package calls it:

use jengine_homestead::{Catalog, GrowthRules, Plot};
let catalog = Catalog::from_json(include_str!("content.json"))?;
let rules = GrowthRules::default();
let mut plot = Plot::new(26, 18, fixed_cells);
plot.till(8, 12)?;
plot.plant(&catalog, 8, 12, "turnip_seeds", now)?;
plot.water(8, 12, &rules, now)?;
// ... an hour later, after a restart, nobody having ticked anything:
let (crop, stage, ripe) = plot.crop_state(&catalog, 8, 12, &rules, now + 3600.0).unwrap();

To list it in a project (it changes nothing in a scene - see below):

{ "packages": [
{ "name": "jengine-homestead", "source": { "type": "path", "path": "../../packages/jengine-homestead" } }
] }

None. The package’s JEnginePackage registers no components, systems or script commands: Hearthwood’s rules run on its server, and its client draws what the server sends. It is a package so a project can list it and a client-side system (a single-player farm, a preview of growth) can be added later without moving the rules.

cargo test -p jengine-homestead: growth at full and dry speed, growth across an “offline” gap equal to watching it, rain by the day and the same everywhere, overlapping waterings counted once, sprinklers watering from when they were placed, a regrowing crop; tilling, planting, watering and harvesting with every refusal; placement overlap and footprints off the edge refused; crafting that takes exactly its inputs and keeps them when the bag is full; timed jobs finishing offline; nodes gone for everyone and back on the clock; a project that takes only what it wants and completes once, and survives being stored. Hearthwood’s own tests (cargo test -p hearthwood-world) play these rules through real rooms and the real scene.