SoulGlitch Tutorials Test 8: Multi-Bubble Discovery

Test 8: Multi-Bubble Discovery

Overview

Test 8 uses query_state to discover bubble waypoints on the current planet, then spawns a visual marker group at each bubble — one sphere on the bubble, one 5 m above, and a connector capsule between them.

Run

go run . 8

Exits after spawning (one-shot). Requires bubbles in the server state.

What it does

  1. query_state → read planet_center, planet_radius, bubbles[]
  2. For each bubble:
    • Compute up vector (bubble position − planet center, normalized)
    • Spawn construct bubble_group_{index} with three locked parts:
      • on_bubble — green sphere at bubble position
      • above_bubble — orange sphere 5 m along up
      • connector — gray capsule bridging the two

Protocol messages

Message Usage
query_state Bubble list + planet context
create_construct Three-part marker per bubble

Requirements

  • Host must populate bubbles in state_response
  • If zero bubbles: prints warning and exits

Source

test8.goRunTest8()

Go source

test8.go — run with go run . 8 from the repo root

package main

import (
	"encoding/json"
	"fmt"
	"math"
	"net"
	"time"
)

func RunTest8() {
	fmt.Println("🎈 Starting Test 8: Multi-Bubble Discovery & Spawning...")

	// 1. Connect and Query State
	conn, err := net.Dial("tcp", "localhost:17000")
	if err != nil {
		fmt.Printf("❌ Connection Failed: %v\n", err)
		return
	}
	defer conn.Close()

	// Send Query
	query := map[string]string{"type": "query_state"}
	qData, _ := json.Marshal(query)
	writePacket(conn, qData)

	// Read Response
	buf := make([]byte, 32768) // Large buffer for potentially many bubbles
	n, err := conn.Read(buf)
	if err != nil {
		fmt.Printf("❌ Read Failed: %v\n", err)
		return
	}

	var state StateResponse
	err = json.Unmarshal(buf[:n], &state)
	if err != nil {
		fmt.Printf("❌ Parse Status Failed: %v\nMessage: %s\n", err, string(buf[:n]))
		return
	}

	if state.Type != "state_response" {
		fmt.Printf("❌ Unexpected Response: %s\n", state.Type)
		return
	}

	fmt.Printf("✅ DISCOVERY SUCCESSFUL!\n")
	fmt.Printf("   🌍 Planet: %v (Radius: %.1f)\n", state.PlanetCenter, state.PlanetRadius)
	fmt.Printf("   🫧 Bubbles Found: %d\n", len(state.Bubbles))

	if len(state.Bubbles) == 0 {
		fmt.Println("⚠️ No bubbles found. Wait for planet initialization or move closer.")
		return
	}

	planetCenter := Vector3{state.PlanetCenter[0], state.PlanetCenter[1], state.PlanetCenter[2]}

	// 2. Iterate and Spawn on/above each bubble
	for _, bubble := range state.Bubbles {
		bubblePos := Vector3{bubble.Pos[0], bubble.Pos[1], bubble.Pos[2]}

		// Calculate Up vector (normal)
		dx := bubblePos[0] - planetCenter[0]
		dy := bubblePos[1] - planetCenter[1]
		dz := bubblePos[2] - planetCenter[2]
		dist := float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
		up := Vector3{dx / dist, dy / dist, dz / dist}

		fmt.Printf("📍 Spawning markers for Bubble #%d\n", bubble.Index)

		// Spawn "on" bubble and "above" bubble
		createReq := ConstructRequest{
			Type:        "create_construct",
			ConstructID: fmt.Sprintf("bubble_group_%d", bubble.Index),
			Parts: []Part{
				// Part 1: Sphere AT bubble position
				{
					ID: "on_bubble", Type: "sphere", Size: Vector3{1.5, 1.5, 1.5},
					Pos: bubblePos, Color: Vector3{0, 1, 0}, Locked: true,
				},
				// Part 2: Sphere 5m ABOVE bubble position
				{
					ID: "above_bubble", Type: "sphere", Size: Vector3{1.5, 1.5, 1.5},
					Pos: Vector3{
						bubblePos[0] + up[0]*5.0,
						bubblePos[1] + up[1]*5.0,
						bubblePos[2] + up[2]*5.0,
					},
					Color: Vector3{1, 0.5, 0}, Locked: true,
				},
				// Part 3: A connector beam
				{
					ID: "connector", Type: "capsule", Size: Vector3{0.3, 5, 0},
					Pos: Vector3{
						bubblePos[0] + up[0]*2.5,
						bubblePos[1] + up[1]*2.5,
						bubblePos[2] + up[2]*2.5,
					},
					Color: Vector3{0.5, 0.5, 0.5}, Locked: true,
				},
			},
		}

		cData, _ := json.Marshal(createReq)
		writePacket(conn, cData)

		// Small delay to not overwhelm the TCP buffer/server
		time.Sleep(50 * time.Millisecond)
	}

	fmt.Printf("✅ Spawning complete for %d bubble groups.\n", len(state.Bubbles))
	time.Sleep(1 * time.Second)
}