Skip to content
 
 

Repository files navigation

Aialander PyLib

A Python library for creating AI bots for AIA's game collection. This library allows you to programmatically create node-based AI logic that can be exported and used in Unity games.

If you are an LLM writing a bot from this README, jump to Notes for LLM Authors at the bottom first. It explains the mental model, lists the mistakes we keep seeing, and shows a correct minimal script.

Installation

This library requires Python 3.7+. Place the AIGamePyLibrary package folder in your project directory and import it:

from AIGamePyLibrary import *

Aialander PyLib is the friendly name; the importable Python package matches the GitHub repo: AIGamePyLibrary (one name, no separate “Library” / “PyLibrary” split).

Quick Start

Here's a simple example for Volleyball. Everything you read here — positions, velocities, "the ball", "the opponent" — is a node you ask the graph for via a simulation-prefixed helper (VolleyballGetVector3, VolleyballGetTransform, VolleyballGetBool, VolleyballGetFloat). These match the VolleyballGet* / SlimeGetVector3 node types in Assets/_Nodes/ (the Vector3 node type id is still SlimeGetVector3 in Unity; the Python API is VolleyballGetVector3 so naming stays consistent). There are no Unity-style dotted accessors like something.Position / something.Velocity — always go through the helpers.

from AIGamePyLibrary import *

# Initialize the slime with name, color, country, and stats (speed, acceleration, jump)
InitializeSlime("AIA", "Yellow", "United States of America", 5, 3, 2)

# Grab the world Vector3s from the graph. Every call returns a Node.
ball_position = VolleyballGetVector3("Ball Position")
self_position = VolleyballGetVector3("Self Position")

# Team spawn is a Transform; convert to a Vector3 via RelativePosition.
team_spawn    = VolleyballGetTransform("Self Team Spawn")
positionSign  = RelativePosition(team_spawn, "Backward")

# Calculate where to move (ball position + offset)
moveTo = ball_position + positionSign * 0.4

# Calculate distance to ball and jump condition
distanceToBall = Distance(ball_position, self_position)
jumpCondition  = distanceToBall < 2.25

# Control the slime (target position, jump condition)
SlimeController(moveTo, jumpCondition)

# Save the AI data to a file
SaveData("SlimeVolleyball/AIComp_Data/Saves/AIA python.txt", "grid")

Core Concepts

Nodes

The library uses a node-based system where operations return Node objects. Nodes can be combined using Python operators:

  • Arithmetic: +, -, *, /, //, %, **
  • Comparison: <, <=, >, >=, ==, !=
  • Boolean: & (and), | (or), ^ (xor), ~ (not)

Example:

ball_pos = VolleyballGetVector3("Ball Position")
self_pos = VolleyballGetVector3("Self Position")
distance   = Distance(ball_pos, self_pos)
shouldJump = distance < 2.5  # Returns a Node representing the comparison

Vector Operations

Vector3 nodes support component access and operations. Always get the Vector3 from a simulation-prefixed helper (VolleyballGetVector3(...) in Volleyball, RelativePosition(transform_node, "Self") for any Transform node in other sims) — Transforms do not have a .Position / .position attribute.

# Access vector components on a Vector3 Node
ballPos = VolleyballGetVector3("Ball Position")
x = ballPos.x  # X component
y = ballPos.y  # Y component
z = ballPos.z  # Z component

# Vector arithmetic between Vector3 Nodes
offset = VolleyballGetVector3("Ball Position") - VolleyballGetVector3("Self Position")
scaled = offset * 0.5

Complete Node Reference

Node configurations determine which nodes are available in the Unity editor. Each configuration has a specific purpose. Expand the lists below to see nodes by configuration.


Default Nodes

Description: General purpose nodes for all simulations. Includes arithmetic, vectors, logic, variables, and debug tools.

Basic Types
Node Purpose Inputs Outputs Options
Float(value) Represents a real number
Bool(value) Represents a true/false or “boolean” value
  • Bool1The selected value
String(value) Represents a text value
Color(value) Outputs the color value selected in the dropdown
  • Color1The selected value
See Color options
Country(value) Outputs the country value selected in the dropdown See Country options
Color options

"Auburn", "Black", "Blonde", "Blue", "Brown", "Dark Brown", "Dark Green", "Green", "Hot Pink", "Light Blue", "Light Grey", "Medium Grey", "Orange", "Pink", "Purple", "Red", "Tan", "White", "Yellow"

Country options

"Unknown", "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Bermuda", "Bohemia", "Botswana", "Brazil", "Bulgaria", "Burkina Faso", "Burundi", "Cameroon", "Canada", "Chile", "China", "Colombia", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia", "Côte d'Ivoire", "Denmark", "Djibouti", "Dominican Republic", "DR Congo", "Ecuador", "Egypt", "Eritrea", "Estonia", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guyana", "Haiti", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kosovo", "Kuwait", "Kyrgyzstan", "Latvia", "Lebanon", "Lithuania", "Luxembourg", "Malaysia", "Mauritius", "Mexico", "Moldova", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Netherlands", "New Zealand", "Niger", "Nigeria", "North Korea", "North Macedonia", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Puerto Rico", "Qatar", "Romania", "Russia", "Samoa", "San Marino", "Saudi Arabia", "Scotland", "Senegal", "Serbia", "Singapore", "Slovakia", "Slovenia", "Somolia", "South Africa", "South Korea", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States of America", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam", "Virgin Islands", "Yemen", "Zambia", "Zimbabwe", "ChatGPT", "Claude", "Deepseek", "Gemini", "Grok", "Llama", "Mistral", "Perplexity", "Qwen"

Arithmetic Operations
Node Alias Purpose Inputs Outputs
AddFloats(a, b) a + b Performs an addition operation between two numbers
SubtractFloats(a, b) a - b Performs a subtract operation between two numbers
MultiplyFloats(a, b) a * b Performs a multiplication operation between two numbers
DivideFloats(a, b) a / b Performs a divide operation between two numbers
Modulo(a, b) a % b Divides a number by another number and returns any remainder
ClampFloat(value, min, max) Limits (clamps) a number between a minimum and maximum value
RandomFloat(min, max) Returns a random number value between two values (changes every Update)
Math Functions
Node Alias Purpose Inputs Outputs Options
AbsFloat(x) Abs(x) Convert a number to it’s unsigned / absolute value
  • Float1The number to perform the function on
  • Float1The resulting value
Operation(x) Performs the selected operation on the input number
  • Float1The number to perform the operation on
  • Float1The resulting value
Operations
Power(base, exponent) a ** b Raises base to the given power (Mathf.Pow)
  • Float1The resulting value
Lerp(a, b, t) Linearly interpolates between A and B by T (Mathf.Lerp)
  • Float1The resulting value
Operation options

abs, round, floor, ceil, sin, cos, tan, asin, acos, atan, sqrt, sign, ln, log10, e^, 10^

Vector Operations
Node Alias Purpose Inputs Outputs
AddVector3(a, b) a + b Adds two three-dimensional vectors to each other
  • Vector31The first Vector3 value
  • Vector32The second Vector3 value to add to the first
SubtractVector3(a, b) a - b Subtracts the second Vector3 from the first (component-wise)
ScaleVector3(vec, scalar) vec * scalar Multiplies a Vector3 by a number
  • Vector31The value to scale
  • Float1The amount to uniformly scale by
DotProduct(a, b) a @ b Returns the dot product between two vectors
  • Float1The resulting value
CrossProduct(a, b) Calculates the cross product (result perpendicular to both inputs)
Magnitude(vec) Returns the length of the input vector
  • Float1The resulting value
Normalize(vec) Returns a vector with the same direction but magnitude 1
Distance(pos1, pos2) Calculates the distance between two points
  • Float1The resulting value
Vector3Split(vec) Splits a Vector3 into x, y, z components
  • Vector31The Vector3 to be split into it’s components
  • Float1The x component of the input Vector3
  • Float2The y component of the input Vector3
  • Float3The z component of the input Vector3
ConstructVector3(x, y, z) Vector3(x, y, z) Creates a Vector3 from three numbers
Comparison & Logic Operations
Node Alias Purpose Inputs Outputs Options
CompareFloats(a, b, operator) a < b, a > b, … Evaluates two float values against the selected operator
  • Float1The number representing the left side of the comparison
  • Float2The number representing the right side of the comparison
  • Bool1The resulting value
Operators
CompareBool(a, b, operator) Evaluates two boolean values against the selected operator
  • Bool1The first value
  • Bool2The value to compare to the first
  • Bool1The resulting value
Operators
Not(condition) ~condition Toggles the input boolean (TRUE↔FALSE)
  • Bool1The input value
  • Bool1The resulting value
ConditionalSetFloat(condition, trueValue, falseValue) Selects between two Float values based on a condition
  • Bool1The value to compare against the dropdown selection
  • Float1If TRUE, this value will be set as the result
  • Float2If FALSE, this value will be set as the result
  • Float1The resulting value
Dropdown
ConditionalSetVector3(condition, trueValue, falseValue) Selects between two Vector3 values based on a condition
  • Bool1The value to compare against the dropdown selection
  • Vector31If TRUE, this value will be set as the result
  • Vector32If FALSE, this value will be set as the result
Dropdown
ConditionalSetBool(condition, trueValue, falseValue) Selects between two Bool values based on a condition
  • Bool1The value to compare against the dropdown selection
  • Bool2If TRUE, this value will be set as the result
  • Bool3If FALSE, this value will be set as the result
  • Bool1The resulting value
Dropdown
CompareFloats operators

==, <, >, <=, >=

CompareBool operators

AND, OR, EQUAL TO, XOR, NOR, NAND, XNOR

ConditionalSetFloat dropdown

True (use trueValue when condition is true), False (use trueValue when condition is false)

ConditionalSetBool / ConditionalSetVector3 dropdown

True, False

Variables & Utilities
Node Purpose Inputs Outputs Options
SetVariable(value) Saves the input value so it can be used by matching GetVariable nodes
  • Any1An input of any data type
Destination node
GetVariable(name) Outputs the value from the corresponding SetVariable node with the same typed name
  • Any1The result of the Set Variable matching the same typed name
Relay(value) Passes through data from input to output (useful for organization)
  • Any1Input of any data type to pass through
  • Any1The passed-through value
IsNull(value) Checks if the input is null
  • Any1A connection of any data type
  • Bool1The resulting value
Keypress(key) Indicates whether the selected key is currently pressed
  • Bool1Is the key pressed
Key is selected in dropdown
RelativePosition(transform, direction) Gets a world-space position relative to the input Transform and selected direction
  • Transform1The transform to extract the position from
Self, Self + Forward, Self + Backward, Self + Left, Self + Right, Self + Up, Self + Down, Forward, Backward, Left, Right, Up, Down, World
RelativePosition options

Self, Self + Forward, Self + Backward, Self + Left, Self + Right, Self + Up, Self + Down, Forward, Backward, Left, Right, Up, Down, World

Note: World exists in the Unity dropdown; currently it behaves the same as Self unless/until the Unity gate assigns it a distinct meaning.

Debug & Visualization
Node Purpose Inputs Outputs
Debug(value) Displays the real-time value of the connected output
  • Any1An input of any data type
DebugDrawLine(start, end, width, color) Draws a 2D line in worldspace (debug visualization)
  • Vector31The start point of the line
  • Vector32The end point of the line
  • Float1The thickness of the line
  • Color1The color of the line
DebugDrawDisc(center, radius, height, color) Draws a 2D disc in worldspace on the XY plane (debug visualization)
  • Vector31The centerpoint of the drawn disc
  • Float1The radius of the drawn disc
  • Float2The thickness of the drawn disc
  • Color1The color to set the disc
TimePlot(name, color, iconUrl, value) Adds a value to the time plot graph during a simulation (toggle with F1)
  • String1The name to be assigned to the Aialander name tag
  • Color1The color of the line to plot
  • String2A URL of a custom icon to use for the graph
  • Float1The value to set on the graph for the current tick
Organization
Node Purpose Inputs Outputs
Region Groups nodes visually for organization (does not affect logic)
CreateFunction(name) Defines a named custom function body (Unity: Construct Custom Function). Nodes assigned to its bounds run only when a matching CustomFunction call evaluates them — not during the global graph solve.
  • Any1 (In) — Return — wire body output here via SetFunctionReturn (same id as Param1 Out)
  • String1String4Optional parameter labels
  • Any1Any4Up to 4 parameters passed into the body (fn.Param1…)
SetFunctionReturn(fn, body_output) Required for a call result. Connects a body output (e.g. Power.Float1) to CreateFunction Return port Any1 (polarity In). body_output — any body node with an out port
AssignToFunction(body_node, fn) Marks a node as owned by the CreateFunction body (ownerFunctionSID) Returns the same body node
CustomFunction(name, param1=None, …) Call site for a CreateFunction by name (Unity key Function). Passes through used params; output is the definition’s Return value when SetFunctionReturn was used.
  • Any1Any4Arguments for connected params
  • Any1Return value (null if definition has no Return)
Custom Functions (how they differ from normal nodes)
  • Definition vs call: CreateFunction("MyFn") defines a reusable body; CustomFunction("MyFn", …) calls it by name (modifier).
  • Parameters: Up to 4 Param outputs on the definition (Any1Any4fn.Param1Param4). Wire them into body nodes. Only params that are connected on the definition appear as inputs on call sites.
  • Return (required for a usable call result): Call SetFunctionReturn(fn, body_output). This connects the body node's output port to the CreateFunction Return input.
    • Return port id is Any1, polarity In (GameObject name "Any - In"). Param1 is also Any1 but polarity Out — same id, different polarity.
    • For Power / Lerp / most float math, the body output port is Float1 → wire to Return Any1 (In).
    • Without SetFunctionReturn, CustomFunction(...) still runs the body but the call output is null.
  • Body bounds: Mark every body node with AssignToFunction(body_node, fn) (ownerFunctionSID). Body nodes are skipped by the global solve and only run when a Function call evaluates them.
  • Isolation: Body cannot wire to the outside world except via Param / Return. Banned in body: nested CreateFunction / Function, SetVariable, and most destination nodes (controllers / properties). Allowed destinations in body: Debug, DebugDrawLine, DebugDrawDisc. Max nested call depth: 8.
  • Unlike Region: Region is visual-only. CreateFunction bounds change execution.
  • Unlike Python helpers in customNodes.py: those expand into ordinary nodes at compile time. Unity Custom Functions are the CreateFunction / Function pair. Native math nodes like Power(...) / Lerp(...) are normal graph nodes and are ideal inside a function body.

Canonical example — Power inside a Custom Function (LLM copy/paste)

from AIGamePyLibrary import *

fn = CreateFunction("PowerFn")

# Body: params → Power → Return
#   fn.Param1 (Any1) = base
#   fn.Param2 (Any2) = exponent
#   Power.Float1     = result  →  MUST go to CreateFunction Return (Any1 In)
powered = AssignToFunction(Power(fn.Param1, fn.Param2), fn)
SetFunctionReturn(fn, powered)   # Power.Float1 → Return Any1 (In)  ← do not omit

# Call with different variables; CustomFunction output IS the Return value
result = CustomFunction("PowerFn", Float(2), Float(3))  # → 8
Debug(result, "2^3", changePosition=False)

SaveData("PowerFunctionTest.txt", "grid")

Full three-call test: Examples/CustomFunctionPowerExample.py.


Survival Simulation
Overview

Aialanders are trapped on a deserted island with apple trees and other inhabitants. Each Aialander has a storage container they can put food. Tree count and location can vary per simulation. The number of competitors and their strategies will vary per simulation. The maximum number of total players is 7.

Objective: Survive on the island given the available food. Other inhabitants may be friendly or aggressive — choose any personality you want.

Requirements
  • The SurvivalController(...) node is the required destination to control a single 3D character.
  • ConstructSurvivalProperties(...) sets that character’s aesthetic.
Nodes
Node Purpose Inputs Outputs Options / Notes
SurvivalController(targetPosition, state, sprint, emote) Controls an Aialander’s brain (navigation uses pathfinding)
  • Vector31The target world position to move the character to
  • SurvivalState1The current state of the character which will control its behavior
  • Bool1Whether the character should attempt to sprint (requires stamina)
  • SurvivalEmote1Optional emote; performing an emote stops movement
Destination node
ConstructSurvivalProperties(...) Sets cosmetic options for this Aialander
  • String1The name to be assigned to the Aialander name tag
  • Country1The country this Aialander is representing
  • Color1The color of the Aialander’s skin
  • Float1Body style (0 = male, 1 = female). Value wraps to prevent errors
  • Float2Hair style. Value wraps to prevent errors
  • Color2Hair color
  • Float3Facial hair style. Value wraps to prevent errors
  • String2Optional outfit image URL
Customization options are documented under Details → Customization.
SurvivalGetTransform(value) Select a Transform representing a current location / entity in the sim Options
SurvivalGetFloat(value) Select a Float representing a current simulation parameter Options
SurvivalGetBool(value) Select a Bool representing a current simulation parameter
  • Bool1The selected value
Options
SurvivalState(value) Select a SurvivalState value Passive, Gather, Eat, Attack, Steal, Dead
SurvivalEmote(value) Select an emote None, Hi, Talk, Bored, Wave
SurvivalAutoPosition(state) Automatically decide where to move based on a given state
  • SurvivalState1The state from which the position will be determined
ConditionalSetSurvivalState(condition, trueValue, falseValue) Select between two SurvivalState values based on a condition
  • Bool1The value to compare against the dropdown selection
  • SurvivalState1If TRUE, this value will be set as the result
  • SurvivalState2If FALSE, this value will be set as the result
True, False
ConditionalSetSurvivalEmote(condition, trueValue, falseValue) Select between two SurvivalEmote values based on a condition
  • Bool1The value to compare against the dropdown selection
  • SurvivalEmote1If TRUE, this value will be set as the result
  • SurvivalEmote2If FALSE, this value will be set as the result
True, False
Details

Customization

Body Type

  • 0 Pants and shorts
  • 1 Dress
  • 2 Robot

Beard Styles

  • 0 None
  • 1 Full trimmed beard

Hair Styles

  • 0 Curtains
  • 1 Caesar
  • 2 Bob
  • 3 Faux hawk
  • 4 Long polytail
  • 5 Pebbles
  • 6 Side pony
  • 7 Samurai side bun
  • 8 Undercut
  • 9 Medium polytail
  • 10 Bowl
  • 11 Space buns
  • 12 Samurai bun
  • 13 Bald

Custom Outfit The properties node has a custom outfit at the bottom that can take a URL string to an image. You can upload the image to something like imgur and then copy the image URL.

If you want, here's the PSD to make a custom outfit texture for your character: https://github.com/theaia/AIGamePyLibrary/blob/main/CustomOutfit.psd

Container Information

Health

  • Each player starts with a designated container with 250 health.
  • Containers do not regenerate health.
  • When a container's health reaches 0 it will be destroyed along with any food stored in it.

Terrain

  • Terrain has an approximate diameter of 160.
  • For the competition the seed will be random—you may be the closest one to a tree, or the furthest.
  • I'll be running the sim with both abundant and scarce resources. Adjust your strategy appropriately.
  • After being harvested, fruit respawn after 190 seconds. Each tree starts with and has a maximum of 3 fruit.

Ranking

Players are sorted by Survival Time, then Most Health, then Hunger, then stored apple count. When a player dies, their survival time stops at the time they die.

Player Information

Abilities

  • Standard move speed is 5.
  • Sprinting increases player speed 9. Players must have available stamina to sprint.
  • Attacking deals 10 damage per hit and consumes 5 stamina. Players must have enough stamina to attack. Attack range is 2. Attack radius is 1 in the forward direction of the player. Attacking while holding food will drop the food.
  • Players will drop food they are holding when they die.
  • Emoting will stop all movement and cause the player to perform a looping animation until their emote state is set to "None".

Health

  • Players all start with 100 health.
  • Players will regenerate 1 health per Tick after they've not taken damage for 5 seconds.
  • After a player has started regenerating health, their "last attacked by" player is reset.

Hunger

  • Players start with 100 Hunger points.
  • Every 5 seconds players lose 10 points. The 5 second timer is reset whenever a player eats.
  • If the player does not have 10 hunger to be consumed, they will instead take 10 damage.
  • Consuming food restores 25 points.

Stamina

  • Players all start with 100 stamina.
  • Sprinting consumes stamina at .15 per tick and Attacking consumes stamina at 5 per attack.
  • After not consuming stamina for 3 seconds players will regenerate 2 stamina per Tick.

Aggression

  • Players have an aggression level.
  • Attacking a player with the same or lower aggression level will increase a player's aggression by 1.
  • Attacking a player's container with the same or lower aggression level will increase a player's aggression by .5.
  • Players can reference players based on aggression using the Get Transform and Get Float nodes.
  • Stealing from an alive player's container will increase aggression by .25.
  • Stealing from a dead player's inventory does not affect a player's aggression level.

Player States

  • Passive - The player will not perform any actions during this state.
  • Gathering - If not carrying any food a player will search for nearby food to gather and automatically pick it from trees or off the ground. When carrying food the player will automatically deposit it into its own storage container if nearby.
  • Eating - If not carrying any food a player will search for nearby food to gather and automatically pick it from trees, off of the ground, or from its own container. When carrying food the player will automatically consume it.
  • Attack - If not carrying any food a player will search for nearby players and containers and will automatically attack them when in range. When carrying food the player will automatically drop it.
  • Steal - If not carrying any food a player will search for nearby containers they do not own to steal from. When carrying food the player will automatically deposit it into its own storage container if nearby.
  • Dead - The player will not perform any actions during this state.

SurvivalGetTransform options

  • 0 Self
  • 1 Player Last Damaged By
  • 2 Player Last Attacking (Global)
  • 3 Player Nearest
  • 4 Player 2nd Nearest
  • 5 Player Farthest
  • 6 Player with Highest Health
  • 7 Player with Lowest Health
  • 8 Player with Highest Hunger
  • 9 Player with Lowest Hunger
  • 10 Player with Highest Stamina
  • 11 Player with Lowest Stamina
  • 12 Player Last Stolen From Self Container
  • 13 Player Last Stealing (Global)
  • 14 Player with Highest Score
  • 15 Player with Lowest Score
  • 16 Player of Closest Score
  • 17 Player with Most Aggression
  • 18 Player with Lowest Aggression
  • 19 Player Nearest with Aggression
  • 20 Player Nearest with No Aggression
  • 21 Player with Most Stored Fruit
  • 22 Player with Least Stored Fruit
  • 23 Container Self
  • 24 Container Closest (excluding self)
  • 25 Container Farthest (excluding self)
  • 26 Container of Last Attacker
  • 27 Container of Last Global Attacker
  • 28 Container of Most Stored Fruit
  • 29 Container of Nearest Dead Player
  • 30 Container of Nearest Dead Player (with Food)
  • 31 Container of Highest Aggression Player
  • 32 Container of Lowest Aggression Player
  • 33 Container of Player with Closest Rank
  • 34 Fruit Nearest
  • 35 Fruit Farthest
  • 36 Fruit Nearest Self Container
  • 37 Fruit (Random)
  • 38 Tree Nearest

SurvivalGetFloat options

  • 0 Health Percentage
  • 1 Hunger Percentage
  • 2 Stamina Percentage
  • 3 Current Rank
  • 4 Stored Food Count
  • 5 Distance to Nearest Player
  • 6 Distance to Nearest Aggressive Player
  • 7 Distance to Last Attacker
  • 8 Distance to Nearest Container
  • 9 Distance to Nearest Fruit Tree
  • 10 Player Count Remaining
  • 11 Current Simulation Time
  • 12 Max Simulation Time
  • 13 Simulation Time Remaining
  • 14 Delta time
  • 15 Fixed delta time
  • 16 Pi
  • 17 Self Gathers
  • 18 Self Food Consumed
  • 19 Self Steals
  • 20 Self Damage Dealt
  • 21 Self Kills
  • 22 Self Aggression Level
  • 23 Available Fruit Count
  • 24 Player Count Without Stored Food
  • 25 Average Player Health
  • 26 Average Player Hunger
  • 27 Average Player Stamina
  • 28 Average Player Aggression Level
  • 29 Total Possible Fruit
  • 30 Distance Traveled

SurvivalGetBool options

  • 0 Is Carrying Resource
  • 1 Container has Health
  • 2 Container Was Attacked
  • 3 Container Was Stolen From
  • 4 Self Was Attacked

Parking Simulation
Overview

The goal of the simulation is to navigate the car to a target parking stall and have it sit inside the stall for 3 seconds. After that, the player progresses to a new level with the same goal but from a different local position and with a different target parking stall.

Alignment to the stall is encouraged but not required. Colliding with any objects will reset the player to the starting position.

Requirements
  • You must drive your car via the destination node: ModularUniformController(...)
  • Use ConstructModularUniformProperties(...) / InitializeParking(...) to set cosmetics
  • Sensors are built from Spherecast(...)CarRaycasts(...)HitInfo(...)
  • The same modular car stack (controller, sensors, Autosteer / Autothrottle) is shared with Demo Derby and RacingV2. RacingV2 adds race-specific getters and uses ConstructRacingV2Properties for cosmetics + stats — see RacingV2 Simulation.
Nodes
Node Purpose Inputs Outputs Notes
ModularUniformController(throttle, steering, brake) Sends inputs to the car to control it
  • Float1Throttle (1 is forward, -1 is reverse)
  • Float2Steering (-1 is left, 1 is right)
  • Float3Brake (any value over 0 will apply brake)
Destination node
ConstructModularUniformProperties(name, country, skinColor, bodyStyle, hairStyle, hairColor, facialHairStyle, carColor, outfitUrl) Sets cosmetic options for this car
  • String1The name to be assigned to the Aialander name tag
  • Country1The country this Aialander is representing
  • Color1The color of the Aialander’s skin
  • Float1Body style (0 = male, 1 = female). Value wraps to prevent errors
  • Float2Hair style. Value wraps to prevent errors
  • Color2Hair color
  • Float3Facial hair style. Value wraps to prevent errors
  • Color3Car color
  • String2Optional image URL to download and apply to the outfit
Destination node
Spherecast(radius, distance) Defines the radius and travel distance used for spherecast sensors
  • Float1The radius of the spherecast to be sent out
  • Float2The maximum distance to check for collisions from the origin
  • Spherecast1The spherecast that will check for collisions
CarRaycasts(spherecast) Sends 8 sensors out around the car (spherecasts)
  • Spherecast1The length/size of spherecasts to send out as sensors
HitInfo(raycastHit) Extracts collision info from a RaycastHit
  • Bool1Was a collision detected?
  • Float1The collision distance (infinity if no collision)
ParkingGetTransform(value) Selection of Transform options that represent a current location in the simulation. value may be a dropdown index (0, 1, …) or one of the exact labels shown in the options column; the saved graph stores the dropdown index in the node’s modifier field. Self, Target Parking Stall
ParkingGetFloat(value) Selection of number-based options that represent a current simulation parameter Speed, Distance to Stall (based on pathfinding), Target Stall Width, Target Stall Depth, Current Level, Fail Count, Current Simulation Time, Max Simulation Time, Delta Time, Fixed Delta Time, Pi, Signed Speed (forward +, reverse −)
ParkingGetBool(value) Selection of True/False options that represent a current simulation parameter
  • Bool1The result of the selected value
Is Partially in Target Parking Stall, Is Fully in Target Parking Stall
InitializeParking(...) (convenience helper)

Same nine positional arguments as ConstructModularUniformProperties(...), but applies them as a convenience initialization helper.

Details
Tips
  • Raycasts originate from the car’s center. Approx width 6, length 10.8.
  • Stepping on the brake while setting throttle to -1 will almost instantly stop the car.
  • Shared with Demo Derby and RacingV2: ModularUniformController, SpherecastCarRaycastsHitInfo, Autosteer, Autothrottle. Parking cosmetics use ConstructModularUniformProperties / InitializeParking (not RacingV2’s stat budget properties node).
Marking the bot as LLM-driven

See the top-level Marking your bot as LLM-driven section. The same props.data["modifier"] = "True" recipe applies to InitializeParking, InitializeDemoDerby, and ConstructModularUniformProperties — they all return the same UniformModularCarProperties node.


Demo Derby Simulation
Overview

Maximize damage to other cars. Collisions deal damage; vulnerable parts change handling (front wheels steering; rear wheels, engine, driveshaft acceleration). At 0 engine health a car explodes and damages nearby cars.

Requirements
  • Same modular car stack as Parking / RacingV2:
    • Drive with ModularUniformController(...)
    • Cosmetics with ConstructModularUniformProperties(...) / InitializeParking(...) / InitializeDemoDerby(...)
    • Sensors via Spherecast(...)CarRaycasts(...)HitInfo(...)
    • Optional Autosteer / Autothrottle (also used by RacingV2)
Nodes
Node Purpose Inputs Outputs Options / Notes
InitializeDemoDerby(name, country, skinColor, bodyStyle, hairStyle, hairColor, facialHairStyle, carColor, outfitUrl) Convenience helper to set derby cosmetics (delegates to InitializeParking) See ConstructModularUniformProperties(...) in Parking Simulation Returns UniformModularCarProperties; set props.data["modifier"] = "True" to mark as LLM-driven
DemoDerbyGetTransform(value) Get key transforms used in the derby sim
Values:
0 self
1 fixed ref (inspector)
2 random pathable waypoint
DemoDerbyGetCar(mode, index_float=None) Select a car reference by mode (nearest, ranked, etc.)
  • Car1The selected car
Modes:
0 by index
1 by rank
2 self
3 nearest car
4 furthest car
5 lowest health car
6 highest health car
7 last damaged car
8 nearest active
9 furthest active
10 nearest disabled
11 furthest disabled
12 nearest with disabled steering (rear may still drive)
13 furthest with disabled steering
14 nearest AI-authored (active)
15 lowest health AI
16 highest health AI
17 nearest human-authored (active)
18 lowest health human
19 highest health human
20 highest ranked
21 lowest ranked
22 nearest ranked (rank neighbor of self)
23 highest ranked (not immobilized)
24 highest ranked (immobilized)
25 lowest ranked (not immobilized)
26 lowest ranked (immobilized)
CarGetPart(mode, car) Pick a part of a car (aim point, weakpoint, etc.)
  • Car1The car to query
Modes:
0 average of all parts
1 nearest part
2 weakest part
3 nearest crucial part
4 WheelFL
5 WheelFR
6 WheelRL
7 WheelRR
8 AxleFL
9 AxleFR
10 AxleRL
11 AxleRR
12 Engine
13 Driveshaft
14 SuspensionFL
15 SuspensionFR
16 SuspensionRL
17 SuspensionRR
18 Hood
19 Trunk
20 BumperFront
21 BumperRear
22 LicensePlateFront
23 LicensePlateRear
24 FenderFL
25 FenderFR
26 DoorL
27 DoorR
28 DoorRL
29 DoorRR
30 TurnSignalFL
31 TurnSignalFR
32 HeadlightL
33 HeadlightR
34 TaillightL
35 TaillightR
36 WindshieldWipers
CarInfo(car) Multi-output car info helper
  • Car1The car to query
Notes
GetCarFromTransform(transform) Convert a Transform into a Car reference
  • Car1The resolved car
Autosteer(goal) Steer toward a world-space goal
Autothrottle(goal, desired_speed) Throttle toward a goal with a target speed
Details

Guardrails (common LLM pitfalls)

  • Transform vs Vector3: Many helpers output a Transform (type Transform) which is not a Vector3 position. In this library, transforms are represented as Node objects and do not have Unity-style fields like .Position / .position.
  • How to get a position Vector3 from a Transform: use RelativePosition(transform_node, "Self") (returns a Vector3).
  • Bad (will error): goal = CarGetPart(0, car).PartTransform.Position
  • Good:
    • part = CarGetPart(0, car)
    • part_tf, _health = CarGetPart(0, car)
    • goal = RelativePosition(part_tf, "Self")

DemoDerbyGetCar modes

  • 0 by index · 1 by rank · 2 self
  • 3 nearest car · 4 furthest car
  • 5 lowest health car · 6 highest health car
  • 7 last damaged car
  • 8 nearest active · 9 furthest active
  • 10 nearest disabled · 11 furthest disabled
  • 12 nearest with disabled steering (rear may still drive) · 13 furthest with disabled steering
  • 14 nearest AI-authored (active) · 15 lowest health AI · 16 highest health AI
  • 17 nearest human-authored (active) · 18 lowest health human · 19 highest health human
  • 20 highest ranked · 21 lowest ranked · 22 nearest ranked (rank neighbor of self)
  • 23 highest ranked (not immobilized) · 24 highest ranked (immobilized)
  • 25 lowest ranked (not immobilized) · 26 lowest ranked (immobilized)

CarGetPart modes

  • Modes 03: average of all parts; nearest part; weakest part; nearest crucial part
  • Modes 436 select a specific part:
    • 4 WheelFL
    • 5 WheelFR
    • 6 WheelRL
    • 7 WheelRR
    • 8 AxleFL
    • 9 AxleFR
    • 10 AxleRL
    • 11 AxleRR
    • 12 Engine
    • 13 Driveshaft
    • 14 SuspensionFL
    • 15 SuspensionFR
    • 16 SuspensionRL
    • 17 SuspensionRR
    • 18 Hood
    • 19 Trunk
    • 20 BumperFront
    • 21 BumperRear
    • 22 LicensePlateFront
    • 23 LicensePlateRear
    • 24 FenderFL
    • 25 FenderFR
    • 26 DoorL
    • 27 DoorR
    • 28 DoorRL
    • 29 DoorRR
    • 30 TurnSignalFL
    • 31 TurnSignalFR
    • 32 HeadlightL
    • 33 HeadlightR
    • 34 TaillightL
    • 35 TaillightR
    • 36 WindshieldWipers

Multi-output accessor warnings (current limitation)

  • CarGetPart: only output Transform1 currently wires up correctly. Using the health output (published as Float2) in another node currently raises KeyError: 'Float2'.
  • CarInfo: only output Transform1 currently wires up correctly. Using Vector32 / Bool3 / Bool4 / Float5 / Float6 in another node currently raises KeyError (commonly Vector32 / Bool3 / Bool4 / Float5 / Float6).
  • The same limitation applies in RacingV2 graphs that use CarInfo / CarGetPart — prefer .CarTransform / .PartTransform plus RacingV2GetFloat / sensors instead of multi-output component reads.

Minimal complete Demo Derby example

This is the smallest end-to-end derby bot. It chases the nearest active car, aims for that car's nearest crucial part, commits to the throttle, and brakes if something is right in front of the bumper but the real target is still far away.

from AIGamePyLibrary import *

# 1. Cosmetics + LLM flag (positional args; capture the returned node).
props = InitializeDemoDerby(
    "MyBot",                    # name
    "Gemini",                   # country (LLM persona names are valid)
    "Tan",                      # skin color
    0,                          # body style
    2,                          # hair style
    "Brown",                    # hair color
    0,                          # facial hair style
    "Blue",                     # car color
    "",                         # custom outfit URL (empty)
)
props.data["modifier"] = "True"   # mark this bot as LLM-driven

# 2. Self position (Vector3) for distance checks.
self_pos = RelativePosition(DemoDerbyGetTransform(0), "Self")

# 3. Pick a target: nearest active opponent, aim at its nearest crucial part.
target      = DemoDerbyGetCar(8)                     # 8 = nearest active
target_tf, _target_part_health = CarGetPart(3, target)  # 3 = nearest crucial part
goal                          = RelativePosition(target_tf, "Self")

# 4. Forward sensor for emergency braking against scenery.
sensor          = Spherecast(1.2, 12.0)
_, ray_f, *_    = CarRaycasts(sensor)
front_hit, front_dist = HitInfo(ray_f)

goal_dist = Distance(self_pos, goal)
panic     = front_hit & (front_dist < 2.5) & (goal_dist > 10.0)
brake     = ConditionalSetFloat(panic, 1.0, 0.0)

# 5. Drive: Autosteer / Autothrottle do the heavy lifting.
ModularUniformController(
    Autothrottle(goal, 25.0),   # throttle
    Autosteer(goal),            # steering
    brake,                      # brake
)

# 6. Save (without this, nothing is exported).
SaveData("MyBot", "auto")

Tips

  • Raycasts originate at the car center (rough width 6, length 10.8, same as Parking / RacingV2).
  • JSON node id for auto throttle is Autothrottle (not AutoThrottle).
  • Shared modular stack also powers RacingV2; use RacingV2Get* for race state and InitializeRacingV2 for cosmetics + stats.

Soccer Simulation
Overview

Team soccer (4 players per team graph). Control each player with move-to, sprint, and interact (shoot / tackle). Match flow includes kickoff, play, goals, overtime, and whistle (stale ball).

Save folder: Soccer/

Requirements
  • Drive players with SoccerController(player, moveTo, sprint, interact) for players 14 (Unity keys SoccerController14)
  • Optional sensors: SoccerPlayerSensors(player, spherecast) → eight RaycastHits labeled A–H on the Player Sensor node
  • Team cosmetics / faceoff: ConstructSoccerProperties(...) / InitializeSoccer(...)
  • World state: SoccerGetBool / SoccerGetFloat / SoccerGetVector3 / SoccerGetTransform
Nodes
Node Purpose Inputs Outputs Options / Notes
SoccerController(player, moveTo, sprint, interact) Controls team player player (1–4)
  • Vector31Move-to world position
  • Bool1Sprint
  • Bool2Interact (shoot / tackle)
Destination. Hold interact to charge a shot (with ball) or attempt tackle (without). See Details.
SoccerPlayerSensors(player, spherecast) Eight-way spherecasts around player player (1–4) RaycastHit18 (letters AH) Aligns with the Player Sensor graphic
ConstructSoccerProperties(name, country, faceoff1..4) Team name, country, and faceoff positions String, Country, Vector3 ×4 Destination. Or use InitializeSoccer(...)
SoccerGetBool(value) Bool world state Bool1 Index or label — see all values
SoccerGetFloat(value) Float world state Float1 Index or label — see all values
SoccerGetTransform(value) Key transforms Transform1 Index or label — see all values
SoccerGetVector3(value) Directions, landmarks, open-player picks Vector31 Index or label — see all values
Details

Tackles

A tackle contest subtracts the stamina delta between the tackling player and the ball carrier from both players. After that drain, if the tackling player has more stamina (or equal), they win the ball.

Shooting (interact release)

If a player has the ball and there is shot charge, setting interact (Bool2) to false releases the shot — the ball is kicked in the direction of that frame’s movement input.

Hold interact while with the ball to charge; without the ball, interact attempts a tackle / pickup.

Vector3 directional checks

SoccerGetVector3 options that search clear / directional views check the 8 player spherecast directions and return the first valid direction meeting the criteria (or null if none). Letters match the Player Sensor graphic (AH).

Search order prioritizes the team’s attacking direction:

  • Home: E, C, H, B, G, A, F, D
  • Away: D, F, A, G, B, H, C, E

Kickoff

  • Opening kickoff receiving team is random
  • After a goal, the team that was scored on receives the next kickoff
  • In extra time, the receiving team is the opposite of who kicked off at match start
  • On whistle (stale ball / no meaningful movement after time), kickoff flips to the opposite of who last received a kickoff

Constants

Live values from the Soccer scene / player prefab. Several are also exposed as SoccerGetFloat labels (listed in parentheses).

Field

  • Field width (sideline to sideline): 50 ("Field Width")
  • Field depth (goal line to goal line): 80 ("Field Depth")
  • Kickoff / center circle radius: 7.25 ("Kickoff Circle Radius")
  • Goal width: 11.4 ("Goal Width")
  • Goal height: 4 ("Goal Height")
  • Players per team: 4

Movement

  • Walk / run speed: 7
  • Sprint speed: 8 (requires available stamina)

Stamina

  • Max stamina: 100
  • Sprint consume: 0.15 per simulation tick while sprinting
  • Regen rate: 5 stamina per second (after the regen delay)
  • Default regen delay (cooldown): 1 second
  • Tackle / contested-steal regen delay: 1.5 seconds (applied to both players in the contest)

Match & interaction

  • Match duration: 180 seconds of playing time
  • Kickoff restriction delay: 1 second (or until first touch)
  • Player interact / tackle / pickup radius: 1.75 ("Player Interact Radius")
  • Shot charge rate: 0.1 per tick; min charge to shoot 0.1; max charge time 2 s
  • Shot strength range: 0.515 (plus max lift 3)
  • Shot cooldown: 0.5 s
  • Pickup charge delay after gaining the ball: 0.3 s
  • Stale-ball whistle: ball stays within 2.5 of its anchor for 5 s

Ball physics (from SoccerBall prefab + project Physics settings)

  • Mass: 0.45
  • Linear damping: 0
  • Angular damping: 0
  • Use gravity: yes (project gravity is (0, −20, 0) — heavier than Unity’s default −9.81)
  • Max linear speed clamp: 30 m/s
  • Rigidbody rotation: frozen (rolling is visual only)
  • Collision detection: Discrete
  • Sphere collider radius: ~0.406 world units (prefab radius 0.4515 × scale 0.9)
  • Physics Material on ball / typical field colliders: none assigned
    • Project default material is also none, so contacts use Unity’s built-in defaults: dynamic/static friction 0.6, bounciness 0
    • Bounce threshold: 2
  • Shot impulse: release sets velocity = force / mass (then clamped to max speed); upward shot bias component 3
  • Pickup lockout after shot (shooter only): 0.125 s; after possession exchange: 0.25 s

Pass a dropdown index (0, 1, …) or the exact Unity label string below. Order matches Unity and DROPDOWN_OPTIONS in data.py.

SoccerGetBool values

"Team Has Ball", "Opponent Has Ball", "Is Ball Loose", "Team Player 1 Has Ball", "Team Player 2 Has Ball", "Team Player 3 Has Ball", "Team Player 4 Has Ball", "Opponent Player 1 Has Ball", "Opponent Player 2 Has Ball", "Opponent Player 3 Has Ball", "Opponent Player 4 Has Ball", "Is Ball Nearby Team Player 1", "Is Ball Nearby Team Player 2", "Is Ball Nearby Team Player 3", "Is Ball Nearby Team Player 4", "Is Ball Nearby Opponent Player 1", "Is Ball Nearby Opponent Player 2", "Is Ball Nearby Opponent Player 3", "Is Ball Nearby Opponent Player 4", "Is Team Player 1 Closest Teammate to Ball", "Is Team Player 2 Closest Teammate to Ball", "Is Team Player 3 Closest Teammate to Ball", "Is Team Player 4 Closest Teammate to Ball", "Is Opponent Player 1 Closest Opponent to Ball", "Is Opponent Player 2 Closest Opponent to Ball", "Is Opponent Player 3 Closest Opponent to Ball", "Is Opponent Player 4 Closest Opponent to Ball", "Is Team Player 1 Open", "Is Team Player 2 Open", "Is Team Player 3 Open", "Is Team Player 4 Open", "Is Opponent Player 1 Open", "Is Opponent Player 2 Open", "Is Opponent Player 3 Open", "Is Opponent Player 4 Open", "Is Kickoff", "Is Team Kicking off", "Is Opponent Kicking off", "Team Is Winning", "Opponent Is Winning", "Team Scored Last Point", "Opponent Scored Last Point", "Ball On Team Side", "Ball On Opponent Side", "Is Ball Headed Towards Team Goal", "Is Ball Headed Towards Opponent Goal", "Is Home Team", "Is Away Team", "Is Active Graph"

SoccerGetFloat values

"Team Score", "Opponent Score", "Team Shots", "Opponent Shots", "Team Possession %", "Opponent Possession %", "Team Attacking %", "Opponent Attacking %", "Ball Speed", "Player Interact Radius", "Player With Ball Shot Charge %", "Ball Carrier Stamina", "Ball Carrier Shot Charge", "Teammate 1 Shot Charge", "Teammate 2 Shot Charge", "Teammate 3 Shot Charge", "Teammate 4 Shot Charge", "Field Width", "Field Depth", "Kickoff Circle Radius", "Goal Width", "Goal Height", "Team Player 1 Stamina", "Team Player 2 Stamina", "Team Player 3 Stamina", "Team Player 4 Stamina", "Distance from Team Player 1 to nearest Opponent", "Distance from Team Player 2 to nearest Opponent", "Distance from Team Player 3 to nearest Opponent", "Distance from Team Player 4 to nearest Opponent", "Opponent Player 1 Stamina", "Opponent Player 2 Stamina", "Opponent Player 3 Stamina", "Opponent Player 4 Stamina", "Opponent Nearest Teammate Player 1 Stamina", "Opponent Nearest Teammate Player 2 Stamina", "Opponent Nearest Teammate Player 3 Stamina", "Opponent Nearest Teammate Player 4 Stamina", "Stamina of last defending opponent", "Current Simulation Time", "Max Simulation Time", "Simulation Time Remaining", "Delta Time", "Fixed Delta Time", "Pi"

SoccerGetTransform values

"Ball", "Team Player 1", "Team Player 2", "Team Player 3", "Team Player 4", "Opponent Player 1", "Opponent Player 2", "Opponent Player 3", "Opponent Player 4", "Teammate Nearest Team Player 1", "Teammate Nearest Team Player 2", "Teammate Nearest Team Player 3", "Teammate Nearest Team Player 4", "Opponent Nearest Team Player 1", "Opponent Nearest Team Player 2", "Opponent Nearest Team Player 3", "Opponent Nearest Team Player 4", "Team Goal Center", "Team Goal Left Post", "Team Goal Right Post", "Opponent Goal Center", "Opponent Goal Left Post", "Opponent Goal Right Post", "Opponent Nearest Team Goal", "Opponent Nearest Opponent Goal", "Teammate Nearest Team Goal", "Teammate Nearest Opponent Goal"

SoccerGetVector3 values

"Ball Velocity", "Clear direction from team carrier", "Backwards clear direction from team carrier", "Clear direction from team carrier (avoid goal lines)", "Clear direction from team carrier (avoid sidelines)", "Clear direction from team carrier (avoid all walls)", "Upper Corner Home Side", "Lower Corner Home Side", "Upper Midfield", "Lower Midfield", "Upper Corner Away Side", "Lower Corner Away Side", "Upper Corner Opposing Side", "Lower Corner Opposing Side", "Upper Corner Team Side", "Lower Corner Team Side", "Center Field", "Get nearest open teammate", "Get furthest open teammate", "Get most open teammate", "Get nearest open opponent", "Get furthest open opponent", "Get most open opponent", "Direction of clear teammate from Teammate 1", "Direction of clear teammate from Teammate 2", "Direction of clear teammate from Teammate 3", "Direction of clear teammate from Teammate 4", "Direction of clear teammate from Opponent 1", "Direction of clear teammate from Opponent 2", "Direction of clear teammate from Opponent 3", "Direction of clear teammate from Opponent 4", "Direction of ball from Teammate 1", "Direction of ball from Teammate 2", "Direction of ball from Teammate 3", "Direction of ball from Teammate 4", "Direction of ball from Opponent 1", "Direction of ball from Opponent 2", "Direction of ball from Opponent 3", "Direction of ball from Opponent 4", "Direction of team goal from Teammate 1", "Direction of team goal from Teammate 2", "Direction of team goal from Teammate 3", "Direction of team goal from Teammate 4", "Direction of opponent goal from Teammate 1", "Direction of opponent goal from Teammate 2", "Direction of opponent goal from Teammate 3", "Direction of opponent goal from Teammate 4", "Clear direction from Teammate 1", "Clear direction from Teammate 2", "Clear direction from Teammate 3", "Clear direction from Teammate 4", "Direction of teammate from Team Player 1", "Direction of teammate from Team Player 2", "Direction of teammate from Team Player 3", "Direction of teammate from Team Player 4"

Tips

  • During kickoff restriction, only the kicking-off team gets graph control; non-kickoff players are kept outside the center circle until first touch / delay expires.
  • Convert transforms to positions with RelativePosition(transform, "Self").

RacingV2 Simulation
Overview

Lap racing on additive V2 tracks using the modular car stack. Default 3 laps; cars can DNF on no waypoint progress; modular part damage still applies.

Save folder: RacingV2/

Requirements
  • Drive with ModularUniformController(throttle, steering, brake) (same as Parking / Demo Derby)
  • Cosmetics + 20-point stats: ConstructRacingV2Properties(...) / InitializeRacingV2(...) (not UniformModularCarProperties)
  • Sensors: SpherecastCarRaycastsHitInfo (shared stack)
  • Optional helpers: Autosteer, Autothrottle, CarGetPart, CarInfo (same multi-output wiring caveat as Demo Derby)
  • Race state: RacingV2GetFloat / RacingV2GetBool / RacingV2GetCar / RacingV2GetWaypoint / RacingV2Waypoint
Nodes
Node Purpose Inputs Outputs Options / Notes
InitializeRacingV2(..., speed, turn, health) Cosmetics + Stat1/2/3 (20-point budget → speed / turn / health) Same cosmetics as Parking, plus three Stat inputs Unity key ConstructRacingV2Properties
RacingV2GetFloat(value) Speed, waypoints, rank, laps, sim time, … Float1 Index or label (see DROPDOWN_OPTIONS)
RacingV2GetBool(value) Grounded / disabled / sim started Bool1 Is Grounded, Is Disabled, Simulation started
RacingV2GetCar(mode, index_float=None) Select a car (modes 0–26, same shape as DemoDerbyGetCar) Optional Float for by-index / by-rank Car1 Rank uses race scoreboard
RacingV2GetWaypoint(value, index_float=None) Next / Previous / By index / Start waypoint Optional Float for By index Waypoint1 Feed into RacingV2Waypoint
RacingV2Waypoint(mode, waypoint, ref=None) Resolve a waypoint to a world point Waypoint (+ optional Transform for Nearest) .Position (Vector3), .Index (Float) Center / Left / Right / Nearest point

Shared modular nodes are documented under Parking Simulation and Demo Derby Simulation (ModularUniformController, CarRaycasts, Autosteer, Autothrottle, …).

Details

Stats

ConstructRacingV2Properties takes Stat1 (speed), Stat2 (turn), Stat3 (health) with a sequential 20-point budget (typical mapping: max speed ~20–60, turn ~0.5–2.0, health fraction of baseline).

RacingV2GetFloat options

Speed, Signed Speed (forward +, reverse −), waypoint indices/distances, Current race rank, Number of competitors, lap times, Current lap / Total laps, sim time, Delta Time, Pi, …

Tips

  • Prefer Autosteer(goal) + Autothrottle(goal, speed) toward RacingV2Waypoint(...).Position.
  • Avoid CarInfo / CarGetPart non-Transform outputs (same KeyError limitation as Demo Derby).
  • Older Racing.unity / Kart nodes are a different stack — use RacingV2 helpers for this scene.

Minimal RacingV2 sketch

from AIGamePyLibrary import *

props = InitializeRacingV2(
    "Racer", "United States of America", "Tan", 0, 0, "Brown", 0, "Red", "",
    7, 7, 6,
)
wp = RacingV2GetWaypoint("Next waypoint")
goal = RacingV2Waypoint("Center", wp).Position
ModularUniformController(Autothrottle(goal, 30.0), Autosteer(goal), Float(0.0))
SaveData("RacingV2/AIComp_Data/Saves/racer.txt", "grid")

Volleyball Simulation
Overview

Volleyball bots control a slime character to move and jump based on the current world state (self/opponent/ball positions and velocities, scores, etc.).

Requirements
  • Drive the slime via the destination node: SlimeController(targetPosition, jumpCondition)
  • Read world state via sim-prefixed helpers: VolleyballGetVector3 / VolleyballGetTransform / VolleyballGetBool / VolleyballGetFloat
Nodes
Node Purpose Inputs Outputs Options / Notes
VolleyballGetVector3(value) World-space vectors (Unity node type id: SlimeGetVector3)
  • Vector31The resulting value from the selection
Values
VolleyballGetTransform(value) Transform references (convert to Vector3 via RelativePosition(tf, \"Self\")) Values
VolleyballGetBool(value) Boolean state
  • Bool1The selected value
Values
VolleyballGetFloat(value) Scalar state Values
InitializeSlime(name, color, country, speed, acceleration, jump) Initialize your slime bot with the specified properties See ConstructSlimeProperties(...) below Convenience helper
SlimeController(targetPosition, jumpCondition) Drive the slime (movement + jump) Destination node
ConstructSlimeProperties(name, color, country, speedStat, accelerationStat, jumpStat) Low-level slime cosmetics + stat initialization
  • String1Name tag
  • Color1Slime color
  • Country1Country / persona
  • Stat1Speed stat
  • Stat2Acceleration stat
  • Stat3Jump stat
Destination node
RelativePosition(transform, direction) Convert a Transform reference into a world-space Vector3 Self, Self + Forward, Self + Backward, Self + Left, Self + Right, Self + Up, Self + Down, Forward, Backward, Left, Right, Up, Down, World
Stat(value) Create a Stat node (used for slime properties)
  • Stat1The stat
value can be int or str
Color(value) Create a Color node See values
Country(value) Create a Country node
  • Country1The selected country / persona
See values
Details

VolleyballGetVector3 values

"Self Position", "Self Velocity", "Opponent Position", "Opponent Velocity", "Ball Position", "Ball Velocity"

VolleyballGetTransform values

"Self", "Opponent", "Ball", "Self Team Spawn", "Opponent Team Spawn"

VolleyballGetBool values

"Self Can Jump", "Opponent Can Jump", "Ball Is Self Side"

VolleyballGetFloat values

"Delta time", "Fixed delta time", "Gravity", "Pi", "Simulation duration", "Team score", "Opponent score", "Ball touches remaining"

RelativePosition directions

"Self", "Self + Forward", "Self + Backward", "Self + Left", "Self + Right", "Self + Up", "Self + Down", "Forward", "Backward", "Left", "Right", "Up", "Down", "World"

Color values

"Auburn", "Black", "Blonde", "Blue", "Brown", "Dark Brown", "Dark Green", "Green", "Hot Pink", "Light Blue", "Light Grey", "Medium Grey", "Orange", "Pink", "Purple", "Red", "Tan", "White", "Yellow"

Country values

See the full list in the Country node docs above (same list used across simulations).

Access rules (important)

  • There are no Unity-style dotted shortcuts. Always use the sim-prefixed helpers.
  • The unprefixed aliases (GetVector3 / GetTransform / GetBool / GetFloat) are Volleyball-only backward-compat; prefer the explicit VolleyballGet* names.
  • Transform → world position: RelativePosition(transform_node, "Self").

Saving Your AI

SaveData Function
  • SaveData(filePath, layout="auto", pruneUnusedNodes=True, keepPosition=True, optimize="normal", verbose=False)
    • Saves the AI data to a JSON file that can be imported into Unity
    • filePath: Path to save the file
    • layout: Layout mode
      • "auto" - Topological layout (recommended)
      • "grid" - Grid-based layout
      • "single" - All nodes at origin
      • "hidden" - Nodes positioned off-screen
      • None - No layout changes
    • pruneUnusedNodes: Remove nodes that aren't connected (default: True)
    • keepPosition: Preserve manually set node positions (default: True)
    • optimize: How hard to compile the graph before saving (see below)
    • verbose: Print optimiser strip/prune counts (default: False)

optimize="release" (shipping / compact builds)

Every node in the saved graph evaluates every tick in-engine, so fewer nodes means both a smaller file and less per-tick work. Pass optimize= to SaveData to choose how aggressively to prune:

Value What it does
"normal" (default) Safe prune only: drop unreachable nodes and SetVariable writes that no GetVariable reads. Keeps every Debug* / TimePlot sink so the graph stays observable while you develop.
"release" Also strips every Debug* / TimePlot sink, then re-prunes to a fixpoint so anything that only fed debug output is removed too. Smallest graph; fewest per-tick evaluations.
# Development: keep DebugDraw / TimePlot so you can inspect the graph
SaveData("MyBot_debug.txt", layout="grid", optimize="normal")

# Shipping: strip debug sinks and dead subgraphs that only fed them
SaveData("MyBot.txt", layout="grid", optimize="release", verbose=True)

Assumption: "release" assumes debug/plot nodes are outputs only — they must not also feed a controller or decision. That holds for typical bots, but verify before shipping if your graph wires debug helpers into gameplay logic.

Positional layout still works (SaveData("MyBot", "grid")); use the keyword when you want release:

SaveData("MyBot.txt", "grid", optimize="release")

Optimizing an existing bot JSON (no Python source)

There is no general decompiler from Unity graph JSON back into Python. You do not need one: the optimiser works on the JSON graph itself.

from AIGamePyLibrary import OptimizeFile, LoadData, SaveData

# Compact an editor-made / already-exported bot (default optimize="release")
OptimizeFile("Haialand-v2.txt", "Haialand-v2_release.txt", verbose=True)

# Or overwrite in place
OptimizeFile("MyBot.txt", verbose=True)

# Or load → tweak in memory → save yourself
LoadData("MyBot.txt")
SaveData("MyBot_compact.txt", layout=None, optimize="release")

leaner=True (headless / size-max; parity-checked)

After Lean prepare, also drop fields the soccer sim does not need to decide:

  • connection sID / instance IDs
  • port nodeSID
  • all serializableRectTransform layout chrome
  • serialize/color flags, empty modifier / ownerFunctionSID
OptimizeFile("Titanium.txt", "Titanium_leaner.txt", optimize="normal",
             pruneUnusedNodes=False, leaner=True)
# or while building:
SaveData("MyBot.txt", layout="grid", optimize="release", leaner=True)

Do not drop real modifier values or port polarity — those break load / dropdown resolution. leaner graphs may not round-trip cleanly through the Unity node editor (layout/chrome removed).

Example: Advanced Bot

from AIGamePyLibrary import *

# Initialize bot
InitializeSlime("MyBot", "Blue", "Canada", 6, 4, 3)

# Pull world state from the graph helpers (no dotted accessors anywhere).
ball_position = VolleyballGetVector3("Ball Position")
self_position = VolleyballGetVector3("Self Position")

# Calculate direction to ball
directionToBall = ball_position - self_position
distanceToBall  = Magnitude(directionToBall)

# Normalize direction and add offset
normalizedDir = Normalize(directionToBall)
targetOffset  = normalizedDir * 0.3
moveTo        = ball_position + targetOffset

# Jump when close to ball and ball is above us (component access on Vector3 Nodes)
ballAbove     = ball_position.y > self_position.y
closeToBall   = distanceToBall < 2.0
jumpCondition = closeToBall & ballAbove

# Control the slime
SlimeController(moveTo, jumpCondition)

# Save with auto layout
SaveData("my_bot.txt", "auto")

Tips

  1. Use Python operators: Instead of calling AddFloats(a, b), use a + b for cleaner code
  2. Node caching: Functions automatically cache nodes with the same inputs for efficiency
  3. Layout options: Use "auto" for clean topological layouts, "grid" for grid-based layouts
  4. Debugging: Use Debug(value) to inspect node values during development
  5. Vector components: Access vector components via .x, .y, .z properties on Vector3 nodes

File Output

The SaveData function generates a JSON file that can be imported into Unity for use in AIA's games. The file contains all the node connections and logic you've defined in Python.


Data types

This section is the “type dictionary” that port keys link to (for example: clicking Float1 jumps you here).

Float

  • Type: Float
  • Ports: Float1, Float2, Float3, ...
  • Meaning: A numeric scalar value.
Bool

  • Type: Bool
  • Ports: Bool1, Bool2, ...
  • Meaning: A true/false scalar value.
String

  • Type: String
  • Ports: String1, String2, ...
  • Meaning: A text value.
Color

  • Type: Color
  • Ports: Color1, Color2, ...
  • Meaning: A dropdown-selected color value.
Country

  • Type: Country
  • Ports: Country1, Country2, ...
  • Meaning: A dropdown-selected country value.
RaycastHit

  • Type: RaycastHit
  • Ports: RaycastHit1, RaycastHit2, ...
  • Meaning: A ray/spherecast hit result container (used by sensors).
Spherecast

  • Type: Spherecast
  • Ports: Spherecast1, Spherecast2, ...
  • Meaning: A sensor definition that configures the size/length of spherecast sensors.
Any

  • Type: Any
  • Ports: Any1, Any2, ...
  • Meaning: Generic “wildcard” type. Carries through whatever concrete type you connect.
Vector3

  • Type: Vector3
  • Ports: Vector31, Vector32, ...
  • Meaning: A 3D vector. Supports .x, .y, .z component access in the graph.
Transform

  • Type: Transform
  • Ports: Transform1, Transform2, ...
  • Meaning: A Unity Transform reference (position/rotation container). Use RelativePosition(transform, "Self") to convert to a world-space Vector3.
SurvivalState

  • Type: SurvivalState
  • Ports: SurvivalState1, SurvivalState2, ...
  • Meaning: Survival simulation state enum.
SurvivalEmote

  • Type: SurvivalEmote
  • Ports: SurvivalEmote1, SurvivalEmote2, ...
  • Meaning: Survival simulation emote enum.
Car

  • Type: Car
  • Ports: Car1, Car2, ...
  • Meaning: Car reference for Demo Derby / RacingV2 (selected via DemoDerbyGetCar(...) / RacingV2GetCar(...) or derived from a Transform).

Notes for LLM Authors

Marking your bot as LLM-driven

The car / kart Properties node carries an isLLM flag that is not exposed in the Unity inspector — it can only be set by the PyLib compiler. At runtime Unity reads it from the node's modifier field during Initialize(), writes it onto the resulting properties (KartProperties / modular car properties), and applies it to the spawned Player (Player.IsLLM = true).

To set it, capture the Properties node returned by any of the helpers and assign its modifier after construction. Accepted values: "True" / "False" or "1" / "0" (anything else falls back to False).

from AIGamePyLibrary import *

# Works for InitializeDemoDerby, InitializeParking, and ConstructModularUniformProperties —
# they all return the same UniformModularCarProperties node.
# For RacingV2, use InitializeRacingV2 / ConstructRacingV2Properties the same way (modifier on the returned node).
props = InitializeDemoDerby(
    "MyBot", "United States of America", "Tan",
    0, 0, "Brown", 0, "Red", "",
)
props.data["modifier"] = "True"   # mark this car as LLM-driven

isLLM is persisted in the saved JSON through the standard modifier field, so the graph round-trips through Unity without losing the flag. Equivalent behavior is wired up on the C# side for ConstructKartProperties (Kart-style simulations) and RacingV2 properties — same modifier accepted values.

This section consolidates everything an LLM (or anyone new to the library) needs to avoid the most common mistakes. Read it in full before writing a script.

🛑 Before you submit a bot, scan it for these six accessor patterns and delete every one of them — they all currently crash in ConnectPorts with a KeyError and are the #1 thing we keep seeing LLMs burn on:

  • CarInfo(...).VelocityKeyError: 'Vector32'
  • CarInfo(...).IsAIKeyError: 'Bool3'
  • CarInfo(...).IsImmobileKeyError: 'Bool4'
  • CarInfo(...).HealthKeyError: 'Float5'
  • CarInfo(...).RankKeyError: 'Float6'
  • CarGetPart(...).HealthPercentKeyError: 'Float2'

That includes the sneaky tuple-unpacking form (car_tf, velocity, is_ai, is_immobile, health, rank = CarInfo(car) — the unpacked vars are the same broken nodes) and any arithmetic/comparison that funnels them into another node (Magnitude(v), v * dt, health < 50, ConditionalSetFloat(is_immobile, ...), etc.). Only .CarTransform and .PartTransform are safe. See Multi-output component accessor bug for replacements.

The mental model

This is a graph compiler, not a runtime game SDK. Your script does not drive the car/slime/Aialander frame-by-frame. It builds a static node graph once, and SaveData(...) writes that graph to JSON. Unity loads the JSON and re-evaluates the graph every tick.

Concretely:

  • Every value you compose (VolleyballGetVector3("Self Position"), Distance(...), props, sensor outputs, etc.) is a Node object, not a number, string, list, or dict.
  • Plain Python if / while / for, min / max / sorted, lambda, list/dict indexing, and import math calls do not become nodes. They run once at compile time and are gone. Use the graph equivalents: ConditionalSetFloat / ConditionalSetVector3 / ConditionalSetBool for branching, Operation(x) for math, DemoDerbyGetCar / SurvivalGetTransform to "pick" entities, etc.
  • Nodes have no Unity-style dotted accessors. There is no something.Position / something.Velocity / something.Transform / something.DeltaTime anywhere in this library. To read world state you always call a simulation-prefixed helper that matches the Unity asset for that sim:
    • Volleyball → VolleyballGetVector3(...) / VolleyballGetTransform(...) / VolleyballGetBool(...) / VolleyballGetFloat(...) (Vector3 node type in Unity: SlimeGetVector3; other nodes: VolleyballGetTransform, etc. — see Assets/_Nodes/).
    • Survival → SurvivalGetTransform(...) / SurvivalGetFloat(...) / SurvivalGetBool(...).
    • Parking → ParkingGetTransform(...) / ParkingGetFloat(...) / ParkingGetBool(...).
    • Demo Derby → DemoDerbyGetTransform(...) / DemoDerbyGetCar(...) / CarGetPart(...).PartTransform / CarInfo(...).CarTransform.
    • RacingV2 → RacingV2GetFloat(...) / RacingV2GetBool(...) / RacingV2GetCar(...) / RacingV2GetWaypoint(...) / RacingV2Waypoint(...) plus the shared modular car stack (ModularUniformController, CarRaycasts, …). Properties: InitializeRacingV2 / ConstructRacingV2Properties.
    • Soccer → SoccerGetBool(...) / SoccerGetFloat(...) / SoccerGetTransform(...) / SoccerGetVector3(...), with per-player SoccerController(1..4, …) and SoccerPlayerSensors(1..4, …).
    • The unprefixed aliases (GetVector3 / GetTransform / GetBool / GetFloat) are Volleyball only — they exist purely for backward-compat with old scripts. Using them in any other sim's graph produces the wrong Unity node and won't deserialize correctly.
    • Custom Functions (CreateFunction / CustomFunction) are reusable Unity subgraphs: params in, return out via SetFunctionReturn(fn, body_output) (e.g. Power.Float1 → Return Any1 In). Body nodes only run when called — not the same as Python customNodes.py helpers. See Default Nodes → Organization → Custom Functions.
    • To turn any Transform node into a world-space Vector3, wrap it in RelativePosition(transform_node, "Self").
  • Save folders by sim include Soccer/, RacingV2/, DemoDerby/, Parking/, etc.
  • The Initialize* helpers take positional arguments, not keyword arguments. There is no name=, country=, modifier_llm=, save_file= etc. See each simulation's section for the exact signature.
  • There is no sim object. Methods like sim.is_active(), sim.get_self_data(), sim.get_opponents(), sim.set_controls(), sim.update() do not exist — if you wrote any of those, you are hallucinating an SDK that isn't here.
  • The AIGamePyLibrary import exposes helpers as free functions, not methods on a module object you treat like a sim runtime. (Aialander PyLib is the same project under that friendly name.)
  • The LLM-driven flag is set on the returned Properties node: props.data["modifier"] = "True". There is no modifier_llm=, is_llm=, llm=, or isLLM= keyword argument on any helper.
  • Your script must end with a call to SaveData("YourBot", "auto") or nothing is exported.

🚨 CRITICAL: Multi-output Bug (KeyError: 'Bool4', 'Vector32', etc.)

The most common error LLMs make when writing Demo Derby or RacingV2 bots is using CarInfo(...).IsImmobile, .Velocity, .Health, .Rank, or CarGetPart(...).HealthPercent in ConditionalSetFloat, comparisons, arithmetic, etc.

# These ALL fail with KeyError in ConnectPorts:
is_stuck = CarInfo(self_car).IsImmobile                    # → 'Bool4'
throttle = ConditionalSetFloat(is_stuck, -1.0, throttle_fwd)
# or: Magnitude(CarInfo(car).Velocity) → 'Vector32'

Fix:

  • Only use .CarTransform and .PartTransform from these helpers.
  • For stuck detection use forward raycast sensors (HitInfo(ray_f)) as shown in Grok.py.
  • See full details in Multi-output component accessor bug below.

Grok.py (and Claude.py) have been updated with a working sensor-based unstick workaround. Older versions will hit this exact error.

Common mistakes

These are the exact mistakes we keep seeing. If your draft does any of them, rewrite it before saving.

❌ Don't ✅ Do
import AIGamePyLibrary as aig then aig.InitializeDemoDerby(...) (treats helpers as methods on a sim object) from AIGamePyLibrary import * and call helpers as free functions
InitializeDemoDerby(name="X", country="USA", modifier_llm=True, save_file="X") props = InitializeDemoDerby("X", "United States of America", "Tan", 0, 0, "Brown", 0, "Red", "") — positional only
country="USA" / country="UK" / country="South-Korea" Use the exact strings from the Country list, e.g. "United States of America", "United Kingdom", "South Korea". Bot personas: "ChatGPT", "Claude", "Deepseek", "Gemini", "Grok", "Llama", "Mistral", "Perplexity", "Qwen"
Pass modifier_llm=True / is_llm=True / llm=True to any helper After init: props.data["modifier"] = "True" (see Marking your bot as LLM-driven)
Pass save_file="X" to any helper Saving is a separate call at the end of the script: SaveData("X", "auto")
while sim.is_active(): sim.set_controls(...) style runtime loop Build the graph once. Unity runs it every tick. There is no loop in your script.
sim.get_self_data(), sim.get_opponents(), sim.set_controls(), sim.update(), sim.is_active() None of these exist. Use DemoDerbyGetTransform, DemoDerbyGetCar, CarInfo, CarRaycasts, ModularUniformController, etc.
if dist < 50: throttle = 1.0 else: throttle = 0.5 throttle = ConditionalSetFloat(dist < 50, 1.0, 0.5)
min(opponents, key=lambda o: ...) / iterating Python lists of game entities There is no Python-side list of opponents. Use selector nodes like DemoDerbyGetCar(8) (nearest active), CarGetPart(3, car) (nearest crucial part), SurvivalGetTransform(3) (player nearest), etc.
math.atan2(...), math.sqrt(...), math.degrees(...) Operation(x) (atan, sqrt, etc.), Magnitude, Distance, DotProduct, Normalize, or just rely on Autosteer(goal) / Autothrottle(goal, speed) for car driving
2D thinking: pos[0], pos[1], heading in degrees Everything is 3D Vector3. Access components via vec.x, vec.y, vec.z. There is no scalar "heading". Use Autosteer for car aim.
transform.Position / transform.position on a Transform node RelativePosition(transform_node, "Self") returns the world Vector3
Self.Position / Ball.Position / entity.Velocity / Self.TeamSpawn / Game.DeltaTime — dotted accessors on a game entity Use the simulation-prefixed node helpers everywhere. Volleyball: VolleyballGetVector3("Self Position"), VolleyballGetVector3("Ball Velocity"), VolleyballGetTransform("Self Team Spawn"), VolleyballGetBool("Self Can Jump"), VolleyballGetFloat("Delta time"). Other sims have their own helpers (DemoDerbyGetTransform, DemoDerbyGetCar, CarGetPart(...).PartTransform, SurvivalGetTransform, ParkingGetTransform, etc.). Never assume .Position / .Velocity / .Transform exists on a Node — it does not, and examples that used to show that shortcut have been rewritten.
GetTransform(...) / GetBool(...) / GetFloat(...) / GetVector3(...) used in Survival / Parking / Demo Derby / Soccer / RacingV2 graphs Those unprefixed names are Volleyball-only backward-compat aliases. In other sims you'll silently build the wrong Unity node. Use the sim prefix: SurvivalGetTransform / ParkingGetTransform / DemoDerbyGetTransform / SoccerGetTransform / RacingV2GetFloat, etc.
Treating CreateFunction like Region, or like Python customNodes.py Custom Functions change execution: body nodes only run via CustomFunction(...) calls; params/optional return are the interface. Region is visual-only.
Building Power / math inside CreateFunction but forgetting the return wire Always call SetFunctionReturn(fn, powered) so Power.Float1 connects to CreateFunction Return (Any1 In). Without it, CustomFunction(...) output is null.
Wiring Return to port id Any Return is Any1 polarity In (not Any). Param1 Out is also Any1 — polarity distinguishes them.
One SlimeController / single controller for Soccer Soccer needs SoccerController(1..4, moveTo, sprint, interact) per player on the team graph
Magnitude(CarInfo(car).Velocity), ClampFloat(CarInfo(car).Health, ...), pos + CarInfo(car).Velocity * dt, ConditionalSetFloat(CarGetPart(3, car).HealthPercent < 50, ...) Broken in the current library. Only .CarTransform (on CarInfo) and .PartTransform (on CarGetPart) can be passed to another node — everything else raises KeyError: 'Vector32' / 'Bool3' / 'Bool4' / 'Float5' / 'Float6' / 'Float2' in ConnectPorts. Plan your bot around Autosteer / Autothrottle + DemoDerbyGetCar / CarGetPart(3, ...).PartTransform + raycast sensors. See Multi-output component accessor bug.
Forget to call SaveData(...) at the end Always finish with SaveData("YourBotName", "auto") — without this the script does literally nothing

Side-by-side example

Wrong (looks like a runtime loop, none of this works):

import AIGamePyLibrary as aig          # treats helpers as methods on a sim object
sim = aig.InitializeDemoDerby(name="Gemini", country="USA",
                              modifier_llm=True, save_file="Gemini")  # not real kwargs
while sim.is_active():                  # no such method
    me = sim.get_self_data()            # no such method
    for op in sim.get_opponents():      # no such method
        if dist(me, op) < 50:           # plain Python if won't compile to a node
            sim.set_controls(throttle=1.0, steer=0.2)  # no such method
    sim.update()                        # no such method

Right (build a graph, mark it LLM-driven, save it):

from AIGamePyLibrary import *

props = InitializeDemoDerby(
    "Gemini", "Gemini", "Tan", 0, 2, "Brown", 0, "Blue", "",
)
props.data["modifier"] = "True"   # mark as LLM-driven (see "Marking your bot as LLM-driven")

self_pos = RelativePosition(DemoDerbyGetTransform(0), "Self")
target   = DemoDerbyGetCar(8)                                 # nearest active car
goal     = RelativePosition(CarGetPart(3, target).PartTransform, "Self")

ModularUniformController(Autothrottle(goal, 25.0), Autosteer(goal), Float(0.0))
SaveData("Gemini", "auto")

For a slightly fuller derby starter (with sensors and a panic brake) see the Minimal complete Demo Derby example. Grok.py now demonstrates a safe, aggressive ramming strategy with sensor-based unsticking that avoids the KeyError: 'Bool4' (see the new warning at the top of Notes for LLM Authors).

Multi-output component accessor bug

This is a known library bug, not an LLM mistake. It's documented here so LLM authors stop writing scripts that hit it.

What breaks

Any of these will raise a KeyError inside AIGamePyLibrary/lib.py → ConnectPorts at compile time (when SaveData(...) runs, or earlier if the consuming node is constructed first):

# --- all of these crash ---
self_info = CarInfo(DemoDerbyGetCar(2))
self_speed = Magnitude(self_info.Velocity)                          # KeyError: 'Vector32'
is_alive   = self_info.Health > 0                                   # KeyError: 'Float5'
if_imm     = ConditionalSetFloat(self_info.IsImmobile, -1.0, 1.0)   # KeyError: 'Bool4'
rank_ok    = self_info.Rank < 3                                     # KeyError: 'Float6'

# unpacking doesn't save you — the unpacked Nodes are the same broken objects:
car_tf, velocity, is_ai, is_immobile, health, rank = CarInfo(car)
Magnitude(velocity)                                                 # still KeyError: 'Vector32'

# GetCarPart has the same shape of bug:
part = CarGetPart(3, target)
weak = part.HealthPercent < 30                                      # KeyError: 'Float2'

Why it breaks

CarInfoComponents and GetCarPartComponents store each accessor's outputIndex as its global position in the node's port list, but ConnectPorts builds the Unity port name as f"{inputType}{outputIndex}" — which must be the type-local port number. Unity's CarInfo node publishes Transform1, Vector31, Bool1, Bool2, Float1, Float2 (type-local), but the Python wrappers ask for Transform1, Vector32, Bool3, Bool4, Float5, Float6. Only the first port of each multi-output node (Transform1, position 1) lines up by accident.

For reference, here's what each accessor currently does and what port id it asks for:

Accessor outputIndex Port name built Actual Unity port Status
CarInfo(...).CarTransform 1 Transform1 Transform1 ✅ works
CarInfo(...).Velocity 2 Vector32 Vector31 KeyError
CarInfo(...).IsAI 3 Bool3 Bool1 KeyError
CarInfo(...).IsImmobile 4 Bool4 Bool2 KeyError
CarInfo(...).Health 5 Float5 Float1 KeyError
CarInfo(...).Rank 6 Float6 Float2 KeyError
CarGetPart(...).PartTransform 1 Transform1 Transform1 ✅ works
CarGetPart(...).HealthPercent 2 Float2 Float1 KeyError

HitInfoComponents sidesteps this by storing outputIndex=1 for both WasHit and Distance and instead setting node.type to bool / float so the consumer prefixes the right port type — CarInfoComponents and GetCarPartComponents would need the same kind of fix.

What to do until it's fixed

LLM authors: plan your bot so you never pass the broken accessors into another node. In practice that's easier than it sounds because the library already gives you graph-side alternatives:

  • Don't velocity-lead the aim. Skip target_pos + target_vel * dt. Let Autosteer(goal) and Autothrottle(goal, speed) handle aim + speed management — they already compensate for relative motion and obstacles internally.
  • Don't check self-speed via Magnitude(CarInfo(...).Velocity). For stuck-recovery, gate on CarInfo(self).IsImmobile… wait, that's broken too — use Spherecast + CarRaycasts + HitInfo to detect "nose blocked" instead, and flip to reverse on that signal.
  • Don't branch on opponent Health or Rank from CarInfo. Use the built-in target selectors that already bake those in: DemoDerbyGetCar(5) (lowest health), DemoDerbyGetCar(8) (nearest active), DemoDerbyGetCar(12) (nearest with disabled steering), DemoDerbyGetCar(20) (highest ranked), DemoDerbyGetCar(21) (lowest ranked), etc. Pick the target by mode, then aim at CarGetPart(3, target).PartTransform (nearest crucial part).
  • Don't branch on CarGetPart(...).HealthPercent. CarGetPart mode 2 is "weakest part" — use that part's PartTransform directly and skip the health comparison.
  • Do keep using .CarTransform and .PartTransform. Both resolve to Transform1 (index 1) and compile correctly — pipe them through RelativePosition(..., "Self") to get a world Vector3.

Substitution cheat sheet

If you find yourself reaching for one of the broken accessors, swap in the pattern on the right instead. Every example on the right compiles against the current library.

Broken pattern (crashes in ConnectPorts) Safe replacement
speed = Magnitude(CarInfo(self).Velocity) — reason about own speed Skip it. Autothrottle(goal, desired_speed) already manages cruise speed for you. If you absolutely need a "too slow" signal, use HitInfo(ray_forward) + HitInfo(ray_back) proximity — if the nose is pinned and the rear is clear, you're stuck.
lead = target_pos + CarInfo(target).Velocity * dt — predict the target Skip it. Autosteer(goal) already tracks a moving Vector3 goal reasonably well.
is_stuck = CarInfo(self).IsImmobile (then ConditionalSetFloat(is_stuck, -1.0, throttle_fwd)) front_hit, front_dist = HitInfo(ray_forward)
rear_hit, rear_dist = HitInfo(ray_back)
nose_wedged = front_hit & (front_dist < 2.0) & (goal_dist > 6.0)
rear_clear = ~rear_hit | (rear_dist > 4.0)
reverse_mode = nose_wedged & rear_clear
finish_them = CarInfo(target).Health < 50 Pre-select a weak target instead of branching on health. target = DemoDerbyGetCar(5) (lowest health) or DemoDerbyGetCar(12) (nearest with disabled steering).
winning = CarInfo(self).Rank < 3 No safe replacement right now. Drop the rank-based branching — the built-in target selectors already keep you aggressive.
is_ai = CarInfo(target).IsAI (avoid human targets) No safe replacement right now. Drop the filter — the derby is set up to only pit eligible cars against each other.
weak = CarGetPart(3, target).HealthPercent < 30 target_part = CarGetPart(2, target) # mode 2 = weakest part
goal = RelativePosition(target_part.PartTransform, "Self")

Worked example: "is_stuck = CarInfo(self).IsImmobile" done right

This is the exact pattern that crashed the reference claude.py with KeyError: 'Bool4'. Here's the broken version (do not ship) and the raycast-only version (compiles, does the same job):

# ❌ CRASHES at SaveData time with KeyError: 'Bool4'
self_info    = CarInfo(DemoDerbyGetCar(2))
is_stuck     = self_info.IsImmobile                     # outputIndex=4 -> asks for Bool4
throttle_fwd = Autothrottle(goal, 28.0)
throttle     = ConditionalSetFloat(is_stuck, -1.0, throttle_fwd)  # boom
# ✅ Same intent, raycast-only, compiles today
sensor = Spherecast(1.2, 12.0)
ray_fl, ray_f, ray_fr, ray_l, ray_r, ray_bl, ray_b, ray_br = CarRaycasts(sensor)
front_hit, front_dist = HitInfo(ray_f)
rear_hit,  rear_dist  = HitInfo(ray_b)

nose_wedged  = front_hit & (front_dist < 2.0) & (goal_dist > 6.0)
rear_clear   = ~rear_hit | (rear_dist > 4.0)
reverse_mode = nose_wedged & rear_clear

throttle_fwd = Autothrottle(goal, 28.0)
throttle     = ConditionalSetFloat(reverse_mode, -1.0, throttle_fwd)

Any bot that follows the Minimal complete Demo Derby example pattern (target car → nearest crucial part → Autosteer + Autothrottle + a forward raycast for panic-brake, no CarInfo component reads other than .CarTransform) avoids this bug entirely.

About

simple fork to be able to work on socker before socker part gets updated

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages