An AI-driven agriculture simulation and intelligent NPC laboratory built in Godot 4 - a research project on breaking static NPC routines with lightweight, native Reinforcement Learning.
In cozy farming sims like Stardew Valley or Animal Crossing, NPCs usually follow rigid, pre-scripted daily routines. Minifundium asks a different question: what if a farm helper actually learned from how you play, instead of just executing a schedule? The name comes from the Latinized Spanish word “minifundio”, referring to a very small farming plot.
Minifundium is split into four isolated layouts, each swapping the chicken helper's "brain" for a different decision-making model - letting deterministic behavior be directly compared against adaptive learning.
The chicken stays Idle until the player explicitly signals a harvest with the contextual action key (R). Validates the physical controller and navigation.
Classic reactive AI: on a timer, the brain checks "is there a ready crop?" and transitions accordingly. Autonomous, but inflexible.
A lightweight, tabular Q-learning analogue built from scratch in GDScript. The agent builds its own weighted transition matrix from the player's reinforcement.
Multiple chickens share one autoloaded global matrix. Teaching a single helper instantly optimizes the behavior of the entire flock.
To avoid resource-heavy external dependencies, sockets, or ML frameworks like MLAgents, Minifundium implements
its weight-based transition logic natively inside lightweight GDScript components (brain_rl.gd).
1. Balanced starting weights
var matrix = {
"Idle": {"Idle": 1, "Harvest": 1},
"Harvest": {"Idle": 1, "Harvest": 1}
}
2. Reinforcing a player command
func on_player_command() -> void:
var crop = chicken.find_ready_crop()
if crop:
matrix[last_state]["Harvest"] += 1
chicken.target_crop = crop
last_state = "Harvest"
chicken.state.transition_to("Movement")
3. Choosing the next action - weighted roulette selection
func _choose_weighted_action() -> String:
var choices = matrix[last_state]
var total_weight = 0
for w in choices.values():
total_weight += w
var random_val = randi() % total_weight
var current_sum = 0
for state in choices.keys():
current_sum += choices[state]
if random_val < current_sum:
return state
return "Idle"
Every time the player rewards a harvest, the odds shift a little further away from wandering and toward farm work - turning an aimless chicken into a proactive helper shaped by the player's own habits.
Notes from the full article on building Minifundium's AI.
Spamming the reward key skewed the roulette wheel so hard that chickens got stuck trying to harvest empty plots forever. Fixed with a clamping function that caps and normalizes matrix weights.
A predictive context validator now double-checks the world state before executing the roulette wheel's choice - if the chosen action isn't actually viable, its weight is temporarily zeroed and the wheel spins again.
Sharing one matrix across three chickens in Lab 4 meant training a single bird instantly changed how the whole flock behaved - a small, unplanned glimpse of collective learning.