Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld e339ab22-c…

Player

Everything the player is and does. Attached to the player's own avatar (the `body` entity inside the player prototype, so every peer gets one).

by◐aiden·posted 1d ago
What it does

game.systems.Player

Everything the player is and does. Attached to the player's own avatar (the body entity inside the player prototype, so every peer gets one).

It owns the torch and its battery, sprint and crouch with stamina, health, what is carried, which clues have been read, how far along the objective chain the player is, and what happens when they press E at something. It publishes all of it as fields so the interface can draw it and the creatures can hear it.

What it does not own

The house owns the doors and the hiding places; this reads an Interactable marker on them and decides whether the player may pass. The run — objectives, clues, the ending — lives in game.systems.PlayerState and in the shared store, so a reload cannot lose it.

How it touches the engine

Two patterns worth keeping, both learned the hard way here:

  • Finding what the player can use goes through the scene.find tool, never queryEntitiesFlex: that function ignores a component filter and answers with every entity in the scene, so "nearest thing in front" becomes a wall.
  • Changing a field is a write onto the component object (comp.brightness = v). There is no component.set.
  • Reach is three-dimensional. The house is stacked — the living room sits under the attic — so a distance test on x/z alone lets a player take the attic key from the room below.

Fields

active, difficulty, health, stamina, battery, torchOn, hiding, objective, prompt, carrying, clues, ending — all read-only mirrors of the state, for the interface.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

game/systems/Player.component — everything the player IS and DOES. Attached to the player's own avatar. It owns the torch, stamina, health, the things carried, and what happens when the player presses a key at something — and it publishes all of it so the interface can draw it and the creatures can hear it. WHAT IT DOES NOT OWN. The house owns the doors and the hiding places; this reads an `Interactable` marker on them and decides whether the player may pass. The run — objectives, clues, the ending — lives in PlayerState and the shared store, not here, so a reload cannot lose it. SPRINTING IS THE CONTROLLER'S SPEED FIELDS, not a second mover. The character controller (the engine's own) already walks the body; stamina reaches it by moving `Locomotion.sprintSpeed` down to a walk the moment the bar empties, which is why running out of breath slows the player rather than being a number on a screen that means nothing.

setField(entityRef: any, componentType: string, values: { [string]: any }) → boolean

Changing a field on a component. There is no `component.set`: you read the component and write its field. Writing a field that does not exist is an error the engine reports, so this checks the read and reports the write rather than swallowing both — a torch whose brightness silently never changes is a bug nobody sees.

argtypedescription
entityRefany
componentTypestring
values{ [string]: any }

findAvatar( ) → void

findMyCamera( ) → void

The player's own camera, so the torch lights where they LOOK. The rig is a sibling of the body under the per-peer clone and there is more than one camera in the scene, so it is found by being the one nearest this avatar.

scanInteractables( ) → void

Interactables. Found by component, so one query gives every door, pickup, note, switch and hiding place in the house. Interactables, with their positions cached. Found with the `scene.find` TOOL, not `queryEntitiesFlex`: the latter ignores a component filter and answers with the whole population — 421 entities, cameras and staircase spindles among them — so "the nearest thing in front" becomes a wall. The tool filters properly and hands back each position, which is also 421 reads saved.

lookedAt( ) → void

What the player is looking at: near, and in front. The dot against the camera's forward is what makes "in front" mean the view rather than a radius, so a player is not offered a door behind them. The facing is the camera's own forward: `transform.forward` on the entity is its local -Z in world space, which is the direction the first-person controller aims. There is no `transform.direction(entity)` — that namespace's `direction` takes two POINTS (from and to) — so calling it with the camera threw, the pcall swallowed it, and the facing stayed at (0, -1): a player who could only ever reach what happened to be due north, wherever they were actually looking.

labelFor(row: any) → string

Using it.

argtypedescription
rowany

tryInteract( ) → void

publish( ) → void

publishShared( ) → void

The shared half of the state, so a second player sees the same door, the same key taken and the same objective.

attachTorch( ) → void

The torch: a spot light on the player's own camera, so it lights where they LOOK rather than where the body happens to face. IT HAS TO BE RETRIED. The avatar does not exist when this component starts — a player joins after the scene is already up — so attaching the light once in `awake` is a torch that nobody ever gets. This is called every frame until it succeeds.

awake( ) → void

update(dt: ?) → void

argtypedescription
dt?

onDestroy( ) → void

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

27+items
▣
module · born here
❒asset
# game.systems.PlayerState Everything the player *is*, as plain data: one table plus the rules that govern it. No entities, no input, no engine — so the survival numbers can be reasoned about on their own, and the interface reads the same values the simulation runs on rather than a copy that drifts. ```lua local PlayerState = require("game.systems.PlayerState") local state = PlayerState.new() ``` ## What is in it - **`new()`** — health, stamina, torch and battery, what is carried, clues, the objective index, the power, and whether the run has ended. - **Movement** — `spendStamina`, `canStartSprint`. - **The torch** — `useTorch`, `torchLow`, `replaceBattery`. - **Carrying** — `has`, `give` (a unique item already taken is not taken again, which is what stops two peers pocketing the same key), `addClue`, `clueCount`, `requiredCluesFound`. - **Damage** — `damage` (returns true when it is fatal), `heal`. - **The objective chain** — `objectiveMet(state, step)` says what each step waits for, and `syncObjective` walks the chain forward as far as the player has actually got. It only ever moves on. - **`ending(state)`** — which ending the run earned, from the clues found and whether the secret room was opened. ## The co-op split `toShared` and `applyShared` state, once, what the whole session agrees on — a door, a key, an objective, the power — against what is only yours: your stamina, your health, your torch. Mixing the two is how a co-op game desynchronises, so the split is a function rather than a judgement at each call site.
▲ 0↑ born
·
inputmap · born here
❒asset
▲ 0↑ born
◇
component · born here
❒asset
# game.systems.Interactable A thing in the house the player can use. **It marks the spot, it does not behave.** It carries what a thing *is* — a door, a pickup, a note, a switch, a place to hide, the way out — and what it is worth; the `Player` component reads it, decides whether the player may have it, and does it. So the house never has to know about keys, and the keys never have to know about hinges. The scene build attaches one to every door leaf and hiding place the house put down, to the front door, and spawns the pickups, notes and the power switch with one already on them. That is what lets a single query find everything the player could be looking at, of every kind. ## Fields | Field | What it is | |---|---| | `kind` | `"door"`, `"pickup"`, `"note"`, `"switch"`, `"hide"` or `"exit"`. | | `ref` | What it is: a door id, an item id, a clue id, or a switch's id. | | `lockedBy` | The `GameRules.ITEMS` id that opens it. Empty means never locked. | | `label` | What the player reads when they look at it. | | `range` | How close the player has to be. | | `spent`, `taken` | A switch already thrown, a pickup already taken. |
▲ 0↑ born
▣
module · born here
❒asset
# game.Bus The one table the game's parts talk over. **Every slice announces what happened and listens for what it cares about; no slice requires another**, and this is the table in the middle they share. ```lua local Bus = require("game.Bus") Bus.emit(Bus.E.noise, { id = "sprint", position = p, source = "player" }) local handle = Bus.on(Bus.E.playerCaught, function(payload) print(payload.monster) -- "resident" end) Bus.off(Bus.E.playerCaught, handle) ``` `Bus.E` is the event-name list, spelled once so no slice can invent a string nobody else listens for. It is the same set as `GameRules.EVENTS`, which also documents the payload each event carries; the scene entrypoint checks the two agree on every load and warns if they have drifted. `on`, `once`, `off`, `emit`, `count`, `clear`, plus `error()` and `clearError()` for the last handler failure. ## Two rules, because getting them wrong desynchronises a co-op run 1. **The bus is local.** It is a module, and a module is one instance *per peer*. An emit reaches the parts of the game running on this machine and nowhere else. Anything another player must agree about — a door, a key, the objective list, the monster — is written to the **shared store** (`runtime_data.world`, under the keys in `GameRules.DATA`) and read back by every peer. Emit the local *response* to a thing; store the *fact* of it. A part that emits `doorOpened` and never writes the store has a door that opens for one player and stays shut for everyone else. 2. **An emit is synchronous.** Handlers run before `emit` returns, in subscription order, on the frame the thing happened. The payload table is handed out, not copied — read it, do not keep it, do not mutate it. A handler that throws is caught and reported, so one broken listener cannot silence every other listener on the same event; `Bus.error()` reads the last one back. Who calls it: every slice. The house emits doors and pickups, the monsters emit their state and a sighting, the survival systems emit noise, damage and clues, the interface listens to most of it, and the audio director listens to nearly all of it.
▲ 0↑ born
▣
module · born here
❒asset
# game.GameRules The constants and rules of **The House That Knows You** — the one module every part of the game reads, so two systems written in different sessions agree on what a room is called, how loud a sprint is, how long a torch battery lasts and which ending the player earned. It holds **no behaviour and no entities**: pure data plus a handful of pure functions. Require it and read it; never write to it. ```lua local RULES = require("game.GameRules") ``` ## What is in it - **`ART_DIRECTION`** — the one sentence every generation prompt reuses word for word, and **`REFERENCES`** — the pictures the game is judged against. - **`PALETTE`** — that sentence as numbers, for the interface and for materials. - **`HOUSE`** — the wall height, the wall thickness, the doorway size, the room module, the storey height and the player's own dimensions. Every prop in the game is sized against these. - **`AREAS`** / **`AREA_BY_ID`** — the twelve areas, each with its floor, its nominal centre on the ground plan and the item that unlocks it. - **`OBJECTIVES`** — the twelve-step chain, in order, each naming the area it happens in and the item it needs. - **`ITEMS`**, **`REQUIRED_CLUES`**, **`CLUE_COUNT_FOR_TRUTH`** — what can be picked up, and what the ending is decided on. - **`ENDINGS`** and **`selectEnding(cluesFound, secretRoomOpened)`** — the three endings and the rule that picks one, stated once so the interface and the systems cannot disagree. - **`SURVIVAL`** — speeds, stamina, health, torch battery, interaction ranges. - **`DIFFICULTY`** — easy, normal, nightmare. These change the monster's persistence, reach and the supply density **only**; all three run the same monster logic. - **`MONSTERS`** and **`MONSTER_STATES`** — THE RESIDENT and THE BROODMOTHER, and the states both run: patrol, investigate, chase, search, attack, recover. - **`NOISE`** — the interface between what a player *does* and what a monster *hears*. A system emits a noise id; the monster AI turns it into a reach. Nothing else may write a raw radius. - **`SPIDERS`** — the baby-spider event: five presses, six seconds, the slow-motion tutorial, the prompts, the damage. - **`ENDLESS`** / **`endlessDifficultyFor(floor)`** — Endless Nightmare, and a floor number that never stops climbing. - **`MODES`**, **`MAX_PLAYERS`**, **`DATA`** — the game modes and the keys shared state lives under. ## Who calls it Every slice: the house builder, both monsters, the survival systems, the interface and the audio director. Every component that runs in play reads its numbers from here rather than carrying its own copy.
▲ 0↑ born
·
inputbinding · born here
❒asset
▲ 0↑ born
·
inputbinding · born here
❒asset
▲ 0↑ born
·
inputbinding · born here
❒asset
▲ 0↑ born
·
inputbinding · born here
❒asset
▲ 0↑ born
·
inputbinding · born here
❒asset
▲ 0↑ born
backing path · game/systems/Player.component

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.