bevy_society
Give your Bevy world a living society: villagers who remember, gossip, take sides, fall out and make up — and who you can script.

One evening in a village of twenty-four people: Bram steals bread, the watch sees it, and by nightfall the rumor has reached twenty-three of twenty-five. Tie lines show friendships green and rivalries red; the red ring marks whoever knows the rumor. Every clip below is the same village, same seed, same run — only the overlay changes.
One clip per feature
Mood — a halo on every villager. Green is pleased, amber neutral, red displeased; halo size is arousal. The colour scale is normalised to the ±0.4 the model actually produces, so a small mood shift is visible. |
Opinions — one bar per villager on the honesty topic, green above the midpoint and red below, length by strength. |
Needs — a full green halo is a fed villager, a small red one is hungry. |
Relationships — the tie graph alone: green friendship, red rivalry, brightness by familiarity. |
Factions — orange millers, green weavers, violet watch, no ties, so the group structure is the only thing on screen. |
The clip above is the same run with every overlay on. |
What you get
- People with a mind — Big Five personalities, moods on a pleasure/arousal/dominance axis, an OCC emotion set, needs that decay, and memories that fade. personality-and-mood, memory
- Daily life — utility-AI decisions over tagged places and other people, with travel, arrival, durations and need satisfaction. needs-and-utility
- Relationships — directed ties carrying affinity, trust, respect and familiarity, with power-law forgetting, structural balance, a Dunbar-style cap and friend/rival labels. Affinity is clamped to
±Tuning::affinity_ceiling(0.35) and aFriendneeds trust as well, so a large town with daily contact keeps friendship scarce: measured, 60 people over 30 game-days reach 155 friend ties of 3 540 ordered pairs (4.4%) and 656 rivals. relationships, the measurement - Opinions — bounded-confidence influence, stubborn anchors and conformity to a clear group majority. opinion-dynamics
- Rumors and reputation — gossip with the Daley–Kendall stifling rule, belief scaled by trust, witnesses who judge deeds and hold grudges. rumor-and-reputation
- Factions and status — in-group affinity, standing as conferred respect, contests and intergroup contact. groups-and-status
- Crowd fields — a stigmergy grid of presence and commotion per cell, readable as a
Field(Crowd)consideration and walkable as aGradienttarget, so an activity can be "join the crowd" without naming a place. needs-and-utility - Scripts — Rhai triggers over a per-tick snapshot, with a
ctxreader and anfxwriter that cannot see its own writes. scripting - Dialogue — barks and conversations through
bevy_speech_bubbles. dialogue
Every formula traces to a paper; every rate is a named field on Tuning; every default either comes from a paper or says it is a design default.
Quick start
[]
= { = "https://github.com/Ladvien/bevy_society" }
use *;
use ;
let defs = starter.expect;
// A headless app has no frame clock, so `Time<Fixed>` never advances on its own. Without this the
// society takes zero ticks however many updates you run.
let step = from_secs_f64;
let mut app = new;
app.add_plugins
.insert_resource
.insert_resource
.insert_resource;
// `DialoguePlugin` needs a speech-bubbles plugin over a camera, which this snippet has not.
let plugins = SocietyPlugins.build;
let plugins = plugins.;
app.add_plugins;
let well = app
.world_mut
.spawn
.id;
for name in
for _ in 0..600
// The society advanced: this line is what proves the fixed step was pinned.
assert!;
Run it headlessly with cargo run --example quickstart, which is this code. On a real run it reports
59 social exchanges, 30 accepted after nine game-days.
The full village, with balloons and the debug server:
cargo run --example village -- /System/Library/Fonts/Supplemental/Arial.ttf
Font paths: macOS /System/Library/Fonts/Supplemental/Arial.ttf; Linux /usr/share/fonts/liberation/LiberationSans-Regular.ttf; Windows C:\Windows\Fonts\arial.ttf.
A tour
People
app.world_mut.spawn;
A player character is a Person without Autonomous: it does only what the game tells it.
Needs and daily life
(
id: "sleep",
target: Home,
duration_hours: 8.0,
rank: 1,
considerations: [
(input: Hour, curve: Hours(22.0, 6.0)),
(input: Need("rest"), curve: Linear(-1.0, 1.0)),
],
satisfies: [("rest", 0.15)],
)
Options are scored by compensated considerations, tiered by rank, and drawn by weight.
Relationships
Ties holds one directed edge per person with affinity, trust, respect and familiarity. Labels are recomputed each tick and a change writes TieLabelChanged.
Opinions
An accepted chat pulls both sides' opinions toward each other inside their confidence bounds, and a confident group majority pulls an agreeable person further, one tick at a time.
Rumors and reputation
app.world_mut.write_message;
Every witness who knows it can pass it on; a listener who already knows it stifles the teller.
Factions and status
(factions: [(id: "millers"), (id: "weavers"), (id: "watch", ingroup_bias: 0.3)])
Two co-members start a tie with in-group affinity, and standing is the respect conferred by everyone who knows a person well enough.
Triggers and scripting
(
id: "watch_stops_theft",
on: NormViolated("steal"),
if: Some("ctx.member(actor, \"watch\")"),
then: "fx.bark(actor, \"Stop, thief!\")",
)
Dialogue
app.add_plugins;
app.world_mut.write_message;
Built on
| Crate | Used for | Source |
|---|---|---|
| Bevy | the engine | https://github.com/bevyengine/bevy |
| bevy_speech_bubbles | barks and conversations (dialogue) |
https://crates.io/crates/bevy_speech_bubbles |
| bevy_stigmergy | per-cell presence and commotion fields (fields) |
https://crates.io/crates/bevy_stigmergy |
| bevy_orca | reciprocal collision avoidance for walkers (steering) |
https://crates.io/crates/bevy_orca |
| bevy_emerge_rng | the seeded ChaCha8 stream | https://crates.io/crates/bevy_emerge_rng |
| Rhai | trigger and effect scripts | https://github.com/rhaiscript/rhai |
| serde | authored definitions | https://github.com/serde-rs/serde |
| RON | the definitions' file format | https://github.com/ron-rs/ron |
| rand_chacha | names the ChaCha8 generator the stream comes from | https://github.com/rust-random/rand |
| bevy_fairy | the agentic debugger in the village example | https://github.com/Ladvien/bevy_fairy |
The science
| Module | Key papers | Note |
|---|---|---|
| Personality and mood | Mehrabian 1996 · Gebhard 2005 (ALMA) | note |
| Needs and utility | Mark & Dill (Game AI Pro 3 ch. 13) · Dill 2015 | note |
| Relationships | Heider 1946 · Zajonc 1968 · Dunbar 1992 | note |
| Opinions | Deffuant et al. 2000 · Friedkin & Johnsen 1990 · Asch 1956 | note |
| Rumors and reputation | Daley & Kendall 1964 · Nowak & Sigmund 1998 · Ohtsuki & Iwasa 2004 | note |
| Factions and status | Tajfel et al. 1971 · Pettigrew & Tropp 2006 · Bonabeau et al. 1995 | note |
| Memory | Park et al. 2023 · Anderson & Schooler 1991 | note |
| Social simulation in games | Game AI Pro chapters · McCoy et al. 2014 | note |
The papers were gathered with home-still, a local corpus pipeline; docs/research/README.md lists a DOI and a corpus id for every mechanism.
Bevy compatibility
| bevy_society | Bevy |
|---|---|
| 0.1 | 0.20 |
Documentation
Getting started · Concepts · Authoring · Scripting · Dialogue · Research · Demos
Contributing
Issues and pull requests are welcome. Agents working in this repository should read AGENTS.md first: it lists the invariants (determinism, the message rule, the single script-application point) that a change must not break.
License
MIT OR Apache-2.0, at your option.
Mood — a halo on every villager. Green is pleased, amber neutral, red displeased; halo size is arousal. The colour scale is normalised to the ±0.4 the model actually produces, so a small mood shift is visible.
Opinions — one bar per villager on the honesty topic, green above the midpoint and red below, length by strength.
Needs — a full green halo is a fed villager, a small red one is hungry.
Relationships — the tie graph alone: green friendship, red rivalry, brightness by familiarity.
Factions — orange millers, green weavers, violet watch, no ties, so the group structure is the only thing on screen.