Key Placement

The key words MUST, MUST NOT, SHOULD, and MAY are used as in RFC 2119.

This page governs where in its room a key pickup stands and when it appears. It does not govern which rooms hold keys or how many — that is the gate economy's scatter rule (Field Gate Economy): BFS depth < 2 from the gated room, at most 2 per room, dead ends first. The three compose: the economy picks the room and the count, this page picks the spot and the moment.

The rule

The original authors where a key sits: 836 o0c_key records over 224 of the 295 reference rooms, 2–6 distinct positions each, decoded by psz-re from object_placement_per_room.json and carried in room_reference.json. A generated field's keys MUST stand on those slots.

psz-godot previously placed the fallback key at the centroid of the cell's portal spawn points and ringed extra copies around it. That is refuted: the centroid has no relationship to where the room's floor is wide enough, which is why keys hover over voids (#627). A centroid is an average of doorways, not a placement.

When the key appears

The original puts keys on the ground when the room loads. psz-godot diverges deliberately (#639): a key MUST materialise when its room is cleared — the last enemy defeated — standing on the spot this page's resolution order picks. The authored slots decide where; the fight decides when.

The rationale is the same playtest line that removed the per-room treasure boxes: a key standing on the floor at entry is a rote pickup — walk over, collect, continue — and in a dead end it means walking back out with nothing gained. Dropping the key from the fight deletes the rote interaction and lets combat deliver the reward: dead end visible, fight, key drops, the gate ahead opens. That is an exploration reward structure, not a participation trophy. It is an intentional divergence from the RE-measured original, recorded here rather than taken silently.

Because a gate's solvability hangs on the key existing, the timing carries hard rules:

Resolution order

CaseBehaviour
Cell carries an authored key_positionThat position wins — the quest editor spoke, and generated cells never carry the field.
Generated cell with authored key slotsKeys MUST stand on the room's mask-filtered key slots, chosen as below.
Cell holds more keys than the room has distinct slotsSlots MUST be reused (modulo wrap) rather than keys dropped — the gate demands them — and each reused copy MUST be ringed apart so pickups stay separately clickable.
Room code has no authored slotsFall back to the portal centroid. This is a fallback for a room code the reference never covered, not a placement policy.

The mask filter

Authored key records are group-tagged like every other room object: a key exists only in the layout masks that build its group. Faithful placement MUST filter slots by the layout mask the room instance actually drew — the same draw that selected the room's objects (_pick_layout) — so a key never stands in a group the room did not build. One draw per room instance, shared by objects and keys; the selection MUST NOT draw twice.

Two consequences fall out of the data and are behaviour, not accidents:

When the mask leaves nothing

88 of the 224 key rooms carry slots in group 0 only, and mask 40 — layout 3 — does not build group 0. Under that draw the original would place no key in that room at all. But which rooms hold keys is not the reference table's decision: it is the gate economy's, already committed before placement runs. So when the drawn mask leaves a room no eligible slot, the mask MUST be set aside and all of the room's authored slots used — the key exists regardless, and an authored spot from another layout of the same room beats a doorway centroid over a void. The filter narrows where; it must not unplace whether.

Positions, not objects

room_reference.json says its objects never reach the game, because an unhandled kind would still eat a slot against the 20-object room cap. Keys are the second exception after enemy slots, and the argument is the same with one honest addition: a key is not a passive spawn point — it occupies interactivity, a thing the player picks up. So the split is stated rather than blurred:

Coordinates and height

Slots are room-local, same units and orientation as authored objects. Every authored key record carries y = 0, so the pickup MUST NOT trust the authored height: it floor-snaps at the slot's (x, z) and hovers a fixed amount above whatever floor it finds — the same contract boxes already follow. A key buried under a raised floor is an unopenable gate, which is a soft lock, not a cosmetic bug.

Seeding

Which subset of a room's eligible slots a cell uses is a seeded choice made at field generation, so a given field populates identically on every revisit rather than reshuffling under an uncollected key.

What this page does not settle

Which slot the original picks for a given room instance is not known — the same undecoded per-room seed and placement weights as Enemy Placement's subset note. The positions are authored data and are exact; the selection among them is ours. Which rooms hold keys is likewise ours-by-measurement (the economy's scatter rule), not the reference table's.