Type: Tactical RPG Game Report (Isometric)
Module: factory.report.package.charts
Framework: Axion (Pixi.js Report Engine)
Base Class: ObjReportPixiGeneric → ObjReportPixiFARCASTER → ObjReportPixiFARCASTERv2
FARCASTER is a turn-based tactical RPG game with isometric 3D rendering, built as an Axion report.
It extends the Pixi.js report framework to deliver an interactive tactical combat experience with:
Key Innovation: The entire game runs as a native Axion report, reusing:
from ObjReportPixiFARCASTER import Report
# Create game report
game = Report(DB=0)
# Configure game
game.SetTitle("FARCASTER - Mission 01")
game.SetGameDimensions(12, 10, 40) # Grid: 12x10, Tile: 40px
game.SetGameState(game_state_id="game_123",
campaign_id="campaign_456",
mission_id="tutorial_outpost")
# Render
html = game.Render()
# In Axion gateway setup
from ObjReportPixiFARCASTER import Report
game_report = Report()
game_report.SetTitle("Tactical Combat")
game_report.Render(
param1="game_state_id",
param2="campaign_id",
param3="mission_id"
)
# Serve to browser
html = game_report.GetTemplate()
Browser
↓ (click unit)
Game Report
↓ (RemoteProcedureCall)
Axion RPC
↓ (method: get_game_state)
ObjServiceTacticalRPG
↓ (game logic)
Axion Database (gekkoridge)
↓ (JSON game state)
Browser (render via Pixi)
RPC Response (JSON):
{
"id": "game_123",
"campaign_id": "campaign_456",
"mission_id": "tutorial_outpost",
"round": 1,
"units": {
"unit_0": {
"id": "unit_0",
"name": "Assault",
"class": "assault",
"health": 120,
"action_points": 14,
"pos": [1, 1],
"abilities": ["standard_shot", "suppressive_fire"],
"is_enemy": false
},
"enemy_1": {
"id": "enemy_1",
"name": "Sectoid",
"class": "grunt",
"health": 50,
"action_points": 10,
"pos": [10, 8],
"abilities": ["alien_shot"],
"is_enemy": true
}
}
}
game = Report()
# Display
game.SetTitle("My Mission")
game.SetDimensions(1200, 900)
# Game Grid (isometric)
game.SetGameDimensions(
grid_w=12, # Width in tiles
grid_h=10, # Height in tiles
tile_sz=64 # Pixels per tile (larger for isometric)
)
# Game State
game.SetGameState(
game_state_id="game_xyz",
campaign_id="campaign_abc",
mission_id="mission_001"
)
# Rendering
game.padding = 30
game.tile_depth = 16 # Height of 3D side faces
game.enable_cache = True
game.enable_interaction = True
Pixi canvas stack (z-indexed):
Terrain Layer — Isometric 3D block tiles
Units Layer — Unit circles centered on tile top faces
Effects Layer — Animations, damage numbers, particle effects (future)
UI Layer — Selection highlights, movement ranges (future)
Each layer is independently managed. Canvas size calculated automatically for isometric:
(gridWidth + gridHeight) * (tileSize / 2) + padding * 2(gridWidth + gridHeight) * (tileSize / 4) + tileDepth + padding * 2Grid Coordinates: (gx, gy) — standard 0-based array indexing
Screen Coordinates: Converted via isometric projection (2:1 ratio)
screen_x = padding + gridHeight * (tileSize / 2) + (gx - gy) * (tileSize / 2)
screen_y = padding + (gx + gy) * (tileSize / 4)
Click Detection: Screen-to-grid inverse transformation
relX = screenX - originX
relY = screenY - originY
gridX = round((relX / a + relY / b) / 2)
gridY = round((relY / b - relX / a) / 2)
where a = tileSize / 2, b = tileSize / 4
Click on Units:
Click on Tiles:
Buttons:
End Turn — Finish player actions, trigger AIRefresh — Force state update from serverGame Log:
Reports expect a get_game_state RPC method:
# ObjServiceTacticalRPG.py (gateway)
def route_get_game_state(self, game_state_id):
"""RPC: Fetch current game state."""
game_state = self.active_missions.get(game_state_id)
if not game_state:
return {"error": "Mission not found"}
return game_state.to_dict()
Game state persisted to gekkoridge.axion:
game_saves — Campaign state and rostergame_replays — Mission action historygame_progression — Character XP and progressionGame publishes Axion events:
game:new — Campaign startedmission:start — Mission initializedaction:resolved — Player action completedunit:died — Unit eliminatedmission:complete — Mission won/lostExtends: ObjReportPixiGeneric
Extends: ObjData
Uses: ObjServiceTacticalRPG
Create custom game variants by extending:
class CustomTacticalGame(Report):
def __init__(self, DB=0):
super().__init__(DB)
self.grid_width = 16
self.grid_height = 12
self.title = "My Custom Game"
def GetTemplate(self):
# Customize rendering
base = super().GetTemplate()
return base + """<script>
// Custom game logic
</script>"""
Requires:
# Start game report
report = Report()
report.Render(
param1="game_state_id",
param2="campaign_456",
param3="mission_001"
)
# Serve via Axion gateway
# Open: http://axion.local/reports/farcaster
vheyn@technocore.co.za
TechnoCore Infrastructure
FARCASTER Tactical RPG
# 1. Create report
game = Report(DB=0)
# 2. Configure
game.SetTitle("FARCASTER Mission 1")
game.SetGameState("game_001", "campaign_001", "tutorial_outpost")
# 3. Render
html = game.Render()
# 4. Serve to browser
# Browser connects via RPC to game logic service
# Game state updates every 5 seconds (configurable)
# Player clicks units and tiles to play
That's it! The entire tactical RPG runs as an Axion report. 🎮⚔️