Ward Seven · Roblox place · build analysis 6 stages · 650 assertions build b3-desk

It died on frame one, and nobody could see it

Ward Seven is a night shift in a veterinary clinic, shipped as a single .rbxl place. Two bugs nearly killed it, and they failed in opposite directions: one took the entire server script down before the ward was built, and one left a desk standing inside the player's head. Neither produced an error a player could read — the game just wasn't there.

Luau, six files2,812 lines
Place file39,597 bytes
Anomalies16 catalogue entries
Assertions650 0 failures
Ward96 × 12 studs
Nights8 + 2 endings

01The ask

The brief was a genre and two reference points, not a design document — a story horror game in a veterinary clinic, in the register of Animal Hospital, with "anomalies" as the mechanic. That is enough to fix the whole shape of it: one shift, one corridor, one thing wrong per night, and a report you have to be right about.

After the first build was delivered “ok it works, how do i play it?” — then, the next morning: “theres no desk, wth”

Both messages are the same message. The place opened, Studio ran it, and what the player got was a baseplate with a dead HUD. The explanation for why is the most useful part of this build.

02Bug one: a write the server is not allowed to make

The build stage ran a lighting setup, and the last line of it set the height at which fallen parts are destroyed:

-- Ward.server.lua, in buildLighting()
workspace.FallenPartsDestroyHeight = -300

In a live play session that property is capability-restricted — writable by a plugin or an open cloud session, not by a game script. Studio's own log had the whole story:

Error: The current thread cannot write 'FallenPartsDestroyHeight'
       (lacking capability PluginOrOpenCloud)
  Script 'ServerScriptService.Ward', Line 140 - function buildLighting
  Script 'ServerScriptService.Ward', Line 149

Line 149 was a bare top-level call. In Luau an uncaught error terminates the Script it happened in — and that Script was the entire game: player state, remote events, ward construction, the shift loop, the report handler. Every one of those came after line 149 and none of them ever ran. The corridor, the doors, the kennels, the desk — all of it was inside a Script that had already stopped.

Why it looked like "nothing is there" instead of "something crashed"

The failure is server-side and swallowed. The player still spawns, because spawning is engine behaviour, not game code. There is no error on screen, no missing-asset warning, no red text — just a flat baseplate and a HUD with no values in it. The only place the truth existed was ~/Library/Logs/Roblox/*_Studio_last.log.

The fix is two things, not one

Guarding the write stops this bug. Making every boot step go through a named stage stops the class of bug:

-- the guard: a capability refusal must never cost the boot
pcall(function() workspace.FallenPartsDestroyHeight = -300 end)

-- the wrapper: every stage is named, every failure is reported
local function stage(name, fn)
    local ok, err = pcall(fn)
    if not ok then
        warn(("[WardSeven] build stage '%s' failed: %s"):format(name, tostring(err)))
    end
    return ok
end

Now a stage can fail and the ward still builds, and the failure arrives with a name attached. The boot log ends with a count: WARD SEVEN v1.1.0 loaded — 0 stage(s) failed.

03Bug two: a desk inside your own head

With the ward finally building, the report came back: “theres no desk, wth”. The ward was there. The desk was not visible. It was authored like this:

WhatValueConsequence
Positionz = START_Z − 1its near face sat 0.5 studs behind the spawn point — the player spawned inside it
Helper useddeco()CanCollide = false — walk-through scenery
Camerathird personsits ~10 studs behind the character → buried in the middle of the block

Three properties multiplied into an object that could not be seen and could not be felt. Roblox culls backfaces, so from inside a solid box you see through it; with CanCollide = false there wasn't even a bump to tell you it existed. A desk that is both invisible and intangible is indistinguishable from no desk — and a screenshot from inside it would have shown an empty corridor.

Two smaller things were wrong in the same corner, and finding them is why the fix was worth doing properly: the charge station sat three studs from the spawn point, right in front of the camera, and the corridor entity spawned inside the intake wall at START_Z + 2 — so "something is standing at the end of the corridor" had never once been visible from the corridor.

The fix: geometry that assumes a body

Desk is now a solid counter (slab(), CanCollide = true), 5 × 3.2 × 3, against the left wall 14 studs down the corridor, with the monitor standing on it. The spawn moved in to START_Z − 9, so there is corridor in front of you instead of furniture. The entity starts inside the corridor at START_Z − 2. The terminal desk you report at is solid too, so you stop at it instead of drifting through.

04How either bug could survive a whole build — and the answer

Both bugs are invisible to the checks that are easy to write. The place file serialises perfectly with a dead script inside it; the source compiles; every class name and property name is real; the desk exists in the instance tree at the right coordinates. What neither check does is run the game.

So the pipeline runs the real scripts, verbatim, against a mock Roblox engine. Lune loads the .lua files off disk — the same bytes that go into the place — and executes them with a fake Instance.new, a fake workspace, fake remotes, fake players and a fake RunService heartbeat. The game does not know the difference.

  1/6 rules and story            pure logic, no Instances — 139 assertions
  2/6 API surface                282 class/property/enum names checked against rbx-dom
  3/6 build place file           roblox.serializePlace
  4/6 verify place file          deserialise it again, compile every source, 37 checks
  5/6 server smoke test          the real server script × 200 heartbeats — 146 checks
  6/6 client smoke test          the real HUD script against every server state — 40 checks
  ─────  publish to a path Studio has never opened
  ✓   playcheck                   deserialise the PUBLISHED file, assert the build id and fixes

The mock has to be as strict as the engine

The first mock was permissive: workspace accepted any write, so the boot-fatal bug passed the suite. The fix was to make the mock refuse what a live session refuses:

-- smoke_test.luau: the mock reproduces the capability boundary
if key == "FallenPartsDestroyHeight" then
    error("The current thread cannot write 'FallenPartsDestroyHeight' "
       .. "(lacking capability PluginOrOpenCloud)", 2)
end

Then the guard was removed on purpose, as a negative control, to prove the suite would have caught the original bug:

FAIL  server script executes without error
      ([string "luau.load(...)"]:170: The current thread cannot write
       'FallenPartsDestroyHeight' (lacking capability PluginOrOpenCloud))
FAIL  ward built for the joined player
RESULT: FAIL — the server never built a ward; stopping here

Restored, and it passes. A test that has never been seen to fail is not evidence; it is decoration.

05The same treatment for the desk

The desk regression is geometric, so it needed geometric assertions. Section 10 of the smoke suite walks the ward the way a player would and asks the questions a player would:

CheckWhat it provesFailure text if it breaks
the desk exists at the intake endthe builder actually ran, and furniture is where the brief saysDesk:missing
the desk is solidit is CanCollide = true — you cannot walk through a counterthe desk is solid, not scenery you walk through
nothing occupies the spawn pointno part's volume contains the player's spawn positionnothing is inside the spawn point
the desk is not on the walk linethe corridor centreline is clear to the far doorDesk:blocks-the-walk-line
the corridor entity starts inside the corridorthe "something standing" anomaly can be seen from insidethe figure starts outside the ward

Putting the old desk back made two of them fail in the same run — the solidity check and the walk-line check. Both are structural, neither depends on a screenshot, and both run in under a second.

06The game the tests are guarding

The mechanic is a single-bit puzzle repeated eight times: one thing is wrong, or nothing is. That only works if the anomaly is unambiguous and the "clean" state is memorable, so the catalogue is fixed at sixteen entries — each one mutating specific named parts, and each one carrying an explicit restore so nights cannot leak state into each other.

AnomalyRoomWeightFirst nightWhat changes
DoorAjarEXAM 1101door rotates 46° into the corridor
DoorShutSURGERY82the normally-ajar door is pulled shut
ExtraDoorCORRIDOR73frame, door and sign appear at z = 16
LightsRedCORRIDOR83all fixtures + point lights go red, brightness 1.6
FlickerCORRIDOR92folder attribute FlickerMode = "fast"
LightsOffCORRIDOR64housings go ceiling-colour, point lights disabled
ChairTurnedEXAM 2101chair rotated to face the wall
PosterSwappedEXAM 281poster text replaced
ClockWrongINTAKE82clock hands moved
EmptyKennelKENNELS91kennel 3 emptied, card left up
ExtraCageKENNELS73a fifth cage built into a four-cage row
FootprintsCORRIDOR84wet print trail across the floor
StainCORRIDOR82dark stain on the right wall
FigureCORRIDOR65a silhouette at the far end
GurneyMovedCORRIDOR74gurney out of surgery, blocking half the width
BellSURGERY66a bell appears above the surgery door

The smoke suite applies all sixteen and restores all sixteen, asserting that the ward returns to its base state each time — because an anomaly that fails to restore would make the next night's clean state wrong, and every night after it would be unwinnable.

07The report, and why it is one bit

Stand within 11 studs of the desk, press E, answer once. Right answer → the night advances and a page of story arrives. Wrong answer → a strike, the corridor turns red, and something starts walking behind you, slower than a sprint. Three strikes → the log closes and the night restarts at 00:12.

The pure state machine behind that lives in WardLogic.lua with no Instances and no services, which is why 139 assertions can run against it in milliseconds with a deterministic math.random stub. Everything the player feels — the pacing, the forgiveness, the ramp of the eight nights — is tunable in WardConfig.lua and nothing else.

ConstantValueWhy that number
ReportRadius11far enough that you do not have to find the exact desk stud, close enough that you must come back
SprintDrain3.4 /s≈ 22 s of running in a whole night — enough to escape once, not enough to sprint the shift
StrikesAllowed3two wrong guesses are survivable; the third resets the night
WARD.Length96long enough for a walk back to be a decision, short enough to learn in one lap
WARD.HalfWidth612 studs wide: two abreast, and the gurney blocks half of it

08Evidence

ArtifactDetailStatus
WardSeven-b3-desk.rbxl39,597 bytes · sha256 28cc3afd31d04bfc…opens in Studio
WardSeven-src.zip64,670 bytes · sha256 5311f5e1eb7867a6…2,812 lines + pipeline
rules and story139 passed, 0 failedpass
API surface282 names · self-test rejects a bogus class and propertypass
place file37 checks after deserialisationpass
server smoke146 checks · 8-night playthrough to an endingpass
client smoke40 checks · 71 instances · every server state renderedpass
playcheck6 markers read out of the published filepass
negative control: boot guard removedsuite reports the capability refusal and stopsfails as designed
negative control: old desk restoredsolidity + walk-line assertions failfails as designed

09Honest limits

What these tests do not prove

They do not prove it is frightening. A mock engine proves that a report resolves, that a desk is solid and that eight nights can be walked to an ending. It cannot prove the corridor is unnerving, that the fourth night's silence lands, or that the thing behind you reads as a threat rather than a pathing bug. That judgement needs a player, and the only player here is the one who said "theres no desk".

What is missing from the pipeline

There is no literal type check in Ward's six stages — the Neon Ledge pipeline has one, and it exists because Trail.WidthScale = NumberSequence.new(1.1) is a name-valid, type-invalid assignment that the API check happily passes. Ward gets away with it because the client smoke test renders every state, which catches most of the same class of error indirectly. It is a gap, not a design choice.

The stale-file trap that costs a whole cycle

Studio holds a .rbxl.lock for as long as the process lives — closing the window is not enough. Rebuilding the file underneath an open session silently changes nothing, and the player reports the old bug again. The build id is in the filename (b3-desk), the HUD stamp and the playcheck stage for exactly this reason: the file you opened and the build you think you have are two different questions.

10What's next

The play guide ward map · all 16 anomalies Download the place 39,597 bytes Sources 2,812 lines of Luau