9.6 KiB
PC2 Test Level & Player Spawn — Design Note
Status: Decided (PC2) Owner: Architecture Consumers: the two PC2 implementation tasks — (1) build the test level, (2) build the player & spawn it. Both must be able to follow this note without further design decisions.
1. Decision: static scene, not chunk streaming
The PC2 playable test level is a single, hand-authored, static Godot 3D
scene. It is not built through the chunk streaming system
(scripts/chunk_stream_manager.gd, chunk_scene.gd, chunk.gd,
chunk_data_decoder.gd).
This is a deliberate decision, not a default:
- The streaming system has never been exercised with real content. It is a
faithful port of the
docs/architecture/world.mdformat spec, but nothing in the project produces or serves chunk payloads for it to consume.scenes/main.tscnwireschunk_stream_manager.gdonto a bareNode3Dwith noplayerreference and no_server_rpc, so at runtime it does nothing. There is no server-side chunk authoring/streaming endpoint to integrate against. - PC2 scope is single-player, local-only, no networking. The value of chunk streaming is server-authoritative streaming around a moving player across a network boundary. None of those preconditions exist in PC2, so exercising the pipeline here would mean building a chunk authoring + serving path first — clearly out of checkpoint scope.
- A static scene is the lowest-risk way to get "spawn a player in a real 3D level and walk around" working, which is the actual PC2 outcome being tested.
Chunk streaming is not removed and not broken by this decision — it stays as an optional, later milestone. The static level is the PC2 proof of the player and camera/physics half of the problem; chunk streaming remains the later proof of the world data half.
2. Where things live
| Artifact | Path | Owned by |
|---|---|---|
| Test level scene | scenes/worlds/test_level.tscn |
level task |
| Player scene | scenes/player.tscn |
player task |
| Player controller script | scripts/player.gd |
player task |
| Game composition scene | scenes/game.tscn |
player task |
scenes/worlds/is the convention for all level scenes (it does not exist yet; the level task creates it). Future levels followscenes/worlds/<name>.tscn.scenes/player.tscnis reusable across levels — it must not hard-code any reference totest_level; it discovers the spawn point at runtime (see §3).
3. Player spawn convention
The level scene must contain a Marker3D named exactly PlayerSpawn.
That node's world transform is the spawn point.
Marker3Dis a plainNode3Dthat shows a cross gizmo in the editor and carries no gameplay behavior — it is the established Godot idiom for "position hint." (Confirmed present in Godot 4.6.2.)- The marker's position is authoritative; its rotation is ignored by the player (spawn yaw is the player's concern, default forward is fine).
- Coordinate system: Y-up, units = meters, 1 unit = 1 m. The marker must sit
at least 1.0 m above the ground's top surface (recommended
y = 1.0where the ground top isy = 0) so the player capsule does not spawn intersecting the floor and get popped out by the physics solver. - There is exactly one
PlayerSpawnper level. (If a level ever needs multiple spawns, that is a later convention — not PC2.)
Lookup rule (implemented by player.gd, not by the level):
# in player.gd _ready()
var spawn := get_tree().current_scene.find_child("PlayerSpawn", true, false)
if spawn is Node3D:
global_position = spawn.global_position
else:
push_warning("player.gd: no PlayerSpawn marker found; spawning at origin")
global_position = Vector3(0, 1.0, 0) # fallback, do not crash
The lookup runs from the current scene root so it finds the marker whether it is
a direct child of the level instance or nested under a Level wrapper. The
fallback keeps the player runnable in a scene that forgot its marker, but a
missing PlayerSpawn is a level bug and the level task's smoke test must fail
on it (see §6).
4. Composition (who puts level + player together)
scenes/game.tscn is the PC2 playable entry point:
Game (Node3D)
├── Level (instance of res://scenes/worlds/test_level.tscn)
└── Player (instance of res://scenes/player.tscn)
Gameis a plainNode3Dwith no script — composition is done by scene instantiation, not code, so there is nothing to keep in sync.- The level scene is self-contained and has zero dependency on the player —
it loads and is smoke-testable on its own. The level task does not instance or
reference
player.tscn. - The player scene is self-contained and has zero dependency on any specific
level — it only requires that the current scene contains a
PlayerSpawn. - Ownership of
game.tscngoes to the player task, which depends on the level task having landedtest_level.tscnfirst (chain withdepends_on).
5. Level content requirements (level task)
scenes/worlds/test_level.tscn, root node TestLevel (Node3D). Required
children:
| Node | Type | Notes |
|---|---|---|
Ground |
StaticBody3D |
BoxShape3D (e.g. 40 × 1 × 40) centered at (0, -0.5, 0) so the top face is y = 0; matching MeshInstance3D (BoxMesh) so it is visible and collidable. Use a StaticBody3D rather than a bare PlaneMesh so collision is explicit and reliable. |
Props |
Node3D |
3–5 simple props as StaticBody3D + MeshInstance3D using built-in primitives only (BoxMesh, CylinderMesh, SphereMesh). assets/ is empty and PC2 must not require imported content. Positioned on the ground surface; scale freely. |
Sun |
DirectionalLight3D |
Pitch it downward (~ -45°), enable shadows if the compatibility renderer allows; shadows are optional. |
WorldEnvironment |
WorldEnvironment |
Ambient light and/or a simple background color. Keep it plain — the project uses gl_compatibility. |
PlayerSpawn |
Marker3D |
Per §3. Recommended (0, 1.0, 0). |
Notes:
- Do not add any
scriptto the level. It is pure content. Any per-level behavior belongs to the player (or a future game-mode script), not the level. - Keep the whole level on the XZ plane around the origin; nothing off at extreme coordinates, no thin walls the capsule can tunnel through.
6. Player contract (player task)
scenes/player.tscn, root Player (CharacterBody3D) + scripts/player.gd.
Minimum for PC2:
- Body:
CharacterBody3Dwith aCollisionShape3DusingCapsuleShape3D(~radius 0.3, ~height 1.8) and a matchingCapsuleMeshfor visibility. - Camera: a
Camera3Dchild. First-person (camera at capsule head height) is the lowest-risk choice; third-person is fine too but is more work. - Spawn: per §3 — read
PlayerSpawnfrom the current scene, fall back gracefully. - Movement: gravity +
move_and_slide()so the player stands onGround. WASD/arrow locomotion is the hard requirement. Mouse-look is recommended but may be split into its own task if the controller grows. - No networking, no avatar UI, no chunk code — PC2 is local-only. Do not add
MultiplayerAPI,ChunkStreamManager, or any avatar customization here.
The player must work when instanced under Game alongside a level; it must not
assume it is the only node in the scene.
7. Entry point / how a human plays it
scenes/game.tscnis the PC2 playable scene. For playtesting, load it directly (e.g.godot --path . scenes/game.tscn), or temporarily setapplication/run/main_sceneto it.- Wiring "Play → Join a world" in the existing
world_listUI is out of scope for this note and out of scope for the level/player tasks. The current menu flow ends at browse-worlds (PC1). If a checkpoint wants a clickable path into the test level, that navigation wiring is its own small task — do not fold it into level or player.
8. Validation (both tasks must run this before "done")
# one-time, first checkout only — builds the class/import cache
godot --headless --path . --import
# then, to catch parse/load errors:
godot --headless --path . --quit
- Level task: additionally load
test_level.tscnheadlessly and assert it (a) loads without error and (b) contains aMarker3DnamedPlayerSpawnpositioned ≥ 1.0 m above the ground top. A smoke script following the existingscripts/smoke_test_*.gdpattern is the right vehicle. - Player task: a smoke test that instantiates
game.tscn, waits a frame, and asserts the player's global position equals thePlayerSpawnposition (within a small epsilon). This is the check that proves the spawn convention actually wired through — not just "the scene loads."
9. Explicitly out of scope for PC2
- Networking / multiplayer / prediction (single-player, local-only).
- Chunk streaming integration and server-side chunk authoring/serving.
- Avatar customization UI and glTF avatar loading.
- Join-a-world navigation from the world list.
- Any content moderation / age-gating logic in the level.
10. Summary
| Concern | Decision |
|---|---|
| Level construction | Static hand-authored Godot 3D scene (chunk streaming deferred, not removed) |
| Level location | scenes/worlds/test_level.tscn |
| Spawn convention | Marker3D named PlayerSpawn, ≥ 1.0 m above ground, looked up by the player |
| Player | scenes/player.tscn (CharacterBody3D) + scripts/player.gd, reusable across levels |
| Composition | scenes/game.tscn instances Level + Player; no script |
| Entry point | Load scenes/game.tscn directly; UI join-wiring deferred |