feat: introduce identity-backed goat

This commit is contained in:
Rijad Zuzo
2026-07-26 22:26:00 +02:00
parent 1f09ebe6b6
commit f79c2bb630
47 changed files with 1903 additions and 96 deletions
+10
View File
@@ -16,6 +16,7 @@ SimulationClock
-> ActionSelectionSystem chooses an action
-> ActionTargetResolver resolves a stable target ID
-> VillageEconomy performs inventory/storage transactions
-> AnimalCareSystem advances animal needs and composes feeding
-> SimulationEventLog records completed facts
-> EventKnowledgeSystem records, ranks, transfers, and retains bounded knowledge
-> RelationshipSystem applies evidence-gated social consequences
@@ -35,6 +36,9 @@ would otherwise obscure that lifecycle:
- `simulation/actions/` owns selection, progress, and target resolution;
- `simulation/economy/VillageEconomy.gd` owns storage/inventory transactions
and keeps village resource summaries synchronized;
- `simulation/animals/AnimalCareSystem.gd` owns animal records, hunger
advancement, loaded binding, feed reservations, and the exact conserved
pantry-to-animal operation used by NPCs and the player;
- `simulation/events/SimulationEventLog.gd` owns ordered event identity,
history queries, and rate calculations;
- `simulation/knowledge/EventKnowledgeSystem.gd` owns per-NPC references to
@@ -89,6 +93,7 @@ hard to read.
| --- | --- |
| `simulation/` | Headless-capable orchestration and core models |
| `simulation/actions/` | Action decisions, execution, and target queries |
| `simulation/animals/` | Animal lifecycle, target claims, and feeding transactions |
| `simulation/economy/` | Authoritative inventory and storage transactions |
| `simulation/events/` | Immutable event history and derived event queries |
| `simulation/knowledge/` | Per-NPC knowledge of objective event IDs |
@@ -100,6 +105,7 @@ hard to read.
| `simulation/persistence/` | Validated local save-file storage |
| `simulation/benchmark/` | Reproducible simulation and loaded-world query workloads |
| `world/` | Loaded-world interaction geometry and presentation adapters |
| `world/animals/` | Animal presentation binding and interaction geometry |
| `world/resource_nodes/` | Finite resource presentation and disposable loaded-anchor index |
| `world/storage/` | Storage interaction geometry, never stored quantities |
| `world/activity/` | Rest/study/patrol interaction sites and capacity facts |
@@ -130,6 +136,10 @@ improving ownership.
reactions are not replayed.
- Resource changes go through `ResourceStateRecord`, NPC inventory, and
`VillageEconomy`; `village.food` and `village.wood` are synchronized views.
- Animal identity, position, hunger, last feed tick, and reservation live in
`AnimalStateRecord`. `AnimalNode` binds that state and contributes only its
loaded interaction point. Animal candidates are currently scanned linearly;
they do not enter the resource grid.
- New mutable features define serialization and deterministic continuation at
the same time as their first gameplay use. Cross-record causes use stable
event IDs rather than object references or prose.
+13 -6
View File
@@ -798,15 +798,22 @@ Completed:
stone instances stay presentation-only. Both finite trees remain reachable,
deplete into shared stumps, restore from authoritative amount, and keep the
world/save total at eighteen resources.
40. `Jajce Goat 07`: Dunja is the first identity-backed animal, with a stable
goat ID, simulation-owned position and hunger, and a loaded cozy
presentation with an honest hungry cue. NPC and player feeding share one
exact transaction that withdraws pantry food, changes only Dunja's state,
and emits one economic fact. World-schema v10 save/restore preserves the
result without replaying the transient feed response.
Next:
1. Add one named goat as an identity-backed simulation vertical slice: stable
animal ID, authoritative position and hunger/feed state, loaded visual
binding, and an exact feed interaction shared by one NPC and the player.
Feeding must withdraw real pantry food and survive deterministic save/load.
2. Add a small herd, breeding, or animal products only after that single-animal
identity and conservation contract is proven.
1. Give Dunja one deterministic pasture/shelter routine using
simulation-owned destination and position, real loaded-world navigation,
and exact mid-move save/restore continuation.
2. Add a second goat only after movement proves that identity remains stable
across care, navigation, unloading, and restore.
3. Keep breeding, products, herd scheduling, animal spatial indexing, and
active/abstract simulation LOD deferred until two real animals require them.
Do not start with GIS data, a full city, a large asset pack, or more NPC
mechanics. The next proof is a beautiful stage for the systems that already
+19 -7
View File
@@ -10,6 +10,7 @@ resource_extracted
storage_deposited
storage_withdrawn
item_consumed
animal_fed
```
Each record contains a monotonically increasing event ID, event type,
@@ -17,8 +18,10 @@ simulation tick, actor ID, source ID, destination ID, item ID, and transferred
amount, plus the authoritative world position captured when the fact is
recorded. NPC events preserve their authoritative interaction target (or NPC
position when no target applies); player extraction/depletion events preserve
the ResourceNode interaction position. Actor `-1` identifies a player-triggered
extraction until persistent player identity is introduced.
the ResourceNode interaction position. `animal_fed` records one food moving
from `village_pantry` to a stable animal ID at the animal's authoritative
position. Actor `-1` identifies a player-triggered extraction or feeding until
persistent player identity is introduced.
Events are immutable facts about completed transfers. They do not perform the
transaction and are not replayed to reconstruct current state. Resource,
@@ -26,8 +29,10 @@ inventory, and storage records remain authoritative.
`SimulationEventLog` owns ordered event identity, append/restore behavior, and
history/rate queries, including exact lookup through `get_by_id()`.
`VillageEconomy` performs transactions and requests event records only after
state changes succeed; `SimulationManager` remains the public signal boundary
`VillageEconomy` performs inventory and storage transactions and requests event
records only after state changes succeed. `AnimalCareSystem` composes the
pantry withdrawal with animal hunger relief and requests `animal_fed` only
after both succeed. `SimulationManager` remains the scene-tree signal boundary
used by presentation.
An action whose definition-backed completion cost becomes unavailable records
@@ -38,17 +43,24 @@ transfer occurred.
## Persistence and determinism
`SimulationStateRecord` schema v9 stores the ordered event stream,
`SimulationStateRecord` schema v10 stores the ordered event stream,
`next_event_id`, directed relationships that may reference an exact event, and
per-NPC known-event references with first-acquisition provenance, plus
opportunity records that reference exact trigger/resolution events. Schema v1
and v2 saves migrate to an empty stream beginning at ID zero; world schemas
v1v7 migrate to an empty opportunity list, while world schema v8 preserves and
normalizes its pantry opportunity history. Parsing rejects duplicate event
v1v7 migrate to an empty opportunity list, while world schemas v8 and v9
preserve and normalize opportunity history. World v9 adds an empty animal list
during migration. Parsing rejects duplicate event
IDs, invalid or duplicate knowledge/opportunity references, impossible
communicator sources, relationship causes the observer does not know, and next
IDs that could collide with restored history.
Animal-feed facts receive an additional cross-record check: actor is either an
existing NPC or the player sentinel, source is the real pantry, destination is
an existing animal, item and definition cost are exactly one food, event tick
is not in the future, and the animal's `last_fed_tick` agrees with its latest
feed fact.
Positive food deposits, positive NPC food withdrawals from `village_pantry`
into that actor's matching inventory, and definition-backed patrol/study
`task_blocked` facts caused by missing wood at `village_woodpile` are currently
+18 -8
View File
@@ -923,17 +923,27 @@ rebuilds the sparse standing tree from amount alone. The river and forest
clusters now share only the terrain-snap, bounded-instance, and anchor-layout
code proven by those two consumers.
The immediate next slice should introduce livestock only through one
identity-backed animal vertical slice: one named goat with a stable animal ID,
simulation-owned position and hunger/feed state, a loaded presentation binding,
and one exact feed interaction available to both an NPC and the player. Feeding
must consume real pantry food and save/restore deterministically. Do not make
the animal a `ResourceNode`, add a herd, breeding, products, or a generalized
animal spatial index before that first identity and conservation contract is
honest.
The first identity-backed livestock slice is complete. Dunja has a stable
animal ID, simulation-owned position and hunger/feed state, and a loaded cozy
presentation whose persistent cue derives from that state. One exact operation
serves both NPC and player feeding, withdraws real pantry food, mutates only the
target animal, and appends one economic fact. World-schema v10 save/restore
preserves the result without replaying the transient response.
The immediate next slice should give Dunja one deterministic
pasture/shelter routine. Her destination and position must remain
simulation-owned, loaded movement must use real navigation, and a mid-move
save/restore must continue exactly without consuming new decision RNG. Keep
loaded-animal discovery linear and do not add a second goat, herd scheduling,
breeding, products, or active/abstract simulation LOD until this movement
contract is honest.
Recently completed:
- `Jajce Goat 07`: named goat Dunja proves stable animal identity, exact
pantry-conserving NPC/player feeding, deterministic hunger and save/restore,
loaded target reservation, a state-derived hungry cue, and a transient cozy
response that never enters persisted state.
- `Jajce Forest Edge 06`: two existing deep-forest tree IDs now anchor a
restrained four-tree silhouette with 24 decorative understory instances.
Both targets require expanded loaded-resource discovery, remain reachable,
+11 -8
View File
@@ -616,10 +616,10 @@ These are expected prototype constraints, not necessarily isolated bugs:
- Temporary activity markers have been removed; NPC and player food/wood
gathering use `ResourceNode` instances with no fallback, food transfer uses
the typed pantry `StorageNode`, and patrol/study/rest use `ActivitySite`.
- Current NPC, village, resource, storage, event, knowledge, relationship,
opportunity, clock, and RNG state serialize through world schema v9. F5/F9
provide one validated local quicksave; a save menu, metadata, and
player-transform persistence remain deferred.
- Current NPC, village, resource, storage, animal, event, knowledge,
relationship, opportunity, clock, and RNG state serialize through world
schema v10. F5/F9 provide one validated local quicksave; a save menu,
metadata, and player-transform persistence remain deferred.
- Simulation-owned resource records retain live amounts, reservations, and
usage definitions while ResourceNode scenes are unloaded.
- An explicit fixed-step clock converts frame delta into simulation ticks, but
@@ -987,10 +987,13 @@ visuals, and two bounded foliage/resource placement proofs are complete. The
riverbank and forest edge preserve stable finite-resource IDs, separate
decorative density from simulation authority, support discovery beyond the
first 24 m range, and reconstruct sparse/depleted visuals from authoritative
amount. The next slice should introduce one named goat only through a stable
simulation identity, authoritative position and feed state, loaded visual
binding, exact NPC/player feeding cost, and deterministic save/restore. Do not
yet generalize the resource grid or introduce active/abstract simulation LOD.
amount. Dunja now proves the first identity-backed animal contract: stable
animal identity, simulation-owned position and hunger, a loaded visual binding,
and the same exact pantry-conserving feed operation for NPC and player.
World-schema v10 restore reconstructs her persistent state without replaying
the transient response. The next bounded animal slice should give Dunja one
deterministic pasture/shelter routine before adding another goat. Do not yet
generalize animal discovery or introduce active/abstract simulation LOD.
The remaining simulation-garden target still aims for:
+9 -4
View File
@@ -19,13 +19,13 @@ Each executable action definition contains:
- display name;
- default duration;
- optional preferred profession ID;
- target type: resource, activity, or free movement;
- target type: resource, activity, animal, or free movement;
- resource action ID when the action targets a ResourceNode;
- optional completion-cost resource ID and amount.
The current actions are gather food, gather wood, deposit food, deposit wood,
withdraw food, patrol, study, eat, rest, sleep, and wander. Idle and dead are
stable state sentinels, not executable action definitions.
The current actions are gather food, gather wood, feed animal, deposit food,
deposit wood, withdraw food, patrol, study, eat, rest, sleep, and wander. Idle
and dead are stable state sentinels, not executable action definitions.
SimNPC reads default duration and preferred-profession metadata from these
definitions. WorldViewManager reads target type and resource-action metadata
@@ -33,6 +33,11 @@ instead of maintaining a separate gather-action map. ActionSelectionSystem and
SimulationManager share the same completion-cost metadata, so patrol and study
both require one stored wood without duplicating that rule.
`feed_animal` is the first animal-targeted action. Its definition prefers the
farmer profession and declares an exact one-food completion cost. Selection,
animal target reservation, and completion remain separate; NPC and player
completion both use `AnimalCareSystem`'s same pantry-to-animal transaction.
## Current profession contract
Each profession definition contains a stable `profession_id`, display name,
+38 -3
View File
@@ -3,7 +3,7 @@
## Current contract
`SimulationStateRecord` is the versioned JSON boundary for the current
simulation. The current world schema is v9 and captures:
simulation. The current world schema is v10 and captures:
- simulation seed, tick interval, tick count, clock remainder, and elapsed
clock ticks;
@@ -13,6 +13,8 @@ simulation. The current world schema is v9 and captures:
- controlled per-NPC wander RNG streams;
- scene-independent resource state plus the definition facts needed while its
ResourceNode is unloaded;
- scene-independent animal identity, position, hunger, feeding history,
reservation, and availability state;
- pantry contents, carried NPC inventory, the ordered economic event stream,
and its next stable event ID;
- directed relationship records with familiarity, trust, and the stable event
@@ -29,7 +31,7 @@ The top-level identity is:
```json
{
"schema": "the_steward.simulation",
"schema_version": 9
"schema_version": 10
}
```
@@ -170,6 +172,21 @@ nested v1/v2 events receive empty/zero defaults, while a new `task_blocked`
event requires its stable action, source storage, resource, and positive
requirement contract.
SimulationStateRecord v10 adds the top-level `animals` array and the first
`AnimalStateRecord` schema. Each animal record stores its stable animal and
species IDs, display name, authoritative world position, hunger,
`last_fed_tick`, reservation owner, and enabled/player/NPC feeding flags.
World schema v9 preserves opportunity history while migrating to an empty
animal list.
Current-schema parsing requires unique animal IDs that do not collide with
resource or storage IDs. An animal reservation must belong to an existing NPC
whose active feed task targets that exact animal. Every `animal_fed` event
must name the pantry as its source, an existing animal as its destination,
food as its item, the exact feed action cost, and a valid NPC actor or the
player sentinel. The latest matching feed event tick must agree with the
animal's `last_fed_tick`.
The bounded opportunity family accepts at most one open record globally.
Opportunity, trigger-event, and resolution-event IDs are unique and the next
ID must remain above restored history. Referenced NPCs, storage, resources, and
@@ -235,6 +252,24 @@ explicitly. Current-schema parsing also canonicalizes numeric, boolean, and ID
variants so a freshly registered finite source retains a byte-stable checksum
after a JSON round trip.
## Animal authority
`AnimalCareSystem` owns `AnimalStateRecord` instances independently of the
scene tree and advances their hunger in stable-ID order. `AnimalNode` binds a
loaded presentation and interaction point to that authority; the simulation
position remains canonical while the node is loaded or absent.
Animals are not `ResourceNode` instances and are not inserted into the
resource discovery grid. The first bounded slice uses a deterministic linear
loaded-animal query. NPC and player feeding resolve through the same atomic
care operation: reserve or select the exact animal, withdraw one real food
from `village_pantry`, reduce that animal's hunger, and append one exact
`animal_fed` fact. Failed withdrawal changes neither animal nor history.
The persistent hungry/content presentation derives from authoritative hunger.
The short feed response is transient and is reset rather than serialized or
replayed after restore.
## Local quicksave boundary
`SaveSlotStore` writes the existing versioned JSON record to
@@ -253,7 +288,7 @@ This phase does not yet provide:
- a save-slot menu, metadata, thumbnails, autosaves, or multiple profiles;
- migrations from any historical world schema other than the explicitly
supported v1v8 layouts;
supported v1v9 layouts;
- player inventory or player relationship records;
- broader relationship dimensions, line-of-sight/hearing evidence,
continuous or personalized memory decay, reinforcement, false beliefs, or
+38
View File
@@ -0,0 +1,38 @@
# Jajce Goat 07
Captured 2026-07-26 with the Metal renderer from the same close-up camera:
- [hungry frame](jajce_goat_07_hungry.png)
- [content frame](jajce_goat_07_content.png)
Only authoritative hunger changes between the paired frames. Dunja lowers her
head toward the bowl and shows a small amber empty-bowl cue while hungry; after
feeding she raises her head and the persistent cue clears. The brief green
response is deliberately transient and is not part of the restored frame.
This visual comparison sits on the gameplay proof:
- Dunja has one stable animal ID and is not a resource node;
- NPC and player use the same exact feed operation;
- one feed withdraws one real pantry food and relieves only Dunja;
- world-schema v10 restores position, hunger, and last-fed tick without
replaying presentation effects.
The next animal frame should prove a real pasture/shelter movement routine,
not multiply static livestock.
Capture command:
```bash
HOME="$PWD/logs/quality/godot_profile" \
APPDATA="$PWD/logs/quality/godot_profile" \
LOCALAPPDATA="$PWD/logs/quality/godot_profile" \
/Applications/Godot.app/Contents/MacOS/Godot \
--path "$PWD" --script res://tools/capture_jajce_lookdev.gd -- \
--scene=res://world/jajce/JajceGoatLookdev.tscn \
--hide-debug-labels --animal-hunger=goat_dunja:90 \
--output=res://docs/baselines/jajce_goat_07_hungry.png
```
For the content frame, change the hunger override to `20` and the output
filename to `jajce_goat_07_content.png`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://0ldryrvw76q6"
path="res://.godot/imported/jajce_goat_07_content.png-138802d07281bb13d9d6edbbb359f838.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/baselines/jajce_goat_07_content.png"
dest_files=["res://.godot/imported/jajce_goat_07_content.png-138802d07281bb13d9d6edbbb359f838.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bexlabef1e1xa"
path="res://.godot/imported/jajce_goat_07_hungry.png-2474d252bb9b213aa9e6cc2457baacb4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/baselines/jajce_goat_07_hungry.png"
dest_files=["res://.godot/imported/jajce_goat_07_hungry.png-2474d252bb9b213aa9e6cc2457baacb4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1