Raven Iron
All Devlogs
DevlogCairn

Cairn v0.7.0: Detecting a Landmark Instead of Adding One

By RavenIron Games

On a world with no map, knowing where you are is knowledge — and it lives in players’ heads and in screenshots pasted into Discord. Cairn moves it into the world, where it can be built, found, lit, and lost.

The design constraint was total: no new pieces, no assets, and nothing on your screen. Everything below follows from that.


1. A Cairn Is Detected, Never Provided

The obvious implementation is a buildable Cairn piece. We didn’t add one, for two reasons.

A mod that adds a prefab must ship server-side, or ZNetScene.CreateObjectsSorted calls DestroyZDO on every hash it cannot resolve — silent damage in someone else’s world, with no error a player would ever see. And a piece can never be uninstalled cleanly: remove the mod and the build site is full of holes.

So Cairn recognises a cairn instead: four Placeable_Stone — a vanilla Hoe piece costing one stone each — stacked inside a four-metre footprint. Nothing is added, nothing is at risk in the save, and uninstalling leaves exactly what it appears to be: a pile of rocks with a sign on it. Every cairn also looks different, because you stacked it.

That pushes the difficulty into the detection rule, which has to tell a waymark apart from a wall, a tower and a stone house. It does that by footprint, because footprint is the thing a cairn cannot fake — a cairn is narrow and short, a building is wide. Piece count alone would not do it: a tidy six-piece wall is a wall.

The stakes are unusual for a detection rule:

A detection rule that is slightly wrong does not throw. It just quietly decides someone’s front step is a landmark.

So the whole rule is pure arithmetic over Vector3 with no game types, testable off-game, and grouping is single-linkage through a spatial grid rather than an all-pairs sweep — a world’s stone pieces run to thousands, and O(n²) inside a tick is how a sweep becomes a stutter. Cell size equals the link radius, so only the 27 neighbouring cells are ever compared.


2. A Light That Behaves Like A Light

A fire on a distant headland should read as a point of light, the way a real one does. Rendered naively it does the opposite: it shrinks with distance until it disappears, which is precisely when you need it.

So the beacon holds constant angular size and billboards toward the camera:

// CONSTANT ANGULAR SIZE — the whole reason this is visible at range.
float size = Mathf.Clamp(dist * angular, minSize, maxSize);

Transform t = lit.Go.transform;
t.position = lit.Position;
t.rotation = Quaternion.LookRotation(lit.Position - camPos);   // billboard
t.localScale = new Vector3(size, size, 1f);

Verified down a chain of fifteen cairns spanning 420m.

The counterweight is occlusion: a ridge between you and a cairn puts the light out, so you have to move to see it. That is the difference between navigating and being told, and it’s the reason this is a beacon rather than a marker. Occlusion is stepped across frames with a cursor rather than raycast all at once, because a navigation aid that costs frames is not one.

It is deliberately not smoke, which was the first instinct. The engine’s own policy runs the opposite way: SmokeSpawner.Spawn refuses to emit past 64m from the local player, Smoke is globally capped at 100 puffs with the furthest culled first, and every puff is a Rigidbody. A beacon needs to be visible at 500m and cost nothing — the exact inverse on all three counts.

It’s also client-drawn from synced state rather than a networked object, because at beacon range the anchor’s ZDO is not loaded and never will be. The light cannot depend on the thing it marks being live.

And because it’s cosmetic, it’s fenced: a beacon that throws disables itself, clears its objects, and logs once. A visual that throws every frame is worse than a visual that is absent.


3. The Queue That Ate The Raven’s Line

Stand at a named cairn and Hugin may land and say its name. Getting that line to actually appear took two attempts.

The first version appended a temp text so vanilla’s own lines would come first — polite, and useless. Measured live, the queue held five entries, four of them untriggered tutorials, and GetTempText returns the first match and stops. A line at the back of that queue is a line nobody ever reads.

The fix was to stop queueing and plant a guide point instead — vanilla’s own mechanism for marking a place, which competes on proximity and wins a priority tie without displacing anything. Its OnDestroy unregisters it, so we never touch the static list ourselves. And we never set a tutorial key: writing one marks a tutorial as seen on the player’s own save, permanently, which is not a cost a navigation mod gets to impose.

The bird itself is deliberately never load-bearing, and vanilla’s constraints make that unavoidable rather than merely polite. Decompiled and confirmed:

  • Raven.FindSpawnPoint demands height > 30f, so the bird cannot land below 30m of altitude — it is absent on exactly the coastlines a navigator cares about.
  • It despawns when a hostile is within 10m.
  • It despawns during RandEventSystem.InEvent() — so a Ragnarok’s Wrath storm silences it at the moment you would most want a landmark named.

You cannot build a navigation channel on that. You can build a grace note. So Cairn offers a line and lets vanilla decide whether the bird shows up; when it doesn’t, nothing is lost and nothing is logged as wrong. cairn raven reports which of the four reasons it’s quiet for — including your current altitude against the 30m floor.

Every one of those members is reached by reflection, resolved at runtime and cached. The reason is a scar: Terminal.commands reads public in the publicized assembly and was private at runtime, which killed a server mid-boot. Losing that way inside a client’s frame loop would be worse.


What It Still Refuses To Do

No map, no compass, no waypoints, markers, pins, arrows, HUD or readout of any kind. Nothing is written to Minimap, so a map mod and this one simply ignore each other — though running both does rather blunt the point.

Landmarks are stored per world, beside the save, in plain text you can read. Details on the Cairn project page.