A shared observation layer for Project Zomboid (Build 42) mods. - part of the DREAM family.
Steam Workshop → [42SP] WorldObserver
WorldObserver is a cooperative world-sensing engine for Project Zomboid mods. Instead of hand-rolling OnTick scan batches, stitching together event listeners, and managing your own cache invalidation, you declare interest—what should we observe, what guarantees for scope and freshness do we need?—and subscribe to ready-made observation streams.
This makes world-observation code compact and declarative. You compose readable pipelines and let the engine handle the execution.
The result is signal over noise: rather than processing raw world state, you consume fewer, more actionable observations that directly express what you actually care about.
With "WO", as with most high-level frameworks, you sacrifice some control over scope and timing that hand-rolled world-sensing logic provides, in exchange for a more compact, convenient, and expressive way to access a broad range of observations.
Use it for features like “corpse squares near the player”, “chef zombies in kitchens”, or “cars under attack”—and other situations that benefit from rich data and can tolerate asynchronous behavior.
When multiple mods would otherwise perform heavy scanning in parallel, WorldObserver can help them cooperate by merging overlapping interests, sharing the probing work, enforcing budgets and fairness, and keeping frame time predictable.
This example shows how you
- declare an interest (so WorldObserver knows what facts to gather)
- subscribe to a base observation stream
- stop cleanly (unsubscribe + stop lease)
Full walkthrough:
local WorldObserver = require("WorldObserver")
local MOD_ID = "YourModId"
-- note: duration in in-game seconds
local lease = WorldObserver.factInterest:declare(MOD_ID, "quickstart.squares", {
type = "squares",
scope = "near",
target = { player = { id = 0 } },
radius = { desired = 8 }, --tiles
staleness = { desired = 4 }, -- typical duration between refresh
cooldown = { desired = 10 }, -- time window in which emissions are suppressed
})
local corpseSquares = WorldObserver.observations:squares()
:squareHasCorpse() -- try removing this line if you see no output
:distinct("square", 10)
:subscribe(function(observation)
local s = observation.square
print(("[WO] squareId=%s x=%s y=%s z=%s source=%s"):format(
tostring(s.squareId),
tostring(s.x),
tostring(s.y),
tostring(s.z),
tostring(s.source)
))
-- Optional: brief visual feedback for the found square
WorldObserver.highlight(s, 750, { color = { 1.0, 0.2, 0.2 }, alpha = 0.9 })
end)
_G.WOHello = {
stop = function()
if corpseSquares then corpseSquares:unsubscribe(); corpseSquares = nil end
if lease then lease:stop(); lease = nil end
end,
}- Facts are discovered by WorldObserver (listeners + probes) into which your mod declared an interest.
- Observation streams then emit plain Lua tables (“observations”) such as
observation.squareorobservation.zombie - These can be used as-is or further refined by your mod
- Shared work and fairness: when multiple mods declare overlapping interests, WorldObserver merges leases and runs shared probing/listening work.
- Safety settings:
radius,staleness,cooldownlet you express quality expectations while WorldObserver stays within budgets. - Signal over noise: helpers +
distinctlet you compact raw updates into “interesting observations” your mod can act on. - Composability: build derived streams by combining base streams (joins, windows, distinct). Start here: Derived streams.
Under the hood, WorldObserver is powered by LQR + lua-reactivex, but you can ignore that until you need derived streams:
- Build: Project Zomboid Build 42 only.
- Scope: v0 is singleplayer-first (player id
0). - Stability: approaching alpha; naming and shapes may still change.
- Location in this repo:
Contents/mods/WorldObserver/42/.
Docs:
- Docs index (start here)
- Quickstart (copy/paste first working example)
- Observations overview (what you can subscribe to)
- Situation factories (name and reuse situation streams)
- Glossary (canonical terminology)
- Troubleshooting
Internal docs:
docs_internal/vision.mddocs_internal/logbook.md
MIT
