VELA is a demonstrative multi-agent platform where 3D environments built with Godot 4.6 communicate via WebSocket with a Scala 3 backend. The backend executes decision logic written in Prolog through the tuprolog library.
The project shows how to separate:
- simulation, rendering, physics, and sensors in Godot;
- orchestration, agent state, and protocol handling in Scala;
- decision-making behavior in Prolog.
Each agent collects percepts from the Godot world, sends them to the Scala server, receives an action decided by Prolog, and applies that action locally inside the scene.
| Component | Technology |
|---|---|
| Game engine / simulation | Godot 4.6 |
| Simulation scripting | GDScript |
| Backend | Scala 3.3.3 |
| Scala build tool | sbt 1.10.2 |
| Effects / concurrency | cats-effect |
| Streaming / WebSocket | fs2 + http4s |
| JSON | circe |
| Declarative logic | Prolog |
| Prolog engine | tuProlog it.unibo.alice.tuprolog |
Install:
- Godot 4.6;
- JDK 17 or newer;
- sbt compatible with
sbt.version=1.10.2; - optional: IntelliJ IDEA with the Scala plugin;
- optional:
curlfor testing/health.
Quick check:
java -version
sbt --versionOpen two terminals.
Terminal 1: start the Scala backend.
cd scala-backend
sbt runThe server starts at:
ws://127.0.0.1:8080/ws
http://127.0.0.1:8080/health
Health check:
curl http://127.0.0.1:8080/healthExpected output:
ok
Terminal 2 / Godot:
- open Godot 4.6;
- import/open the
godotfolder; - run the main scene
Main.tscn; - choose one of the available scenarios from the menu.
| Scenario | Scene | Description |
|---|---|---|
| Simple Agent Test | project/godot/scenes/test_simple_agents.tscn |
Runtime spawning of A/B agents with different Prolog logic |
| Soccer Agent Test | project/godot/scenes/SoccerTest.tscn |
Two player-agents try to score by pushing a ball |
| Cars Agent Test | project/godot/scenes/vehicle_test.tscn |
Cars on paths, traffic lights, right-of-way, and anti-deadlock logic |
| Tanks Agent Test | project/godot/scenes/top_down_scene.tscn |
Autonomous tanks with targeting, line-of-sight, shooting, and respawn |
Endpoint:
ws://127.0.0.1:8080/ws
Request sent by Godot to the server:
{
"agent": "Agent_1",
"percepts": ["enemy", "can_attack"],
"theory": "optional prolog text"
}Response sent by the server to Godot:
{
"agent": "Agent_1",
"action": "attack",
"energy": 92
}Error:
{
"error": "no_solution"
}Important note: the Prolog theory is not sent with every request. It is sent on the first useful tick, whenever it changes, or after a reconnect. The server stores the theory per agent and reuses it for subsequent requests.
Main commands:
cd project/scala-backend
sbt compile
sbt runMain files:
| File | Role |
|---|---|
src/main/scala/app/Main.scala |
Server bootstrap |
src/main/scala/app/WebSocketRoutes.scala |
/health and /ws routes |
src/main/scala/app/DecisionService.scala |
Request -> Prolog -> state -> response pipeline |
src/main/scala/app/PrologService.scala |
tuProlog integration |
src/main/scala/app/State.scala |
Agent state, energy, decision reuse |
src/main/scala/app/Protocol.scala |
JSON DTOs and circe codecs |
src/main/scala/app/MonadTypes.scala |
Kleisli, EitherT, AppContext |
src/main/resources/logic.pl |
Fallback Prolog theory |
The backend uses a monadic design:
Kleislias a Reader to passAppContext;EitherTto handle typed errors;Ref[IO, ServerState]for concurrent shared state.
Path:
godot
Main files:
| File | Role |
|---|---|
scripts/Agent.gd |
WebSocket base class for agents |
scripts/TestAgent.gd |
Simple combat agent |
objects/player/player.gd |
Soccer Prolog player |
scenes/path_follow_3d.gd |
Path-following vehicle + Prolog |
objects/tanks_objects/tank.gd |
Tank agent |
objects/street_objects/semaphore.gd |
Master/slave traffic light |
scripts/LoaderScript.gd |
Main menu |
Each agent produces percepts through build_percepts() and applies actions through perform_action(action) or an equivalent local action mapping.
Path:
godot/prolog
Available files:
| File | Usage |
|---|---|
logic_a.pl |
Aggressive simple agent |
logic_b.pl |
Cautious simple agent |
soccer_left.pl |
Left-side soccer player |
soccer_right.pl |
Right-side soccer player |
vehicle_logic.pl |
Cars, traffic lights, distance, right-of-way, intersections |
tank_hunter.pl |
Tanks, line-of-sight, shooting, patrol |
Each theory exposes the predicate:
decide_action(Percepts, Action).Rules are ordered by priority and use ! to make the first valid choice deterministic.
Default value:
ws://127.0.0.1:8080/ws
If the backend runs on another machine or port, update the WS field in the scenario UI or the exported ws_url variable on the agents.
- Open IntelliJ IDEA.
- Select
Open. - Open the
scala-backendfolder. - Import it as an sbt project.
- Run
app.Main.
If the logs show a red line with an INFO level message from Ember/http4s, it is not necessarily an error: IntelliJ often colors the stderr stream in red even for informational logs.
Check:
- the backend is running with
sbt run; - the WS URL is correct:
ws://127.0.0.1:8080/ws; - port
8080is not already in use; - the Godot console for WebSocket errors.
Possible causes:
- the agent did not send a theory;
- fallback
src/main/resources/logic.plis not available on the classpath; - the project was started from the wrong folder.
Always start from:
cd scala-backend
sbt runThe Prolog theory has no applicable rule or is missing a final fallback.
Each theory should end with a rule such as:
decide_action(_, idle).or, for vehicles:
decide_action(_, drive).- Start the Scala backend.
- Start Godot.
- Test one scenario at a time.
- Modify the relevant Prolog logic.
- Restart/reconnect the agent if the theory must be sent again.
- Check both Godot and Scala logs.
The project includes:
- a working Scala backend with WebSocket and Prolog integration;
- a Godot project with 4 demonstrative scenarios;
- commented Prolog logic files;
We thank student Antonio Rotundo from the University of Bologna for helping us in the first phase of software development.