A mound of dark boulders at night with glowing gold veins running through it, warm light and drifting gold dust pouring from its timber doorway, and a dwarf with a pickaxe walking toward the entrance

Workers vanishing into the gold mine, and the light that says where they went

Rendering & Graphics7 min readUpdated
ClaudeBuilt the thing
Adam SturrockDecided what mattered

Players told us their workers were disappearing. Send a Miner to the gold mine and it walks up to the rock, vanishes, and a few seconds later reappears carrying gold, and from the outside nothing about that looks intentional. Several people read it as a bug.

It is the oldest convention in the genre. The worker goes inside the mine. In Shards of Stone a trip inside lasts 80 simulation ticks, which is four seconds, and up to eight workers can be in one mine at once. The game was doing exactly what it was designed to do. What it was not doing was saying so.

Adam passed the reports on in a batch of player feedback, and the ask was plain: make it obvious that the workers have gone inside. The answer is that an occupied mine now looks occupied. Lantern light spills out of the doorway, pools on the ground in front of it, and the gold veins in the rock pulse while anyone is working inside.

The same 3D gold mine twice, seen from the front. On the left it is empty and its timber doorway is dark. On the right three workers are inside, the doorway glows, a pool of warm light spreads onto the grass and the gold veins are brighter

Try it: send workers into the mine

The figure below is the real thing, not a recording. The 2D pane is the game's own 2D renderer drawing all seven gold mine sprites, and the 3D pane loads the same model a match loads. Each press of the button is one real four second trip. Switch to Before and send a few in: the count goes up and the mine shows nothing at all, which is exactly what players were looking at.

Mine empty

All seven mine sprites, drawn by the game’s 2D renderer.

Press Send a worker in, then compare Before and After. Each press is one four second trip, and the light lasts as long as a worker is inside. Live 3D model loads the real mine; Show gold mask shows which texels count as gold.

A light that answers yes or no

The first decision was what the light should say. It could report how many workers are inside, or only whether anyone is. A player glancing across a base wants the second: is this mine being worked or not. So the brightness is a step, not a ramp. One worker takes the glow straight to 70% of full, and each additional worker adds only a little more, up to 100% at eight.

// Glow target for a number of occupants: off, then a clear jump.
function mineGlowTarget(occupants: number, maxOccupants = 8): number {
  if (!(occupants > 0)) return 0;
  const t = Math.min(1, (occupants - 1) / Math.max(1, maxOccupants - 1));
  return 0.7 + 0.3 * t;
}

The displayed level eases toward that target, 0.35 seconds to fade in and 0.9 seconds to fade out. The asymmetry is deliberate. Workers come and go constantly on a busy mine, and a fast fade-out would make the door flicker between trips instead of reading as a mine that is in use.

Fog of war needed its own rule. A mine you last saw minutes ago must not light up because an enemy worker has just walked into it. The glow only draws while a tile of the mine is actually visible, and when it is not, the light switches off instantly. A fade would give the game away: it would show the exact moment the occupancy changed, through fog.

Nothing in the model says where the door is

The mine's 3D model came out of Meshy, from the 2D sprite, through the pipeline described in turning 2D sprites into 3D buildings. A generated model is triangles and a texture. It has no idea it has a doorway, and there is no reliable way to recover one from the geometry, so the door's position, size and angle are numbers dialled in by hand.

Seven 2D sprite variants have the same problem seven times over, since every painting puts its door somewhere slightly different. One of them has a shut wooden door rather than an open tunnel, so on that variant the light leaks round the edges and under the sill instead of filling the opening.

That is what the gold mine lab is for. It draws the real model and every sprite with the shipped effect on top, exposes every number as a slider, and copies the result out as a paste-ready block. The lab and both of the match's renderers read one shared file of those numbers, so what looks right in the lab is what ships. The general case is covered in why every system gets its own lab, and this effect is a good example of a number that cannot be found any other way.

Two of the 2D gold mine sprites, a timber-framed cave and a mine with minecart rails, shown empty with dark doorways on the left and occupied on the right, with warm light filling each doorway and sparks drifting out

Light that stayed inside its tunnel

The first 3D version put a glowing quad in the doorway and looked right from the front. From above, or with the camera swung round, the light all but disappeared, and the lab's low and overhead views showed why. The door sits inset at the back of a short tunnel, well behind the front face of the rock, so any camera looking down from above sees the tunnel roof and none of the light.

The fix was to make the light escape. An occupied mine now has four more layers, all driven by the same glow level:

  • soft light shafts that flare out of the doorway and tilt slightly upward;
  • a pool of warm light on the ground in front of the door, and a second one on the mine's own entrance floor;
  • haze that rises out of the door and leans back over the rock, so an overhead camera sees it over the mound;
  • embers and gold dust drifting up, some from the door and some from a vent on top.

A second mistake turned up while tuning those. The drifting motes travelled a fixed multiple of the halo's distance from the door, so pushing the halo out in the lab flung them more than a model's depth away, where they hung in empty air beside the mine. Every layer now has its own distance, and a test pins the furthest reach of the whole effect to the model's footprint so a future tweak cannot fling them again.

Mines also used to be placed at any rotation, which could turn the doorway away from the camera entirely. Their rotation is now held within 35 degrees either side of facing the camera, so the entrance, and the light, are always on the side the player sees.

The 3D gold mine from a low three-quarter angle with eight workers inside, golden haze and glowing embers drifting out of the recessed doorway and sparks rising above the peak

Making the gold veins glow without repainting them

The rock itself is painted with bright yellow veins running through grey and purple stone, framed by brown timber. Making the veins glow would sell "work is happening in there", but nothing records where the veins are, and repainting a mask by hand for a generated texture plus seven sprites was not appealing.

So both renderers find the gold by colour. Every texel is converted to hue, saturation and value, and a soft window is kept around the vein yellow. Each neighbouring material fails exactly one test: grey stone has too little saturation, and timber has the wrong hue, around 20 to 30 degrees against the vein's 51. The 3D shader evaluates the mask per pixel. The 2D renderer runs the same function once per sprite into a cached mask. A unit test feeds both a set of sampled colours so the two cannot drift apart.

This is the transferable part, and it needs no library:

// How much an sRGB colour (0..1) belongs to a target colour, 0..1.
// Hue is a window; saturation and value are floors. All edges are soft.
function smoothstep(e0, e1, x) {
  const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
  return t * t * (3 - 2 * t);
}
 
function rgbToHsv(r, g, b) {
  const max = Math.max(r, g, b), d = max - Math.min(r, g, b);
  let h = 0;
  if (d > 1e-5) {
    if (max === r) h = ((g - b) / d + 6) % 6;
    else if (max === g) h = (b - r) / d + 2;
    else h = (r - g) / d + 4;
  }
  return [h * 60, max > 0 ? d / max : 0, max];
}
 
function colourMask(r, g, b, { hue, hueTol = 14, satMin = 0.62, valMin = 0.58, softness = 0.25 }) {
  const [h, s, v] = rgbToHsv(r, g, b);
  let dh = Math.abs(h - hue);
  dh = Math.min(dh, 360 - dh);
  const hs = softness * 40, ss = softness * 0.4;
  const hueM = 1 - smoothstep(hueTol - hs / 2, hueTol + hs / 2, dh);
  const satM = smoothstep(satMin - ss / 2, satMin + ss / 2, s);
  const valM = smoothstep(valMin - ss / 2, valMin + ss / 2, v);
  return hueM * satM * valM;
}

Multiply your emissive term by that mask in the fragment shader and only the chosen material lights. The veins pulse gently when a mine is empty, and brighten and quicken while workers are inside. As a mine is worked out, the glow dulls toward a tarnished ochre.

The occupied gold mine with glowing veins on the left, and beside it the same view as a black and white mask where only the gold vein texels are white

Checking your own game for invisible state

If a unit can leave the map without dying (inside a mine, a transport, a tower, a tunnel), check what a player sees at the spot where it went. If the answer is "the same thing as before it arrived", a player will read the disappearance as a bug. The cheapest fix is almost never a tooltip. It is a change to the thing the unit went into, visible at game-camera distance, that only has to answer yes or no.

The same pass also taught the tutorial to say it: the step that sends the first worker to mine now explains that workers go inside, and that worker starts a few tiles further out so the walk in is visible. The gold now comes out with the worker, too: every worker carries a visible load home, which is how workers came to wear what they gather.

Questions

Why do workers disappear when they mine gold in an RTS?

Because in the Warcraft tradition they go inside the mine. A worker walks into the entrance, stays hidden for a few seconds while it mines, then walks out carrying gold. In Shards of Stone a trip inside lasts four seconds, and up to eight workers can share one mine. The worker is not lost or bugged, and it comes back out with its gold.

How can you tell a gold mine has workers inside it?

The mine lights up. While at least one worker is inside, warm lantern light spills from the doorway, pools on the ground in front of it and the gold veins in the rock pulse. The first worker makes the biggest change, and more workers only brighten it a little. The light fades out a moment after the last worker leaves.

How do you make only part of a texture glow in three.js?

Find the part by colour instead of by painting a separate mask. Convert each texel to hue, saturation and value, keep a soft window around the colour you want, and multiply the emissive term by that mask in the fragment shader. Tune the window so neighbouring materials each fail one test, for example grey stone failing saturation and brown timber failing hue.

Can an effect like this give away information through fog of war?

Yes, if it is not gated. A mine you last saw an hour ago must not light up because an enemy walked into it just now. The glow only draws when a tile of the mine is currently visible. Out of sight it switches straight off rather than fading, because a fade would itself show the moment the occupancy changed.

← All posts