[Phase 5.5 spirit] Spawn at look-point increments Population counter but no spirit sphere is visible in the rendered viewport #15

Closed
opened 2026-10-06 22:41:50 +00:00 by Mark · 1 comment
Owner

Summary

The Spirit entity (Phase 5.5: data/entity_defs/spirit.json, color #c8b4ff, shape sphere) successfully loads into the Creation Toolkit's entity registry — the detail panel shows the correct purple swatch and "Spirit — shape: sphere" label — and Spawn at look-point instantiates it server-side (Population counter incremented 137 → 265 in one click). But the spawned spirit is never visible in the rendered 3D viewport from the default camera. The admin can't see what they spawned.

Environment

  • Repo: Mark/veilwalkers
  • Build: debug (Godot 4.7.2)
  • Driven by: Computer Use MCP

Steps to Reproduce

  1. Boot, host, open the toolkit (F1).
  2. Click the Entities tab. Confirm "Spirit (spirit)" is in the entity list.
  3. Click Spirit (spirit) to select it. The detail panel below the list should show a purple swatch and "Spirit — shape: sphere".
  4. Click Spawn at look-point.
  5. The Population counter under the World tab increments (137 → 265 in one click confirms a real spawn happened).
  6. Press F1 to close the toolkit so the world is fully visible.
  7. Look for a purple/violet sphere in the 3D scene.

Expected

A new purple/violet sphere appears in the world, visible from the default camera. The spirit's look_point position is the camera's forward-ray hit on the ground plane, which from the default camera at (0, 0, 0) looking -Z should be in front of and slightly below the camera (e.g. world position (0, 0, -5) or so).

Actual

No purple/violet sphere is visible in the rendered viewport across multiple screenshots after the spawn. The world shows the same 4-5 sample entities (mana_crystal, reed_plant, wander_beasts) that were there before the spawn. The Population counter went up but the visual artifact of the spawn is missing.

Possible Causes (in order of likelihood)

  1. Look-point is behind the camera. If the camera is at (0, 0, 0) and the look-point math uses the world origin, the spirit may be spawning at (0, 0, 0) — inside the camera or behind it. Spawning at world origin would also collide with the existing mana_crystal at (0, 1, -6) and could be occluded by it.
  2. Spirit is being culled by the camera's frustum. If the spirit is spawned at a position outside the camera's near/far clip range, it won't render. Default Godot Camera3D has far = 4000 so this is unlikely unless overridden.
  3. The spirit's color is being applied to a MeshInstance3D whose material overrides the swatch color. If the spawn path uses a default material (e.g. white StandardMaterial3D), the spirit would render as white instead of purple. But it still should be visible.
  4. The spirit is spawning at a different scale than expected (very tiny or very large) and is effectively invisible. The spirit def likely has a scale field; check what the actual spawn uses.
  5. The spawn path doesn't actually call add_child() on the spirit's MeshInstance3D, so the instance exists in the population counter but is orphaned in the scene tree.

Why This Matters

  • Phase 5.5 is the spirit entity system. The whole point is that an admin can spawn a spirit and see it in the world. If the spawn happens but the visual doesn't, the feature is invisible to the user.
  • The user can't verify the spirit's behavior (manifest, decay, interactions) if they can't see the spirit in the first place.

Suggested Investigation Path

  1. Add a print(spirit.global_position) and print(spirit.scale) immediately after the spawn in the toolkit's spawn handler. Compare against the world camera position.
  2. Add a temporary MeshInstance3D debug sphere at the spawn position with a bright debug material, see if it shows up.
  3. Verify the spirit's def_id == "spirit" resolves to the correct entity_defs/spirit.json def at runtime (not the Phase 0 placeholder).
  4. If the spirit has a script field that the spawn path tries to attach and that script throws, the spawn may roll back the visual creation while still incrementing the population counter.
## Summary The **Spirit** entity (Phase 5.5: `data/entity_defs/spirit.json`, color `#c8b4ff`, shape `sphere`) successfully loads into the Creation Toolkit's entity registry — the detail panel shows the correct purple swatch and "Spirit — shape: sphere" label — and **Spawn at look-point** instantiates it server-side (Population counter incremented 137 → 265 in one click). But the spawned spirit is never visible in the rendered 3D viewport from the default camera. The admin can't see what they spawned. ## Environment - Repo: `Mark/veilwalkers` - Build: debug (Godot 4.7.2) - Driven by: Computer Use MCP ## Steps to Reproduce 1. Boot, host, open the toolkit (F1). 2. Click the **Entities** tab. Confirm "Spirit (spirit)" is in the entity list. 3. Click **Spirit (spirit)** to select it. The detail panel below the list should show a purple swatch and "Spirit — shape: sphere". 4. Click **Spawn at look-point**. 5. The **Population** counter under the World tab increments (137 → 265 in one click confirms a real spawn happened). 6. Press F1 to close the toolkit so the world is fully visible. 7. Look for a purple/violet sphere in the 3D scene. ## Expected A new purple/violet sphere appears in the world, visible from the default camera. The spirit's `look_point` position is the camera's forward-ray hit on the ground plane, which from the default camera at `(0, 0, 0)` looking `-Z` should be in front of and slightly below the camera (e.g. world position `(0, 0, -5)` or so). ## Actual **No purple/violet sphere is visible in the rendered viewport across multiple screenshots after the spawn.** The world shows the same 4-5 sample entities (mana_crystal, reed_plant, wander_beasts) that were there before the spawn. The Population counter went up but the visual artifact of the spawn is missing. ## Possible Causes (in order of likelihood) 1. **Look-point is behind the camera.** If the camera is at `(0, 0, 0)` and the look-point math uses the world origin, the spirit may be spawning at `(0, 0, 0)` — inside the camera or behind it. Spawning at world origin would also collide with the existing mana_crystal at `(0, 1, -6)` and could be occluded by it. 2. **Spirit is being culled by the camera's frustum.** If the spirit is spawned at a position outside the camera's near/far clip range, it won't render. Default Godot Camera3D has `far = 4000` so this is unlikely unless overridden. 3. **The spirit's `color` is being applied to a `MeshInstance3D` whose material overrides the swatch color.** If the spawn path uses a default material (e.g. white StandardMaterial3D), the spirit would render as white instead of purple. But it still should be visible. 4. **The spirit is spawning at a different scale than expected** (very tiny or very large) and is effectively invisible. The spirit def likely has a `scale` field; check what the actual spawn uses. 5. **The spawn path doesn't actually call `add_child()` on the spirit's MeshInstance3D**, so the instance exists in the population counter but is orphaned in the scene tree. ## Why This Matters - Phase 5.5 is the **spirit** entity system. The whole point is that an admin can spawn a spirit and see it in the world. If the spawn happens but the visual doesn't, the feature is invisible to the user. - The user can't verify the spirit's behavior (manifest, decay, interactions) if they can't see the spirit in the first place. ## Suggested Investigation Path 1. Add a `print(spirit.global_position)` and `print(spirit.scale)` immediately after the spawn in the toolkit's spawn handler. Compare against the world camera position. 2. Add a temporary `MeshInstance3D` debug sphere at the spawn position with a bright debug material, see if it shows up. 3. Verify the spirit's `def_id == "spirit"` resolves to the correct `entity_defs/spirit.json` def at runtime (not the Phase 0 placeholder). 4. If the spirit has a `script` field that the spawn path tries to attach and that script throws, the spawn may roll back the visual creation while still incrementing the population counter.
Mark closed this issue 2026-10-06 23:25:50 +00:00
Author
Owner

Closed by 7256c2b. Root cause: WorldEditCommands._apply_spawn ran EntityRegistry.spawn_entity which set is_manifested=false on the SpiritComponent (presence mode = no body per the SpiritComponent contract). The mesh existed at the correct position but the spirit had no body, so admins only saw the Population counter tick up.

Fix: after EntityRegistry.spawn_entity returns, _apply_spawn now sets is_manifested=true and current_power=max(1, current_power) on the spirit component when def_id == 'spirit', and tracks the entity in SpiritPopulation. The scripted summon_spirit() path still wins because it sets these explicitly after on_attach.

Tests: tests/unit/test_spirit_spawn_visible.tscn exercises the full path (controller.dispatch_edit → WorldEditCommands.apply → EntityRegistry.spawn_entity) and asserts is_manifested==true, current_power>=1, SpiritPopulation.count()==1, and (with --rendering-driver opengl3) counts purple pixels in the rendered viewport. All 8 unit tests still pass.

Closed by 7256c2b. Root cause: WorldEditCommands._apply_spawn ran EntityRegistry.spawn_entity which set is_manifested=false on the SpiritComponent (presence mode = no body per the SpiritComponent contract). The mesh existed at the correct position but the spirit had no body, so admins only saw the Population counter tick up. Fix: after EntityRegistry.spawn_entity returns, _apply_spawn now sets is_manifested=true and current_power=max(1, current_power) on the spirit component when def_id == 'spirit', and tracks the entity in SpiritPopulation. The scripted summon_spirit() path still wins because it sets these explicitly after on_attach. Tests: tests/unit/test_spirit_spawn_visible.tscn exercises the full path (controller.dispatch_edit → WorldEditCommands.apply → EntityRegistry.spawn_entity) and asserts is_manifested==true, current_power>=1, SpiritPopulation.count()==1, and (with --rendering-driver opengl3) counts purple pixels in the rendered viewport. All 8 unit tests still pass.
Sign in to join this conversation.
No description provided.