A deterministic robot arena combat engine with a custom domain-specific language (DSL) for programming autonomous robot behaviors. Write code in the ArenaScript language, compile it to bytecode, and watch your bots fight in a live arena visualization.
Engine v0.2 · Language v1.1 — Adds
whileloops, list indexing (list[i]), runtime string concatenation, predictive perception sensors (predict_position,incoming_projectile,damage_direction,threat_level), better runtime error messages with source-line info, an in-app command palette (Ctrl+K), language reference drawer (Ctrl+/), keyboard shortcut help (Shift+?), match-simulation loading overlay, local match history, and two new reference bots (Oracle, Zealot) showcasing the new features.
- Custom DSL — lexer, parser, semantic analyzer, bytecode compiler
- Sandboxed VM — stack-based bytecode interpreter with budget metering (no runaway loops)
whileloops withbreak/continue- List indexing (
list[i], supports negative indices, out-of-bounds returnsnull) - String concatenation via
+when either side is a string - Runtime errors carry source line/column info, surfaced in diagnostics
- Deterministic multi-phase tick loop with seeded PRNG for reproducible matches
- Resource economy — heat + ammo + energy + HP create strategic tradeoffs every tick
- Predictive perception —
enemy_velocity,predict_position,incoming_projectile,damage_direction,threat_levellet bots lead shots and dodge - Resupply depots — contested neutrals that refill ammo and vent heat
- Information warfare — cloaking with break-on-damage/attack + directional scan
- Hive memory — shared team key/value store for squad coordination
- Advanced combat — light/heavy projectiles, short-range zap, armed self-destruct
- 6 hand-crafted arenas — Crucible, Inferno, Fortress, Gauntlet, Plains, and the radially-symmetric free-for-all arena, The Nexus
- 4 robot classes — Brawler, Ranger, Tank, Support with distinct stats & heat profiles
- Match formats — 1v1, 2v2, 3v3, 4v4, 5v5 team battles and battle royale (free-for-all, up to 20 bots, last one standing)
- Team Builder — assemble custom rosters: two squads (up to 5v5) or a battle royale field of up to 20, fielding your own editor bot, presets, or library bots, with one-click auto-fill
- Match broadcast mode — when a match runs the arena takes over the screen with a cinematic intro, a broadcast scoreboard, and a live combatant HUD (per-team rosters with real-time HP bars)
- Match scorecard — winner banner + team comparison, revealed when the replay finishes so the live match stays spoiler-free
- Command palette (Ctrl+K) — jump between bots, views, docs, and actions
- Keyboard shortcut help (Shift+?) — full binding reference in-app
- Language reference drawer (Ctrl+/) — searchable, sectioned quick-lookup
- Match loading overlay — visible progress during simulation
- Match history panel — recent runs persisted to localStorage
- Live arena rendering — bots, projectiles, hazards, depots, control points, cover, pickups
- Replay system — scrubber, bookmarks (1st damage / 1st kill), variable speed
- Ranked — Elo-based matchmaking, Bronze → Champion tiers
- Tournaments — single-elim, round-robin, Swiss formats
- PHP backend — matchmaking, lobbies, rankings, tournaments
| Layer | Technology |
|---|---|
| Frontend | Vanilla JavaScript (ES2022 modules), HTML, CSS |
| Backend | PHP |
| Build | None required - runs directly in the browser |
No build tools, no bundlers, no package managers needed.
-
Serve the project with any web server:
# PHP built-in server php -S localhost:8000 # Python python3 -m http.server 8000 # Node.js (npx) npx serve .
-
Open
http://localhost:8000in your browser -
Select a bot preset or write your own ArenaScript program
-
Click Compile, then Run Match
| Shortcut | Action |
|---|---|
Ctrl+Enter |
Compile |
Ctrl+Shift+Enter |
Compile & run match |
Ctrl+S |
Save current editor program to library |
Ctrl+K |
Command palette |
Ctrl+/ |
Open language reference |
Shift+? |
Show keyboard shortcut help |
Tab |
Insert 2 spaces in editor |
Space |
Play / pause replay (in Arena view) |
← / → |
Step replay frame (in Arena view) |
Esc |
Close modal / exit full-screen match |
arenascript/
├── index.html # Landing / marketing front page
├── app.html # Single-page app (builder, arena, library, ...)
├── learn.html # Step-by-step ArenaScript tutorial
├── community.html # Public bot gallery (standalone page)
├── css/
│ └── style.css # Dark theme UI stylesheet
├── js/
│ ├── app.js # Main frontend application
│ ├── demo.js # Node.js CLI demo
│ ├── lang/ # DSL compiler pipeline
│ │ ├── tokens.js # Lexer / tokenizer
│ │ ├── ast.js # AST node definitions
│ │ ├── parser.js # Recursive descent parser
│ │ ├── semantic.js # Type checking & scope resolution
│ │ ├── compiler.js # AST → bytecode compiler
│ │ └── pipeline.js # High-level compile orchestrator
│ ├── runtime/ # Bytecode execution
│ │ ├── opcodes.js # Instruction set definitions
│ │ ├── vm.js # Stack-based bytecode VM
│ │ └── budget.js # Execution budget accounting
│ ├── engine/ # Simulation engine
│ │ ├── world.js # World state (entities, positions, health)
│ │ ├── tick.js # 11-phase tick loop (core game loop)
│ │ ├── actions.js # Action intent collection
│ │ ├── sensors.js # Perception layer (fog-of-war)
│ │ ├── movement.js # Movement & AABB collision detection
│ │ ├── combat.js # Attack resolution & damage
│ │ ├── los.js # Line-of-sight computation
│ │ ├── events.js # Event generation & dispatch
│ │ └── replay.js # Deterministic replay writer/reader
│ ├── server/ # Competitive systems
│ │ ├── matchmaking.js # Queue management & Elo pairing
│ │ ├── ranked.js # Elo ratings & rank tiers
│ │ ├── tournament.js # Tournament brackets
│ │ ├── match-runner.js # Server-side match execution
│ │ └── lobby.js # Multiplayer lobby management
│ └── shared/ # Common utilities
│ ├── config.js # Game balance constants
│ ├── types.js # Core type definitions
│ ├── prng.js # Seeded deterministic PRNG
│ └── vec2.js # 2D vector math
└── api/ # PHP backend endpoints
├── config.php # Game configuration
├── matchmaking.php # Queue management & pairing
├── ranked.php # Elo rating calculations
├── tournament.php # Tournament bracket generation
├── match-runner.php # Match execution & result storage
└── lobby.php # Lobby creation & joining
ArenaScript is a domain-specific language for programming robot behaviors. Programs are event-driven: you define handlers that execute each tick.
robot "MyBot" version "1.0"
meta {
author: "YourName"
class: "brawler"
}
const {
ENGAGE_RANGE = 8
}
state {
mode: string = "hunt"
}
on spawn {
set mode = "hunt"
}
on tick {
let enemy = nearest_enemy()
if enemy != null {
if can_attack(enemy) {
attack enemy
} else {
move_toward enemy.position
}
} else {
move_to nearest_control_point()
}
}
on damaged(event) {
set mode = "fight"
}
| Feature | Description |
|---|---|
| Types | number, boolean, string, id, vector, position, list<T>, nullable T? |
| Declarations | let for locals, set for state mutations, const for constants, state block for persistent vars |
| Control Flow | if/else if/else, for...in loops, return, after/every timer blocks |
| Operators | +, -, *, /, %, ==, !=, <, >, <=, >=, and, or, not |
| Events | on spawn, on tick, on damaged, on low_health, on destroyed, on enemy_seen, on enemy_lost, on cooldown_ready, on signal_received |
| Movement | move_to, move_toward, move_forward, move_backward, turn_left, turn_right, strafe_left, strafe_right, stop, retreat |
| Combat | attack, fire_at, fire_light, fire_heavy, burst_fire, grenade, zap, shield, vent_heat |
| Utility | cloak, self_destruct, place_mine, send_signal, mark_position, taunt, overwatch, capture, ping |
| Perception sensors | nearest_enemy, visible_enemies, scan, scan_enemies, last_seen_enemy, enemy_heading, is_enemy_facing_me, nearest_ally, visible_allies, nearest_sound |
| Resource sensors | health, energy, heat, ammo, heat_percent, ammo_percent, overheated |
| State sensors | is_cloaked, cloak_remaining, self_destruct_armed, self_destruct_remaining, is_taunted, is_in_overwatch, has_effect |
| Map sensors | nearest_depot, is_on_depot, nearest_control_point, nearest_cover, nearest_heal_zone, nearest_hazard, nearest_mine, nearest_pickup |
| Team memory | hive_get(key), hive_set(key, value), hive_has(key) — shared per-team key/value store |
| Functions | fn name(params) { ... } for custom helper functions |
See docs/language-reference.md for the full sensor and command reference with examples.
| Class | HP | Energy | Speed | Damage | Range | Ammo | Heat Dissipation | Playstyle |
|---|---|---|---|---|---|---|---|---|
| Brawler | 120 | 80 | 2.2 | 14 | 3.5 | 50 | 2.2x (fastest) | Aggressive melee, rarely overheats |
| Ranger | 80 | 100 | 2.0 | 10 | 8.0 | 100 | 1.0x (baseline) | Ranged kiting, largest ammo pool |
| Tank | 150 | 60 | 1.5 | 8 | 4.0 | 80 | 0.7x (slowest) | Defensive holding, overheats fast |
| Support | 90 | 120 | 1.8 | 6 | 6.0 | 70 | 1.5x | Team support, good heat management |
Beyond HP and energy, every combat action feeds into a heat + ammo resource model:
- Heat builds from firing and ability use. At 100 the robot is overheated and cannot fire until it cools below 60.
- Ammo is finite per-spawn and only refills at resupply depots placed neutrally in the middle of each arena.
- Melee attack and zap cost no ammo — close-quarters options when ranged runs dry.
- vent_heat trades the combat slot this tick for aggressive cooling.
This creates a constant "fight-now vs. retreat-to-resupply vs. conserve-and-cool" decision loop that runs on top of normal positioning and target selection.
The simulation runs a deterministic multi-phase tick loop. Each tick processes, in order:
- Cooldown + heat update — decrement cooldowns, decay heat, handle overheat recovery, upkeep cloak
- Timer execution — fire any elapsed
after/everyblocks - Pickup spawn + effect expiry + noise decay — passive world tick
- VM execution — run each robot's
on tickhandler and collect intents - Movement resolution — apply velocity, resolve collisions
- Combat resolution — attacks, projectile spawns, noise emission
- Mine detonation + pickup collection
- Damage & effects — advance projectiles, apply heal/hazard zones, apply resupply depots, resolve armed self-destructs
- Discovery + signal dispatch + control point capture
- Event dispatch — fire
damaged/low_health/destroyed/ etc. handlers - Replay capture + win condition check (with sudden-death timeout resolution)
See docs/architecture.md for full details on each phase.
| Setting | Value |
|---|---|
| Arena Size | 140 x 140 units |
| Tick Rate | 30 ticks/sec |
| Max Match Duration | 3000 ticks (100 seconds) + up to 900 ticks sudden death |
| CPU Budget | 1000 instructions/tick/robot |
| LOS Range | 150 units |
| Heat Cap | 100 (recovery threshold 60) |
The competitive system uses Elo ratings with the following tiers:
| Tier | Rating |
|---|---|
| Bronze | 0+ |
| Silver | 1000+ |
| Gold | 1200+ |
| Platinum | 1400+ |
| Diamond | 1600+ |
| Champion | 1800+ |
K-factor is 32 for ratings below 2400, and 16 above.
The backend provides REST endpoints for multiplayer features:
| Endpoint | Purpose |
|---|---|
api/config.php |
Retrieve game balance configuration |
api/matchmaking.php |
Enqueue players and find Elo-based pairings |
api/ranked.php |
Calculate and update Elo ratings |
api/tournament.php |
Generate brackets (single-elim, round-robin, Swiss) |
api/match-runner.php |
Execute matches server-side and store results |
api/lobby.php |
Create/join lobbies, supports 1v1, 2v2, and FFA modes |
ArenaScript is designed to drop straight into a cPanel public_html directory,
including into a subdirectory such as public_html/arenascript/.
- Upload the project contents into the target directory (root or subdirectory).
- Ensure the host runs PHP 8.1 or newer (uses
strict_types,mixed,neverreturn types, Argon2id password hashing). - In cPanel, create a MySQL database + user and grant the user full privileges on that database.
- Visit
api/install.phpin a browser (locally, or after temporarily settingARENA_ALLOW_INSTALLER=1in the host's environment). Fill in the DB credentials and the admin account — the installer writesapi/.env.local, runs the migrations, and creates the admin user. - Delete
api/install.phpafter a successful install. The installer self-locks viaapi/.installed.lock, but removing the file entirely is safer. - Confirm the app loads and that you can sign in. API calls resolve their
base URL dynamically from the module location (
import.meta.url), so the app will work whether it's athttps://example.com/orhttps://example.com/arenascript/.
The shipped api/.htaccess blocks direct web access to .env.local,
.installed.lock, .storage/, and .sql migration files. If your host
runs AllowOverride None, the PHP bootstrap also drops a secondary
.htaccess and an empty index.html into api/.storage/ at runtime as
defense in depth.
Before opening beta to real users:
- Set
ARENA_CORS_ORIGINto your real origin (e.g.https://arena.example.com). The default*is only safe for local dev. - Set
ARENA_DB_ENABLED=1and confirm DB credentials are populated. - Delete
api/install.phpafter first-time setup. - Force HTTPS at the web server or in cPanel (Let's Encrypt + redirect).
- Verify
api/.env.localis mode 0600 (chmod 600 api/.env.local).
All rights reserved.