diff --git a/docs/ENTITY_TROUBLESHOOTING.md b/docs/ENTITY_TROUBLESHOOTING.md new file mode 100644 index 0000000..fd8e2fc --- /dev/null +++ b/docs/ENTITY_TROUBLESHOOTING.md @@ -0,0 +1,302 @@ +# Entity Troubleshooting Guide + +## Overview + +This guide helps diagnose issues with entity rendering and reception in Starworld. + +## Debug Logging + +Starworld provides comprehensive debug logging controlled by environment variables. Enable these to diagnose entity-related issues: + +### Available Debug Flags + +```bash +# Enable entity packet debugging (shows packet structure and content) +export STARWORLD_DEBUG_ENTITY_PACKETS=1 + +# Enable entity lifecycle tracking (shows creation/update/deletion) +export STARWORLD_DEBUG_ENTITY_LIFECYCLE=1 + +# Enable general network packet debugging +export STARWORLD_DEBUG_NETWORK=1 +``` + +### Example Usage + +```bash +# Enable all debugging for maximum visibility +export STARWORLD_DEBUG_ENTITY_PACKETS=1 +export STARWORLD_DEBUG_ENTITY_LIFECYCLE=1 +export STARWORLD_DEBUG_NETWORK=1 + +# Run with authentication +./build/starworld --auth --overte=127.0.0.1:40104 + +# Or run in simulation mode to test rendering +export STARWORLD_SIMULATE=1 +./build/starworld +``` + +## Common Issues + +### 1. Entities Not Appearing + +**Symptoms:** +- Domain connection succeeds +- DomainList packet received +- No EntityData packets received +- No entities visible in VR + +**Diagnostic Steps:** + +1. **Enable entity debugging:** + ```bash + export STARWORLD_DEBUG_ENTITY_PACKETS=1 + export STARWORLD_DEBUG_ENTITY_LIFECYCLE=1 + ./build/starworld --overte=127.0.0.1:40104 + ``` + +2. **Check EntityQuery transmission:** + Look for log lines like: + ``` + [OverteClient] Sent EntityQuery to entity-server (192.168.1.100:40102, 35 bytes, seq=5) + ``` + +3. **Verify EntityServer address:** + The DomainList reply should contain an EntityServer entry: + ``` + [OverteClient] Assignment client 0: type=0 (EntityServer) + ``` + +4. **Check for EntityData packets:** + Look for: + ``` + [OverteClient] Received EntityData packet (XXX bytes) + [OverteClient] Entity added: EntityName (id=12345) + ``` + +**Common Causes:** + +- **No EntityServer running:** Domain server may not have an entity server configured +- **Anonymous connection limitation:** Some domains restrict entity data for anonymous users +- **Network/firewall issues:** EntityServer may be on a different port/address that's blocked +- **HMAC verification issue:** Server rejecting sourced packets (see NETWORK_PROTOCOL_INVESTIGATION.md) + +**Solutions:** + +- Try authenticated connection: `./build/starworld --auth` +- Verify domain server has entity-server running +- Check firewall rules for UDP port (typically domain UDP port) +- Test with simulation mode to verify rendering works: `export STARWORLD_SIMULATE=1` + +### 2. Entities Received But Not Rendered + +**Symptoms:** +- EntityData packets received and logged +- Entity count increases +- No visual models appear in VR + +**Diagnostic Steps:** + +1. **Check entity counts:** + Look for lifecycle logging: + ``` + [OverteClient/Lifecycle] Total entities: 5, Update queue: 5 + ``` + +2. **Verify entity properties:** + With `STARWORLD_DEBUG_ENTITY_LIFECYCLE=1`: + ``` + [OverteClient] Entity added: Chair (id=12345) + Type: 3 + Position: (1.5, 0.0, -2.0) + Dimensions: (0.5, 0.5, 0.5) + Model: https://example.com/models/chair.glb + ``` + +3. **Check for zero dimensions:** + Entities with zero dimensions are skipped: + ``` + [bridge/reify] Skipping node 12345 (zero dimensions) + ``` + +4. **Verify model loading:** + Check bridge logs for model loading: + ``` + [bridge/reify] Loading 3D model for node 12345 from URL: ... + [bridge/reify] Using downloaded model: /home/user/.cache/starworld/models/abc123.glb + ``` + +**Common Causes:** + +- **Zero dimensions:** Entity has dimensions (0, 0, 0) +- **Missing primitive models:** Cache directory `~/.cache/starworld/primitives/` is empty +- **Model download failure:** HTTP download failed or URL is invalid +- **Stardust bridge not loaded:** Bridge library failed to load or initialize + +**Solutions:** + +- Generate primitive models: + ```bash + blender --background --python tools/blender_export_simple.py + ``` + +- Check cache directories exist: + ```bash + ls -la ~/.cache/starworld/primitives/ + ls -la ~/.cache/starworld/models/ + ``` + +- Verify bridge is loaded: + ``` + [StardustBridge] Rust bridge present: libstardust_bridge.so + ``` + +### 3. Connection Drops After 11-18 Seconds + +**Symptoms:** +- Initial connection succeeds +- DomainList received +- Connection killed after 11-18 seconds +- Error: "Node killed by domain server (silent node)" + +**Cause:** +This is a known HMAC verification issue on the server side. See [docs/NETWORK_PROTOCOL_INVESTIGATION.md](NETWORK_PROTOCOL_INVESTIGATION.md) for detailed analysis. + +**Workaround:** +Currently under investigation. The client implementation is correct; the issue is server-side configuration. + +### 4. Entity Colors Not Applied + +**Symptoms:** +- Entities appear with default/white color +- Entity color logged but not visible + +**Cause:** +Color tinting is not yet implemented. The StardustXR asteroids Model element doesn't currently expose material manipulation APIs. + +**Current State:** +- Entity colors are parsed from packets ✅ +- Colors stored in entity structure ✅ +- Colors propagated to Rust bridge ✅ +- Colors logged in debug mode ✅ +- Colors applied to model materials ❌ (TODO) + +**Expected Logs:** +``` +[bridge/reify] Node 12345 has color tint: RGBA(1.00, 0.00, 0.00, 1.00) - NOT YET APPLIED +``` + +### 5. Textures Not Applied + +**Symptoms:** +- Entities show model geometry but no textures +- Texture URL logged but not visible + +**Current State:** +- Texture URLs parsed from packets ✅ +- Texture download system implemented ✅ +- HTTP/HTTPS texture downloads cached ✅ +- Textures passed to bridge ✅ +- Textures applied to materials ❌ (Depends on StardustXR API) + +**Expected Logs:** +``` +[StardustBridge] Texture downloaded: https://example.com/texture.jpg -> /home/user/.cache/starworld/models/xyz789.jpg +[bridge/reify] Node 12345 has texture URL: /home/user/.cache/starworld/models/xyz789.jpg - NOT YET APPLIED +``` + +## Testing Entity Rendering + +### Simulation Mode + +Test entity rendering without connecting to a server: + +```bash +export STARWORLD_SIMULATE=1 +export STARWORLD_BRIDGE_PATH=/home/runner/work/Starworld/Starworld/bridge/target/release +./build/starworld +``` + +This creates three test entities: +- Red cube (Box type, 0.2m) +- Green sphere (Sphere type, 0.15m) +- Blue suzanne (Model type, 0.25m) + +### Unit Tests + +Run the test harness to verify packet parsing: + +```bash +cd build +./starworld-tests +``` + +Expected output: +``` +[TEST] Protocol signature hex=eb1600e798dc5e03c755a968dc16b7fc +[TEST] Entity packet structure: 75 bytes +ALL TESTS PASS +``` + +## Log Analysis + +### Successful Entity Reception + +``` +[OverteClient] Connected to domain server +[OverteClient] DomainList received +[OverteClient] Assignment client 0: type=0 (EntityServer) at 192.168.1.100:40102 +[OverteClient] Sent EntityQuery to entity-server (192.168.1.100:40102, 35 bytes, seq=3) +[OverteClient] Received EntityData packet (523 bytes) +[OverteClient] Entity added: Chair (id=12345) +[OverteClient/Lifecycle] Total entities: 1, Update queue: 1 +[bridge/reify] Reifying 1 nodes +[bridge/reify] Loading 3D model for node 12345 from URL: https://example.com/chair.glb +[StardustBridge] Model downloaded: https://example.com/chair.glb -> /home/user/.cache/starworld/models/abc123.glb +``` + +### Failed Entity Reception + +``` +[OverteClient] Connected to domain server +[OverteClient] DomainList received +[OverteClient] No EntityServer found in assignment clients +[OverteClient] Sent EntityQuery to domain-server (192.168.1.100:40104, 35 bytes, seq=3) +[OverteClient] (No EntityData packets received) +[OverteClient/Lifecycle] Total entities: 0, Update queue: 0 +``` + +## Environment Variables Reference + +| Variable | Default | Description | +|----------|---------|-------------| +| `STARWORLD_SIMULATE` | `0` | Enable simulation mode (1=on) | +| `STARWORLD_DEBUG_ENTITY_PACKETS` | `0` | Log entity packet contents | +| `STARWORLD_DEBUG_ENTITY_LIFECYCLE` | `0` | Log entity creation/updates | +| `STARWORLD_DEBUG_NETWORK` | `0` | Log all network packets | +| `STARWORLD_BRIDGE_PATH` | `./bridge/target/debug` | Path to Rust bridge library | +| `OVERTE_UDP_PORT` | from URL | Override UDP domain server port | + +## Getting Help + +If you encounter issues not covered here: + +1. Enable all debug logging +2. Capture full output to a file: + ```bash + export STARWORLD_DEBUG_ENTITY_PACKETS=1 + export STARWORLD_DEBUG_ENTITY_LIFECYCLE=1 + ./build/starworld --overte=127.0.0.1:40104 2>&1 | tee debug.log + ``` + +3. Check existing documentation: + - [NETWORK_PROTOCOL_INVESTIGATION.md](NETWORK_PROTOCOL_INVESTIGATION.md) - Connection issues + - [ENTITY_RENDERING_ENHANCEMENTS.md](ENTITY_RENDERING_ENHANCEMENTS.md) - Rendering details + - [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md) - Build and development + +4. File an issue with: + - Debug log output + - Domain server address/version + - Expected vs actual behavior + - Steps to reproduce diff --git a/src/StardustBridge.cpp b/src/StardustBridge.cpp index cb50e18..4de8e0b 100644 --- a/src/StardustBridge.cpp +++ b/src/StardustBridge.cpp @@ -229,6 +229,33 @@ bool StardustBridge::setNodeModel(NodeId id, const std::string& modelUrl) { bool StardustBridge::setNodeTexture(NodeId id, const std::string& textureUrl) { auto it = m_nodes.find(id); if (it == m_nodes.end()) return false; + + // Check if URL is HTTP(S) - if so, download via ModelCache (also works for textures!) + if (textureUrl.substr(0, 7) == "http://" || textureUrl.substr(0, 8) == "https://") { + // Request download from ModelCache (cache handles all file types) + ModelCache::instance().requestModel( + textureUrl, + [this, id](const std::string& url, bool success, const std::string& localPath) { + if (success && m_fnSetTexture) { + std::cout << "[StardustBridge] Texture downloaded: " << url << " -> " << localPath << std::endl; + m_fnSetTexture(id, localPath.c_str()); + } else if (!success) { + std::cerr << "[StardustBridge] Failed to download texture: " << url << std::endl; + } + }, + [id](const std::string& url, size_t bytesReceived, size_t bytesTotal) { + // Optional: log download progress + if (bytesTotal > 0 && bytesReceived % 1024 == 0) { // Log every 1KB to reduce spam + float percent = (bytesReceived * 100.0f) / bytesTotal; + std::cout << "[StardustBridge] Downloading texture for node " << id << ": " + << percent << "% (" << bytesReceived << "/" << bytesTotal << " bytes)" << std::endl; + } + } + ); + return true; // Download initiated, will complete asynchronously + } + + // Direct URL (file://, data:, etc.) - pass through to bridge if (m_fnSetTexture) { return m_fnSetTexture(id, textureUrl.c_str()) == 0; } diff --git a/tests/TestHarness.cpp b/tests/TestHarness.cpp index 7da87df..d17bb65 100644 --- a/tests/TestHarness.cpp +++ b/tests/TestHarness.cpp @@ -92,6 +92,88 @@ int main(){ } } + // Test 4: Entity packet structure validation + { + // Simulate a simple EntityAdd packet structure: + // [type:u8][id:u64][name:null-terminated][position:3xf32][rotation:4xf32][dimensions:3xf32][model_url:null-terminated][texture_url:null-terminated][color:3xf32][entity_type:u8] + std::vector entityPacket; + + // Packet type (0x10 = ENTITY_ADD) + entityPacket.push_back(0x10); + + // Entity ID (uint64): 12345 + uint64_t entityId = 12345; + for (int i = 0; i < 8; i++) { + entityPacket.push_back((entityId >> (i * 8)) & 0xFF); + } + + // Name: "TestEntity\0" + std::string name = "TestEntity"; + entityPacket.insert(entityPacket.end(), name.begin(), name.end()); + entityPacket.push_back(0); // null terminator + + // Position: (1.0, 2.0, 3.0) as 3 floats + float pos[3] = {1.0f, 2.0f, 3.0f}; + for (int i = 0; i < 3; i++) { + uint8_t* bytes = reinterpret_cast(&pos[i]); + entityPacket.insert(entityPacket.end(), bytes, bytes + 4); + } + + // Rotation: identity quaternion (0,0,0,1) as 4 floats + float rot[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + for (int i = 0; i < 4; i++) { + uint8_t* bytes = reinterpret_cast(&rot[i]); + entityPacket.insert(entityPacket.end(), bytes, bytes + 4); + } + + // Dimensions: (0.5, 0.5, 0.5) as 3 floats + float dims[3] = {0.5f, 0.5f, 0.5f}; + for (int i = 0; i < 3; i++) { + uint8_t* bytes = reinterpret_cast(&dims[i]); + entityPacket.insert(entityPacket.end(), bytes, bytes + 4); + } + + // Model URL: empty + entityPacket.push_back(0); + + // Texture URL: empty + entityPacket.push_back(0); + + // Color: (1.0, 0.0, 0.0) red as 3 floats + float color[3] = {1.0f, 0.0f, 0.0f}; + for (int i = 0; i < 3; i++) { + uint8_t* bytes = reinterpret_cast(&color[i]); + entityPacket.insert(entityPacket.end(), bytes, bytes + 4); + } + + // Entity type: 1 (Box) + entityPacket.push_back(1); + + std::cout << "[TEST] Entity packet structure: " << entityPacket.size() << " bytes" << std::endl; + + // Validate minimum size + size_t minExpectedSize = 1 + 8 + 11 + 12 + 16 + 12 + 1 + 1 + 12 + 1; // = 75 bytes + if (entityPacket.size() != minExpectedSize) { + std::cerr << "[FAIL] Entity packet size mismatch: got " << entityPacket.size() + << " expected " << minExpectedSize << "\n"; + ++failures; + } + + // Validate packet type + if (entityPacket[0] != 0x10) { + std::cerr << "[FAIL] Entity packet type mismatch\n"; + ++failures; + } + + // Validate entity ID + uint64_t readId = 0; + std::memcpy(&readId, &entityPacket[1], 8); + if (readId != entityId) { + std::cerr << "[FAIL] Entity ID mismatch: got " << readId << " expected " << entityId << "\n"; + ++failures; + } + } + if (failures == 0) { std::cout << "ALL TESTS PASS" << std::endl; return 0;