Test 3: Procedural Bestiary
Overview
Test 3 spawns 20 procedurally generated creatures in parallel. Each creature gets random DNA (type, colors, scale, mutations) and a behavior loop matched to its morphology.
Run
go run . 3
Runs for ~120 seconds per creature, then exits.
Creature types
| Type |
Morphology |
Animation |
| Worm (0) |
4–15 linked spheres/boxes |
Vertical sine torques per segment |
| Star (1) |
Central sphere + 3–7 capsule arms |
Constant yaw torque on arms |
| Walker (2) |
Box body + 4 or 6 capsule legs |
Alternating leg torques |
| Cloud (3) |
5–14 locked spheres in a blob |
Kinematic vertical bob per sphere |
| Totem (4) |
3–7 stacked locked boxes |
Static (no updates) |
Each creature gets a generated name like "Swift Crimson Worm".
Protocol messages
| Message |
Usage |
create_construct |
Randomized parts + optional pin joints |
update_construct |
Type-specific torque or position updates (40 ms tick) |
Highlights
- Procedural naming from dominant color channel
- Staggered spawns (100 ms apart) to avoid flooding the server
- Spawns scattered in a ~40 m × 40 m area around
(0, 4, 0)
Source
test3.go — RunTest3(), StartUniqueCreature(), GenerateName()
Go source
test3.go — run with go run . 3 from the repo root
package main
import (
"encoding/json"
"fmt"
"math"
"math/rand"
"net"
"sync"
"time"
)
// --- Procedural Generation Utils ---
func GenerateName(planType int, color Vector3) string {
adjectives := []string{"Spiked", "Smooth", "Giant", "Tiny", "Swift", "Lumbering", "Glowing", "Shadow", "Ancient", "Neon"}
colors := []string{"Red", "Green", "Blue", "Golden", "Purple", "Cyan", "Orange", "Crimson", "Emerald", "Obsidian"}
nouns := []string{"Worm", "Star", "Walker", "Cloud", "Totem"}
// Pick color name based on dominant channel
cIdx := 0
if color[0] > color[1] && color[0] > color[2] {
cIdx = 0
if color[0] > 0.8 {
cIdx = 7
}
} // Red/Crimson
if color[1] > color[0] && color[1] > color[2] {
cIdx = 1
if color[1] > 0.8 {
cIdx = 8
}
} // Green/Emerald
if color[2] > color[0] && color[2] > color[1] {
cIdx = 2
if color[2] > 0.8 {
cIdx = 5
}
} // Blue/Cyan
adj := adjectives[rand.Intn(len(adjectives))]
colName := colors[cIdx]
if rand.Float32() > 0.7 {
colName = colors[rand.Intn(len(colors))]
} // Random chance for "Golden" etc
return fmt.Sprintf("%s %s %s", adj, colName, nouns[planType])
}
// --- Generator ---
func StartUniqueCreature(wg *sync.WaitGroup, id int, offset Vector3) {
defer wg.Done()
conn, err := net.Dial("tcp", "localhost:17000")
if err != nil {
fmt.Printf("❌ [ID-%d] Connect Fail: %v\n", id, err)
return
}
defer conn.Close()
// --- 1. DNA Generation ---
planType := rand.Intn(5) // 0=Worm, 1=Star, 2=Walker, 3=Cloud, 4=Totem
// Phenotypes
baseColor := RandomColor()
accentColor := RandomColor()
scale := 0.6 + rand.Float32()*1.4 // Size variation (0.6x to 2.0x)
mutation := rand.Float32() // 0.0-1.0 gene for shape variation
creatureName := GenerateName(planType, baseColor)
constructID := sanitizeID(fmt.Sprintf("creature_%d_%s", id, creatureName))
fmt.Printf("🧬 Spawned: '%s' (Type: %d, Scale: %.1fx)\n", creatureName, planType, scale)
parts := []Part{}
joints := []Joint{}
// --- 2. Morphology Generation (Building the Body) ---
switch planType {
case 0: // WORM Class
// Variations: Short/Fat, Long/Thin, Tapered
segments := 4 + rand.Intn(12)
thickness := (0.3 + rand.Float32()*0.5) * scale
for i := 0; i < segments; i++ {
pid := fmt.Sprintf("seg_%d", i)
z := offset[2] - float32(i)*(thickness*1.8)
// Tapering logic
mySize := thickness
if mutation > 0.5 {
mySize *= (1.0 - float32(i)/float32(segments)*0.6)
} // Tail taper
col := baseColor
if i%2 == 0 && mutation < 0.3 {
col = accentColor
} // Striped mutation
pType := "sphere"
if mutation > 0.7 {
pType = "box"
} // Blocky worm mutation
parts = append(parts, Part{
ID: pid, Type: pType, Size: Vector3{mySize, mySize, mySize},
Pos: Vector3{offset[0], offset[1] + 2, z},
Color: col, Groups: []string{"lasso_target"},
})
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 + thickness},
})
}
}
case 1: // STAR Class
arms := 3 + rand.Intn(5)
thickness := 0.4 * scale
parts = append(parts, Part{
ID: "core", Type: "sphere", Size: Vector3{thickness * 1.5, thickness * 1.5, thickness * 1.5},
Pos: Vector3{offset[0], offset[1] + 2, offset[2]},
Color: accentColor, Groups: []string{"lasso_target"},
})
for i := 0; i < arms; i++ {
angle := (float64(i) * 2 * math.Pi / float64(arms)) + float64(mutation)
length := (1.5 + rand.Float32()*1.5) * scale
pid := fmt.Sprintf("arm_%d", i)
pX := offset[0] + float32(math.Cos(angle))*length
pZ := offset[2] + float32(math.Sin(angle))*length
parts = append(parts, Part{
ID: pid, Type: "capsule", Size: Vector3{thickness * 0.5, length, 0},
Pos: Vector3{pX, offset[1] + 2, pZ},
Color: baseColor, Groups: []string{"lasso_target"},
Rot: Vector3{0, float32(angle * 180 / math.Pi), 90},
})
joints = append(joints, Joint{
Type: "pin", A: "core", B: pid,
Pos: Vector3{offset[0] + float32(math.Cos(angle))*0.3, offset[1] + 2, offset[2] + float32(math.Sin(angle))*0.3},
})
}
case 2: // WALKER Class
bodySize := scale
parts = append(parts, Part{
ID: "body", Type: "box", Size: Vector3{bodySize, bodySize * 0.6, bodySize * 1.2},
Pos: Vector3{offset[0], offset[1] + 2 + bodySize, offset[2]},
Color: baseColor, Groups: []string{"lasso_target"},
})
legs := 4
if mutation > 0.8 {
legs = 6
}
for i := 0; i < legs; i++ {
side := float32(1.0)
if i%2 == 1 {
side = -1.0
}
zPos := (float32(i/2) - float32(legs)/4.0 + 0.5) * scale
lID := fmt.Sprintf("leg_%d", i)
parts = append(parts, Part{
ID: lID, Type: "capsule", Size: Vector3{0.15 * scale, 1.2 * scale, 0},
Pos: Vector3{offset[0] + side*bodySize, offset[1] + 1, offset[2] + zPos},
Color: accentColor, Groups: []string{"lasso_target"},
})
joints = append(joints, Joint{
Type: "pin", A: "body", B: lID,
Pos: Vector3{offset[0] + side*bodySize*0.5, offset[1] + 2 + bodySize, offset[2] + zPos},
})
}
case 3: // CLOUD (Multi-Sphere)
blobs := 5 + rand.Intn(10)
for i := 0; i < blobs; i++ {
pid := fmt.Sprintf("b_%d", i)
bSize := (0.5 + rand.Float32()*1.0) * scale
lX := (rand.Float32() - 0.5) * 2 * scale
lY := (rand.Float32() - 0.5) * 2 * scale
lZ := (rand.Float32() - 0.5) * 2 * scale
parts = append(parts, Part{
ID: pid, Type: "sphere", Size: Vector3{bSize, bSize, bSize},
Pos: Vector3{offset[0] + lX, offset[1] + 5 + lY, offset[2] + lZ},
Color: baseColor, Locked: true, Groups: []string{"lasso_target"},
})
}
case 4: // TOTEM (Stacked)
blocks := 3 + rand.Intn(5)
for i := 0; i < blocks; i++ {
pid := fmt.Sprintf("t_%d", i)
tSize := (0.8 + rand.Float32()) * scale
parts = append(parts, Part{
ID: pid, Type: "box", Size: Vector3{tSize, tSize, tSize},
Pos: Vector3{offset[0], offset[1] + float32(i)*tSize, offset[2]},
Color: baseColor, Locked: true, Groups: []string{"lasso_target"},
Rot: Vector3{0, float32(i) * 15, 0},
})
}
}
// --- 3. Finalize and Send ---
req := ConstructRequest{Type: "create_construct", ConstructID: constructID, Parts: parts, Joints: joints}
data, _ := json.Marshal(req)
writePacket(conn, data)
// --- 4. Behavior Loop ---
ticker := time.NewTicker(40 * time.Millisecond)
defer ticker.Stop()
startTime := time.Now()
phase := 0.0
for range ticker.C {
if time.Since(startTime).Seconds() > 120 {
break
}
phase += 0.1
updates := []PartUpdate{}
switch planType {
case 0: // Worm slither
for i := 0; i < len(parts); i++ {
force := Vector3{0, float32(math.Sin(phase+float64(i)*0.4) * 20), 0}
updates = append(updates, PartUpdate{PartID: parts[i].ID, Torque: &force})
}
case 1: // Star spinning
for i := 1; i < len(parts); i++ {
force := Vector3{0, 15, 0}
updates = append(updates, PartUpdate{PartID: parts[i].ID, Torque: &force})
}
case 2: // Walker leg cycle
for i := 0; i < len(joints); i++ {
side := float32(1.0)
if i%2 == 1 {
side = -1.0
}
force := Vector3{0, 0, float32(math.Sin(phase+float64(i)*math.Pi)) * 40 * side}
updates = append(updates, PartUpdate{PartID: fmt.Sprintf("leg_%d", i), Torque: &force})
}
case 3: // Cloud undulation
for i := 0; i < len(parts); i++ {
y := float32(math.Sin(phase+float64(i)*0.5)) * 0.05
newPos := parts[i].Pos
newPos[1] += y
updates = append(updates, PartUpdate{PartID: parts[i].ID, Position: &newPos})
}
}
if len(updates) > 0 {
uReq := UpdateRequest{Type: "update_construct", ConstructID: constructID, Updates: updates}
uData, _ := json.Marshal(uReq)
writePacket(conn, uData)
}
}
}
func RunTest3() {
var wg sync.WaitGroup
fmt.Println("🌟 Starting Test 3: PROCEDURAL BESTIARY 🌟")
// Spawn a variety of creatures
for i := 0; i < 20; i++ {
wg.Add(1)
spawnX := float32((rand.Float32() - 0.5) * 40)
spawnZ := float32((rand.Float32() - 0.5) * 40)
go StartUniqueCreature(&wg, i, Vector3{spawnX, 4, spawnZ})
time.Sleep(100 * time.Millisecond) // Stagger spawns
}
wg.Wait()
fmt.Println("✅ All creatures have evolved and finished.")
}