Test 2: Multi-Client Constructs
Overview
Test 2 stress-tests multiple simultaneous TCP clients, each owning its own construct. Four goroutines connect in parallel and run independent animation loops for ~60 seconds.
Run
go run . 2
What it does
Spawns four constructs near the origin, each on its own TCP connection:
| Client |
Construct |
Behavior |
| SnakeBot 1 |
Pin-jointed snake (sphere head + 8 box segments) |
Head bob + segment wave torques |
| BoxTower 1 |
Stacked boxes on locked base |
Periodic sideways torque on middle block |
| Jellyfish 1 |
Locked bell + 6 pin-jointed tentacle chains |
Radial pulsing tentacle torques |
| SnakeBot 2 |
Second snake at a different offset |
Same as SnakeBot 1 |
Protocol messages
| Message |
Usage |
create_construct |
One per client connection |
update_construct |
Per-client animation loop (30–500 ms tick rates) |
Highlights
- Demonstrates one construct per connection (typical server model)
- Mix of pin joints, locked parts, and torque-driven motion
- Uses
sync.WaitGroup to wait for all clients to finish
Source
test2.go — RunTest2(), StartSnakeBot(), StartBoxTower(), StartJellyfish()
Go source
test2.go — run with go run . 2 from the repo root
package main
import (
"encoding/json"
"fmt"
"math"
"net"
"sync"
"time"
)
// --- Generators ---
func StartSnakeBot(wg *sync.WaitGroup, id int, offset Vector3) {
defer wg.Done()
conn, err := net.Dial("tcp", "localhost:17000")
if err != nil {
fmt.Printf("❌ [SnakeBot-%d] Failed to connect: %v\n", id, err)
return
}
defer conn.Close()
constructID := fmt.Sprintf("snake_bot_%d", id)
fmt.Printf("🐍 [SnakeBot-%d] Connected. Creating construct...\n", id)
parts := []Part{}
joints := []Joint{}
segments := 8
// Head
parts = append(parts, Part{
ID: "head", Type: "sphere", Size: Vector3{0.4, 0.4, 0.4},
Pos: Vector3{offset[0], offset[1] + 2, offset[2]},
Color: Vector3{0.2, 0.8, 0.2}, Groups: []string{"lasso_target"},
})
for i := 0; i < segments; i++ {
z := offset[2] - float32(i+1)*0.7
pid := fmt.Sprintf("seg_%d", i)
parts = append(parts, Part{
ID: pid, Type: "box", Size: Vector3{0.5, 0.5, 0.5},
Pos: Vector3{offset[0], offset[1] + 2, z},
Color: Vector3{0.2, float32(i) * 0.1, 0.2}, Groups: []string{"lasso_target"},
})
prev := "head"
if i > 0 {
prev = fmt.Sprintf("seg_%d", i-1)
}
joints = append(joints, Joint{
Type: "pin", A: prev, B: pid,
Pos: Vector3{offset[0], offset[1] + 2, z + 0.35},
})
}
req := ConstructRequest{Type: "create_construct", ConstructID: constructID, Parts: parts, Joints: joints}
data, _ := json.Marshal(req)
writePacket(conn, data)
// Animate
fmt.Printf("🐍 [SnakeBot-%d] Slithering...\n", id)
ticker := time.NewTicker(30 * time.Millisecond)
defer ticker.Stop()
startTime := time.Now()
for range ticker.C {
elapsed := float32(time.Since(startTime).Seconds())
if elapsed > 60 {
break
}
updates := []PartUpdate{}
// Make head bob
torque := Vector3{float32(math.Sin(float64(elapsed*3))) * 10, 0, 0}
updates = append(updates, PartUpdate{PartID: "head", Torque: &torque})
// Wave segments
for i := 0; i < segments; i++ {
pid := fmt.Sprintf("seg_%d", i)
wave := float32(math.Sin(float64(elapsed*5+float32(i)))) * 15
t := Vector3{0, wave, 0}
updates = append(updates, PartUpdate{PartID: pid, Torque: &t})
}
uReq := UpdateRequest{Type: "update_construct", ConstructID: constructID, Updates: updates}
uData, _ := json.Marshal(uReq)
writePacket(conn, uData)
}
}
func StartBoxTower(wg *sync.WaitGroup, id int, offset Vector3) {
defer wg.Done()
conn, err := net.Dial("tcp", "localhost:17000")
if err != nil {
fmt.Printf("❌ [BoxTower-%d] Failed to connect: %v\n", id, err)
return
}
defer conn.Close()
constructID := fmt.Sprintf("box_tower_%d", id)
fmt.Printf("🗼 [BoxTower-%d] Building tower...\n", id)
parts := []Part{}
joints := []Joint{} // No joints, just stacking physics objects (if we set them not locked)
// Base (Locked)
parts = append(parts, Part{
ID: "base", Type: "box", Size: Vector3{2, 0.5, 2},
Pos: Vector3{offset[0], offset[1], offset[2]},
Locked: true, Color: Vector3{0.5, 0.5, 0.5}, Groups: []string{"lasso_target"},
})
height := 6
for i := 0; i < height; i++ {
pid := fmt.Sprintf("block_%d", i)
parts = append(parts, Part{
ID: pid, Type: "box", Size: Vector3{0.8, 0.8, 0.8},
Pos: Vector3{offset[0], offset[1] + 1 + float32(i)*0.9, offset[2]},
Color: Vector3{1, 0.5, 0}, Groups: []string{"lasso_target"},
})
}
req := ConstructRequest{Type: "create_construct", ConstructID: constructID, Parts: parts, Joints: joints}
data, _ := json.Marshal(req)
writePacket(conn, data)
// Animate: Add occasional random force to wobble
fmt.Printf("🗼 [BoxTower-%d] Wobbling...\n", id)
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
startTime := time.Now()
for range ticker.C {
elapsed := float32(time.Since(startTime).Seconds())
if elapsed > 60 {
break
}
updates := []PartUpdate{}
// Push middle block
mid := fmt.Sprintf("block_%d", height/2)
force := Vector3{float32(math.Sin(float64(elapsed))) * 20, 0, 0}
updates = append(updates, PartUpdate{PartID: mid, Torque: &force})
uReq := UpdateRequest{Type: "update_construct", ConstructID: constructID, Updates: updates}
uData, _ := json.Marshal(uReq)
writePacket(conn, uData)
}
}
func StartJellyfish(wg *sync.WaitGroup, id int, offset Vector3) {
defer wg.Done()
conn, err := net.Dial("tcp", "localhost:17000")
if err != nil {
fmt.Printf("❌ [Jellyfish-%d] Failed to connect: %v\n", id, err)
return
}
defer conn.Close()
constructID := fmt.Sprintf("jellyfish_%d", id)
fmt.Printf("🪼 [Jellyfish-%d] Floating...\n", id)
parts := []Part{}
joints := []Joint{}
// Bell
parts = append(parts, Part{
ID: "bell", Type: "sphere", Size: Vector3{1.5, 1.5, 1.5}, // Radius
Pos: Vector3{offset[0], offset[1] + 5, offset[2]},
Color: Vector3{0.8, 0.4, 0.9}, Groups: []string{"lasso_target"},
Locked: true, // Float in air
})
tentacles := 6
segPerTentacle := 4
for t := 0; t < tentacles; t++ {
angle := float64(t) * (2 * math.Pi / float64(tentacles))
xOff := float32(math.Cos(angle)) * 0.8
zOff := float32(math.Sin(angle)) * 0.8
parentID := "bell"
for s := 0; s < segPerTentacle; s++ {
pid := fmt.Sprintf("t%d_s%d", t, s)
yPos := (offset[1] + 5) - 1.2 - float32(s)*0.6
parts = append(parts, Part{
ID: pid, Type: "capsule", Size: Vector3{0.1, 0.5, 0},
Pos: Vector3{offset[0] + xOff, yPos, offset[2] + zOff},
Color: Vector3{0.6, 0.2, 0.7}, Groups: []string{"lasso_target"},
})
joints = append(joints, Joint{
Type: "pin", A: parentID, B: pid,
Pos: Vector3{offset[0] + xOff, yPos + 0.3, offset[2] + zOff},
})
parentID = pid
}
}
req := ConstructRequest{Type: "create_construct", ConstructID: constructID, Parts: parts, Joints: joints}
data, _ := json.Marshal(req)
writePacket(conn, data)
// Animate tentacles
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
startTime := time.Now()
for range ticker.C {
elapsed := float32(time.Since(startTime).Seconds())
if elapsed > 60 {
break
}
updates := []PartUpdate{}
// Pulse tentacles
for t := 0; t < tentacles; t++ {
for s := 0; s < segPerTentacle; s++ {
pid := fmt.Sprintf("t%d_s%d", t, s)
// Expand/Contract motion
factor := float32(math.Sin(float64(elapsed*2+float32(s)*0.5))) * 5.0
// Radial outward force
angle := float64(t) * (2 * math.Pi / float64(tentacles))
force := Vector3{
float32(math.Cos(angle)) * factor,
0,
float32(math.Sin(angle)) * factor,
}
updates = append(updates, PartUpdate{PartID: pid, Torque: &force})
}
}
uReq := UpdateRequest{Type: "update_construct", ConstructID: constructID, Updates: updates}
uData, _ := json.Marshal(uReq)
writePacket(conn, uData)
}
}
func RunTest2() {
var wg sync.WaitGroup
// TCP dev sandbox near origin (not multiverse cell indices).
baseX := float32(0)
baseY := float32(4)
baseZ := float32(0)
fmt.Println("🚀 Starting Multi-Construct Load Test...")
// Spawn SnakeBot
wg.Add(1)
go StartSnakeBot(&wg, 1, Vector3{baseX + 5, baseY, baseZ + 5})
// Spawn BoxTower
wg.Add(1)
go StartBoxTower(&wg, 1, Vector3{baseX - 5, baseY, baseZ + 5})
// Spawn Jellyfish
wg.Add(1)
go StartJellyfish(&wg, 1, Vector3{baseX, baseY + 5, baseZ - 5})
// Spawn Another Snake
wg.Add(1)
go StartSnakeBot(&wg, 2, Vector3{baseX + 10, baseY, baseZ})
wg.Wait()
fmt.Println("✅ All constructs finished simulation.")
}