Files
modbison/docs/architecture/pc2_test_level.md

9.6 KiB
Raw Permalink Blame History

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.md format spec, but nothing in the project produces or serves chunk payloads for it to consume. scenes/main.tscn wires chunk_stream_manager.gd onto a bare Node3D with no player reference 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 follow scenes/worlds/<name>.tscn.
  • scenes/player.tscn is reusable across levels — it must not hard-code any reference to test_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.

  • Marker3D is a plain Node3D that 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.0 where the ground top is y = 0) so the player capsule does not spawn intersecting the floor and get popped out by the physics solver.
  • There is exactly one PlayerSpawn per 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)
  • Game is a plain Node3D with 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.tscn goes to the player task, which depends on the level task having landed test_level.tscn first (chain with depends_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 script to 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: CharacterBody3D with a CollisionShape3D using CapsuleShape3D (~radius 0.3, ~height 1.8) and a matching CapsuleMesh for visibility.
  • Camera: a Camera3D child. 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 PlayerSpawn from the current scene, fall back gracefully.
  • Movement: gravity + move_and_slide() so the player stands on Ground. 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.tscn is the PC2 playable scene. For playtesting, load it directly (e.g. godot --path . scenes/game.tscn), or temporarily set application/run/main_scene to it.
  • Wiring "Play → Join a world" in the existing world_list UI 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.tscn headlessly and assert it (a) loads without error and (b) contains a Marker3D named PlayerSpawn positioned ≥ 1.0 m above the ground top. A smoke script following the existing scripts/smoke_test_*.gd pattern is the right vehicle.
  • Player task: a smoke test that instantiates game.tscn, waits a frame, and asserts the player's global position equals the PlayerSpawn position (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