Skip to content

hrshtt/FactoryVerse

Repository files navigation

FactoryVerse

FactoryVerse is a multi-layered platform addressing a critical gap in AI Agent Research in Factorio. To this effect it provides a comprehensive low-level scaffolding for developing embodied agents in Factorio, developed for scaling multi-agent research via the FV Embodied Agent mod. While the FV Snapshot mod, fills the gap of reading the massive map state in a deterministic and non-blocking manner. These two core mods provide a stable foundation for doing high-level, composable agent research in Factorio.

Beyond the core foundation, FactoryVerse also explores an opinionated design to enhance the LLM Agent's ability for interacting and reasoning about the complex factorio game state. This is achieved by providing High-level Action and Factory Object interfaces that wrap the underlying low-level RCON calls into an Object-Oriented Markov Decision Process (OOMDP) interface, for agent control and entity interaction via an agent owned python runtime. While the complex game state is materialized via a real-time synchronized DuckDB database, providing an equivelence between the Remote View (map) screen available to human players via the GUI.

What FactoryVerse Provides

FactoryVerse ships four integrated components:

1. FV Embodied Agent Mod (src/fv_embodied_agent/)

A Factorio mod that implements multi-agent gameplay infrastructure. Each agent is a fully-featured entity with its own character, force, inventory, and state machines. The mod exposes 30+ low-level action methods via Factorio's remote interface system, enabling external control through RCON.

2. FV Snapshot Mod (src/fv_snapshot/)

A Factorio mod that materializes game state to disk as append-only JSONL files. Every entity placement, resource extraction, and chunk charting event is captured deterministically and written to disk—providing a complete audit trail of game state.

3. Factory Objects (src/FactoryVerse/game/factory/)

Type-safe, stateful Python abstractions that wrap the mod's remote interface into an OOMDP. Instead of calling 30+ stateless RCON functions, agents interact with objects like Furnace, MiningDrill, and AssemblingMachine that have domain-specific methods (add_fuel(), set_recipe(), output_position()). These objects mirror the GUI interactions human players use.

4. Top-Level Accessors & DuckDB Integration

Python accessors that provide two complementary query interfaces:

  • Reachable View: Query entities and resources within interaction range (full mutation access)
  • Remote View: Query entities anywhere on the map via SQL (read-only, then walk to interact)
  • Ghost Entity: In-built dry run placement for entities, useful for planning large-scale construction

The snapshot mod's JSONL files are loaded into a DuckDB spatial database (src/FactoryVerse/game/infra/duckdb/) that stays synchronized with game state in real-time via UDP. This enables agents to query the entire map with SQL instead of processing screenshots.

Utility Mods: In addition to the core infrastructure, FactoryVerse provides supporting mods such as FV Placement Hints (src/fv_placement_hints/). This module is designed to replicate, in structured form, the same visual feedback Factorio provides when you use the cursor to hover, pick up, or move entities—highlighting valid placement tiles and indicating where entities can connect or interact. By consolidating placement validation, connection solving, and area scanning logic into Lua functions, FV Placement Hints exposes to agents and external tools the same kind of real-time spatial reasoning that players rely on visually, enabling automated systems to gauge and select valid placements just as a human would with the game's interface. (See Appendix A.7 for details.)

Research Motivation

FactoryVerse investigates three core research questions:

1. Scalable Mod Architecture for Multi-Agent Gameplay

How can we design a mod architecture that scales for multiple embodied agents in Factorio?

The FV Embodied Agent mod provides a simple, extensible foundation for multi-agent scenarios. Each agent operates independently, actions like walking, mining, and crafting use async contracts (UDP notifications) to enable non-blocking, concurrent agent behavior. This is inspired by the many existing Multi-Agent sandbox environments.

2. Database as Proxy for Multi-Scale Vision

Is a database a good proxy for multi-scale vision in embodied gameplay?

Rather than only processing screenshots, FactoryVerse materializes entire game state to disk and provides SQL query access to a real-time spatial database. Factorio's deterministic simulation guarantees that event handlers capture exact game state—every change is written to disk as a set of initial dump and subsequent append-only JSONL files. In Factorio's lua runtime, reading game state directly via rcon blocks the runtime, making the snapshot system essential for scaling multi-agent game state reads. UDP sync provides real-time updates for performance, but the file-based record ensures deterministic source of truth as fallback: if UDP drops packets, sequence gaps trigger reconstruction from disk files.

3. Action Wrappers and Object-Oriented MDP for LLM Agents

What abstraction best enables LLMs to interface with stateful game entities?

High-level actions wrap the stateful life cycles for async actions and the underlying UDP contracts provided by the embodied agent mod into a more user-friendly interface via python classes. They also provide entry points for instantiating factory objects.

Factory objects create an OOMDP abstraction that mirrors GUI interactions. Instead of calling low-level RCON functions with positional arguments and manual serialization, agents interact with stateful objects that have explicit affordances. This bridges the gap between human visual/interactive understanding and LLM code generation.


Installation

Development (Editable Install)

Clone the repository and install with uv:

# Clone repository
git clone https://github.com/hrshtt/FactoryVerse.git
cd FactoryVerse

# Install in editable mode with dev dependencies
uv pip install -e .
uv sync --group dev

# Copy and configure environment
cp .env.example .env
# Edit .env with your API keys and settings

From Repository (Non-Editable)

# Install directly from GitHub
uv pip install git+https://github.com/hrshtt/FactoryVerse.git

Interfaces

FactoryVerse provides multiple entry points for different use cases. All interfaces load the FactoryVerse mods (fv_embodied_agent + fv_snapshot) automatically.

CLI (fv / factoryverse)

Primary interface for managing Factorio instances and development:

# Launch client with mods
uv run fv client launch --scenario test-ground

# Start Docker server(s) with hot-reload
uv run fv server start --num 1 --scenario test-ground --watch

# Interactive LLM agent mode
uv run fv agent

# Instance and data management
uv run fv instance list
uv run fv data refresh

MCP Server (factoryverse-mcp)

Model Context Protocol server for LLM tool integration (Claude Desktop, etc.):

uv run factoryverse-mcp

Web Dashboard (fv-ui)

Browser-based monitoring for agents, services, and trajectories:

uv run fv-ui

Module Structure

src/FactoryVerse/
├── game/           # Factorio domain knowledge
│   ├── agent/      # Embodied actions, views, event streams
│   ├── factory/    # Entity types, prototypes, recipes
│   ├── scenarios/  # Scenario adapters (lab-grid, test-ground)
│   ├── tasks/      # Task definitions and verification
│   └── infra/      # DuckDB spatial database implementation
├── environment/    # Tiered orchestrator (Tier 1-6)
├── infra/          # Platform infrastructure
│   ├── docker/     # Server container management
│   ├── mcp/        # MCP server implementation
│   ├── llm/        # LLM client and prompts
│   └── ui/         # Web dashboard
└── utils/          # Documentation generators, helpers

Philosophy: game/ contains Factorio-specific knowledge (what the game is), environment/ orchestrates initialization (how components connect), infra/ provides platform services (how to run).


Evaluation

FactoryVerse includes a task verification system for evaluating agent performance:

Task Types (game/tasks/):

  • Throughput: Sustain N items/minute via automation (rate-based verification)
  • Unbounded: Maximize production (no quota)
  • Freeplay: No verification criteria

Verification measures automation vs manual production, tracking items produced by machines versus hand-crafting. Multiple sources supported: RCON (live), JSONL (replay), DuckDB (queries).

Parallel Evaluation: The lab-grid scenario provides 64 isolated cells (8×8 grid) for running multiple agents simultaneously with independent inventories, forces, and snapshots.


Technical Deep Dive

Infrastructure Architecture

FactoryVerse's infrastructure is designed for multi-agent, multi-instance isolation and real-time synchronization. The system uses incremental port allocation to support multiple concurrent Factorio instances without conflicts, enabling parallel experiments with complete isolation.

FactoryVerse maintains real-time synchronization between Factorio (Lua) and Python through a layered approach: the fv_snapshot mod writes game state to JSONL files on disk, Python loads these into DuckDB, and UDP provides real-time incremental updates. This design ensures disk files serve as the deterministic source of truth, while UDP sync provides performance optimization with automatic fallback to disk replay if packets are dropped.

Why UDP is Necessary for Async Actions:

Factorio's RCON (Remote Console) executes commands synchronously within a single game tick—the simulation pauses, runs Lua code, and returns results atomically. While this works for fast operations like inspect_entity or place_entity, some actions cannot complete within one tick because they rely on Factorio's internal simulation systems that operate over multiple ticks:

  • Walking: A* pathfinding and character movement happen incrementally
  • Crafting: Recipes process over time based on crafting speed (e.g., 0.5 seconds = 30 ticks)
  • Mining: Resource extraction is progressive based on mining speed
  • Research: Technologies unlock over many ticks based on research speed and lab count

The problem: RCON returning only confirms the action started, not that it completed. Instead of polling RCON every tick (wasteful and blocks multi-agent orchestration), FactoryVerse uses UDP notifications: each agent maintains state machines in Lua that send UDP notifications when actions complete, enabling non-blocking, concurrent agent operations.

(See Appendix A for detailed port allocation, RCON execution model, sync services implementation, and configuration management.)

FV Embodied Agent Mod

The FV Embodied Agent mod (src/fv_embodied_agent/) implements the core agent infrastructure—a simple, extensible, modular, barebones foundation for multi-agent gameplay.

Agent Class (Agent.lua):

  • Lifecycle Management: Agent:new(), Agent:destroy(), force creation/merging
  • State Machines: Consolidated state for walking, mining, and crafting activities
  • Action Methods: Mixed in from modular action files in agent_actions/:
    • walking.lua: Pathfinding, waypoint navigation, goal adjustment for entity collision
    • mining.lua: Incremental and deplete modes, stochastic product handling
    • crafting.lua: Queue management, progress tracking, completion notifications
    • placement.lua: Entity placement with validation, ghost placement, placement cues
    • entity_ops.lua: Recipe setting, inventory management, entity pickup
    • researching.lua: Technology queue management, research status
    • reachability.lua: Entity and resource queries within reach distance

Remote Interface: Each agent dynamically registers a per-agent remote interface (agent_{agent_id}) accessible via RCON, exposing 30+ low-level action methods. These methods are the low-level primitives that factory objects wrap into a higher-level OOMDP. This also provides the scaffolding for future extensibility for other paradigms inside the game like bots, trains, biters, etc. with modular action spaces.

Multi-Agent Support:

  • Each agent has its own force with configurable relationships
  • Agents can share technology trees or research independently
  • Rendering labels (name tags, map markers) for visual identification

(See Appendix A.4 for detailed Factorio remote interface reference and method catalog.)

FV Snapshot Mod

The FV Snapshot mod (src/fv_snapshot/) materializes game state to disk for database loading—implementing the database as proxy for multi-scale vision research question.

Snapshot System (game_state/ modules):

  • Entities.lua: Serializes all placed entities with components (belts, inserters, drills, assemblers, inventories, recipes)
  • Resource.lua: Streams resource tiles (ores) and entities (trees, rocks) with clustering into patches
  • Map.lua: Tracks charted chunks, manages snapshot phases (initialization vs maintenance)
  • Power.lua: Captures electric network topology and coverage areas
  • Research.lua: Exports technology tree state, research progress, unlocked recipes

Two-Phase Snapshotting:

Phase 1: Bootstrap Mode (Initial Snapshotting)

  • Trigger: Map generation completes, all starting chunks charted
  • Process: Lua runs find_entities_filtered() on all initially charted chunks, serializes to JSONL files with batching to prevent game freezes. Python discovers and loads these files into DuckDB, building derived tables via spatial clustering.
  • Output: Complete spatial database snapshot of initial game state

Phase 2: Maintenance Mode (Real-Time Sync)

  • Trigger: Bootstrap complete, agents start playing
  • Process: Event handlers (on_built_entity, on_mined_entity, on_chunk_charted) capture changes, writing to disk as append-only logs (source of truth) and emitting UDP notifications (performance optimization). Python's SyncService subscribes to UDP, applies incremental updates, and detects sequence gaps to trigger disk replay if packets are dropped.
  • Guarantee: Disk files are the proof—UDP can miss packets, but determinism + append-only log ensures exact reconstruction

Integration with Python:

The RemoteView class (src/FactoryVerse/game/agent/remote_view.py) provides the Python interface:

view = RemoteView(snapshot_dir, udp_dispatcher=dispatcher)
await view.load()  # Load JSONL snapshots into DuckDB
await view.start()  # Start real-time UDP sync

# Query entities anywhere on map (returns RemoteViewEntity instances)
drills = view.get_entities("SELECT * FROM map_entity WHERE entity_name = 'burner-mining-drill'")

# Query ghosts for construction planning
pending = view.get_ghosts("SELECT * FROM ghost WHERE ghost_name = 'assembling-machine-1'")

(See Appendix A.5 for detailed file structure, output format, event dispatcher, and architecture components.)

Python Factory Objects

The Python Factory Objects (src/FactoryVerse/game/factory/) provide a type-safe, object-oriented interface to Factorio—wrapping the RCON interface exposed by fv_embodied_agent with stateful, composable abstractions.

Core Concepts:

  • Accessor Modules: walking, mining, crafting, research, inventory, reachable_view, remote_view
  • Entity Types: Furnace, AssemblingMachine, Inserter, MiningDrill, TransportBelt, ElectricPole, Container
  • Item Types: Item, ItemStack, PlaceableItem, MiningDrillItem (with placement cues)
  • Type Classes: MapPosition, Direction, BoundingBox

Entity View System: Two access modes enforce proximity-based access control (defined by EntityView enum):

  • REMOTE View (read-only): Returned by remote_view.get_entities(sql)

    • Allows: Spatial queries, prototype data, inspect(), planning calculations
    • Blocks: Mutations (pickup(), add_fuel(), set_recipe(), build())
    • Use case: Query entities anywhere on map for planning, then navigate to them for interaction
  • REACHABLE View (full access): Returned by reachable_view.get_entity(name)

    • Allows: Everything—all spatial, planning, AND mutation methods
    • Methods depend on entity's mixins (FuelableMixin, CrafterMixin, ContainerMixin, etc.)
    • Use case: Interact with nearby entities (within reach distance)

Ghost Property: is_ghost is a boolean property of BaseEntity:

  • Ghost Entities (entity.is_ghost == True): Returned by item.place_ghost(pos) or remote_view.get_ghosts(sql)
    • Placeholders for entities to be built
    • Appear in spatial queries for construction planning
    • Special methods: build() (REACHABLE only), remove() (both views)
    • Blocks: All state mutations (ghosts have no live state—no fuel, no recipe, no inventory)
    • inspect() returns static data describing the planned entity

View + Ghost Interaction:

  • REMOTE + ghost: Can inspect and remove, cannot build
  • REACHABLE + ghost: Can inspect, remove, AND build (requires proximity)
  • REMOTE + real entity: Can inspect, cannot mutate
  • REACHABLE + real entity: Full access to all operations

Why Factory Objects Instead of Direct Remote Calls?

While the fv_embodied_agent mod exposes a complete remote interface (remote.call("agent_1", "walk_to", {x=100, y=200})), calling these functions directly via RCON has fundamental limitations:

  • Stateless: Each remote.call() is isolated—no object identity or persistent entity references
  • Stringly-Typed: All data passes as JSON strings through RCON—no type safety, no dynamic property access
  • Positional Arguments: Remote calls use unnamed Lua table arguments—error-prone for methods with many parameters
  • No inherit Composition: Cannot implicitly chain operations or build reusable references (e.g., "this furnace at position X")
  • Manual Serialization: Must manually parse data from return types to parameters across calls
  • Procedural, Not Object-Oriented: No encapsulation of entity state—every operation requires passing position/name explicitly

The OOMDP Solution:

Factory objects create an OOMDP abstraction that mirrors the GUI interactions human players use:

Example Comparison:

# Low-level remote interface (30+ stateless functions):
# Every operation requires explicit agent_id, position, serialization
rcon.send_command(
    "/c rcon.print(helpers.table_to_json("
    "remote.call('agent_1', 'inspect_entity', 'stone-furnace', {x=10, y=20})"
    "))"
)
result = json.loads(rcon.response)
# Now manually parse result, check fuel, decide what to do...

rcon.send_command(
    "/c rcon.print(helpers.table_to_json("
    "remote.call('agent_1', 'put_inventory_item', 'coal', 5, 'stone-furnace', {x=10, y=20})"
    "))"
)
# Repeat for every interaction...

# Get persistent object reference
furnace = reachable_view.get_entity("stone-furnace")

# Object knows its own position and state
print(furnace.inspect())  # Human-readable formatted output

# Type-safe methods
coal_stack = inventory.get_item("coal", count=5)
furnace.add_fuel(coal_stack)  # Entity-specific affordance

# Compose operations naturally
ore_stack = inventory.get_item("iron-ore", count=10)
furnace.add_ingredients(ore_stack)

Key Insight: Factory objects transform 30+ low-level procedural functions into an object-oriented MDP where:

  • Entities are stateful objects (like GUI elements human players click on)
  • Methods are affordances (like menu options that appear when you right-click an entity)
  • The abstraction matches human mental models (click furnace → add fuel) rather than RPC mechanics

Accessor Patterns:

# Walking
await walking.to(MapPosition(100, 200))

# Mining (max 25 items per operation)
resources = reachable_view.get_resources("iron-ore")
await resources[0].mine(max_count=25)

# Crafting
await crafting.craft("iron-plate", count=10)
crafting.enqueue("copper-plate", count=50)

# Inventory
iron_count = inventory.get_total("iron-plate")

# Entity operations (reachable only)
furnace = reachable_view.get_entity("stone-furnace")
furnace.add_fuel(coal_stack)
furnace.add_ingredients(ore_stack)

# Map-wide queries (remote view - read-only)
drills = remote_view.get_entities(
    "SELECT * FROM map_entity WHERE entity_name = 'burner-mining-drill'"
)
# Remote entities support inspection but not mutation
for drill in drills:
    print(f"Drill at {drill.position} - {drill.inspect()}")

# Database queries
result = remote_view.query("SELECT * FROM resource_patch WHERE resource_name = 'iron-ore'")

Entity Methods: Each entity type has domain-specific methods reflecting its gameplay role:

  • Furnace (FuelableMixin, ContainerMixin): add_fuel(), add_ingredients(), take_products()
  • AssemblingMachine (CrafterMixin, ContainerMixin): set_recipe(), get_recipe()
  • Inserter (SpatialPropertiesMixin): get_pickup_position(), get_drop_position()
  • MiningDrill (SpatialPropertiesMixin, CrafterMixin): output_position(), get_search_area(), place_adjacent()
  • TransportBelt (SpatialPropertiesMixin): extend(turn=None)
  • ElectricPole (SpatialPropertiesMixin): extend(direction, distance=None)

Why This Matters: These methods make implicit game mechanics explicit and discoverable. An LLM can:

  • Call drill.output_position() instead of manually calculating offset based on prototype data
  • Use inserter.get_drop_position() instead of guessing connection logic
  • Invoke furnace.add_fuel(stack) with proper type checking instead of raw RCON JSON serialization

This dramatically improves LLM code generation quality and reduces trial-and-error.

DuckDB Schemas (Broad Strokes)

The DuckDB schema (src/FactoryVerse/game/infra/duckdb/schema_definitions.py) provides a spatial database for map queries:

Core Tables:

  • resource_tile: Individual ore tiles with position and amount
  • resource_entity: Trees, rocks with bounding boxes
  • map_entity: All placed entities (drills, furnaces, belts, etc.) with spatial geometry
  • entity_status: Current operational status (working, no_power, no_fuel)

Component Tables:

  • inserter: Direction, input/output positions and connected entities
  • transport_belt: Direction, input/output connections for belt networks
  • electric_pole: Supply area geometry, connected poles
  • mining_drill: Mining area geometry, output position
  • assemblers: Current recipe

Patch Tables:

  • resource_patch: Clustered ore patches with total amount, centroid, geometry
  • water_patch: Water tile clusters with coastline length
  • belt_line: Belt network topology with line segments

Spatial Types:

  • Uses DuckDB spatial extension (PostGIS-compatible)
  • GEOMETRY columns for bounding boxes, areas, lines
  • POINT_2D for centroids
  • RTREE indexes for efficient spatial queries

ENUM Types: Dynamically generated from Factorio prototype data:

  • recipe: All craftable recipes
  • resource_tile: Ore types (iron-ore, copper-ore, coal, stone, crude-oil)
  • placeable_entity: All placeable entity names
  • direction: North, East, South, West, etc.
  • status: working, no_power, no_fuel, etc.

Example Queries:

-- Find nearest iron ore patches
SELECT patch_id, total_amount, 
       ST_Distance(centroid, ST_Point(0, 0)) AS distance
FROM resource_patch 
WHERE resource_name = 'iron-ore'
ORDER BY distance ASC;

-- Find drills without power coverage
SELECT d.entity_key, m.position
FROM mining_drill d
JOIN map_entity m ON d.entity_key = m.entity_key
WHERE m.electric_network_id IS NULL;

-- Find water sources for offshore pumps
SELECT patch_id, coast_length, total_area
FROM water_patch
WHERE coast_length > 20
ORDER BY coast_length DESC;

How Factorio Gameplay Works

Understanding Factorio's core progression is essential for designing effective AI agents. The gameplay follows a tightening flywheel pattern:

Primary Loop

  1. Prospect → Extract: Locate resource patches; transition from manual mining to automated drilling systems
  2. Process → Assemble: Smelt raw ores into intermediate materials; construct science pack production chains
  3. Research → Unlock: Feed science packs to laboratories to advance the technology tree, unlocking higher-tier automation
  4. Scale → Optimize: Expand production throughput, refactor inefficient layouts, increase sustained science-per-minute
  5. External Pressure → Defend: Manage pollution-driven enemy evolution; clear expansion areas and establish defensive perimeters
  6. Culminate → Launch: Construct and fuel a rocket silo; launching the satellite triggers primary victory condition

Post-Victory Loop (Optional)

  1. Infinite Research: Rocket launches yield space science packs that fuel repeating technologies, enabling long-term factory optimization beyond the win condition

Success Criteria

  • Primary: First rocket launch (victory screen)
  • Secondary: Sustainable defense against evolving enemies, increasing science-per-minute throughput, continuous technological progression

The loop forms an accelerating flywheel: Extract → Automate → Research → Scale → Repeat → Launch — with steady-state infinite research sustaining long-horizon optimization.


Context on the Table

FactoryVerse Overview

Rather than relying on visual interpretation or reactive object access, FactoryVerse makes implicit game context explicit through a queryable spatial database approach:

Spatial Intelligence

  • PostGIS Integration: Leverage mature spatial database capabilities for complex geometric queries
  • Multi-Scale Queries: Query factory state at different levels of abstraction (entity-level → spatial-level → global logistics)
  • Proximity Analysis: Identify spatial relationships, coverage gaps, and optimization opportunities

Temporal Analytics

  • Production Flow Analysis: Track resource throughput rates, identify bottlenecks, and optimize production ratios
  • Evolution Tracking: Monitor pollution spread, enemy base expansion, and defensive coverage over time
  • Performance Metrics: Sustained science-per-minute, resource efficiency, and factory growth patterns

This is attempting an opinionated equivalence between the game state mapped onto SQL tables.

Context Materialization

Instead of requiring LLMs to infer context from visual cues, FactoryVerse materializes this information as queryable data:

-- Find the best iron ore patches near your factory
SELECT 
  resource_name,
  total_amount,
  ST_Distance(centroid, ST_Point(0, 0)) AS distance_from_spawn
FROM resource_patch 
WHERE resource_name = 'iron-ore'
ORDER BY total_amount DESC, distance_from_spawn ASC;

-- Check which resources aren't being mined yet
SELECT resource_name, COUNT(*) as uncovered_patches
FROM resource_patch rp
WHERE NOT EXISTS (
  SELECT 1 FROM map_entity e 
  JOIN mining_drill d ON e.entity_key = d.entity_key
  WHERE ST_DWithin(rp.centroid, ST_Point(e.position.x, e.position.y), 2)
)
GROUP BY resource_name;

-- Find water sources for offshore pumps
SELECT patch_id, coast_length, total_area
FROM water_patch
WHERE coast_length > 20
ORDER BY coast_length DESC;

Why Database as Vision?

Instead of screenshot parsing, agents query precise spatial data via SQL—exact positions, connection topology, entity state. This enables multi-scale reasoning (single entity → factory-wide logistics) and complements visual approaches when needed.


Appendix

A. Infrastructure Details

A.1 Port Allocation Scheme

FactoryVerse uses incremental port allocation to support multiple concurrent Factorio instances without conflicts. Each instance is isolated from others and can be run in parallel.

Service Type Base Port Server N Client Purpose
RCON (TCP) 27000 27000+N 27100 Remote console communication
Game (UDP) 34197 34197+N Factorio multiplayer traffic
Snapshot (UDP) 34400 34400+N 34500 Real-time game state sync
Agent Notifications (UDP) 34202 34202+(N×10) 34202-34211 Async action completion events

Why This Matters:

  • Complete Isolation: Each server instance uses entirely separate port ranges—no cross-talk between experiments
  • Parallel Experiments: Run multiple agents on different servers simultaneously without configuration conflicts
  • Dynamic Allocation: Agent notification ports use find_free_udp_port() for flexible allocation

A.2 RCON and Async Actions

RCON commands execute synchronously within a single game tick. For multi-tick actions (walking, mining, crafting, research), agents use UDP notifications instead of polling—Lua state machines send completion events when actions finish.

A.3 Sync Services

Real-time synchronization flows: Lua → JSONL files (source of truth) → DuckDB tables, with UDP providing incremental updates. Sequence gap detection triggers disk replay if packets drop.

A.4 Factorio Remote Interfaces

Factorio's remote interface system (remote.add_interface / remote.call) is a global mechanism for cross-script communication. Mods register named interfaces that expose Lua functions callable from any context—including RCON.

FactoryVerse uses remote interfaces extensively:

Base Interfaces (exposed by mods):

  • admin: Development utilities (add_items, unlock_technology, clear_inventory)
  • agent: Agent lifecycle management (create_agent, destroy_agents, list_agents)
  • entities: Generic entity operations (set_recipe, set_filter, extract_inventory_items)
  • map: Snapshot and charting control (get_snapshot_status, set_snapshot_config)
  • test_ground: Scenario testing utilities (resource placement, area clearing)

Per-Agent Interfaces (dynamically created): When you create an agent, a new interface agent_{id} is registered with 30+ low-level action methods:

  • Movement: walk_to, stop_walking, teleport
  • Mining: mine_resource, stop_mining
  • Crafting: craft_enqueue, craft_dequeue
  • Entity ops: place_entity, pickup_entity, inspect_entity
  • Inventory: get_inventory_items, take_inventory_item, put_inventory_item
  • Research: enqueue_research, get_technologies, get_research_queue
  • Reachability: get_reachable, get_chunks_in_view

These are low-level, stateless function calls—essentially a procedural API for controlling agents.

Example Usage:

-- Creating an agent registers a new remote interface
local agent_data = remote.call('agent', 'create_agent', udp_port, ...)
-- Returns: {agent_id=1, interface_name="agent_1", ...}

-- Now "agent_1" interface exists with 30+ low-level methods:
remote.call("agent_1", "walk_to", {x=100, y=200})
remote.call("agent_1", "mine_resource", {resource_name="iron-ore", max_count=25})
remote.call("agent_1", "craft_enqueue", {recipe_name="iron-plate", count=10})

A.5 Snapshot System

Two-Phase Operation:

  1. Bootstrap: Initial map scan writes chunk-wise JSONL files ({chunk_x}/{chunk_y}/entities-init.jsonl)
  2. Maintenance: Event handlers append to update logs; UDP notifications trigger DuckDB sync

Architecture (src/FactoryVerse/game/infra/duckdb/):

  • database.py: DuckDB connection and schema management
  • loader.py: JSONL parsing and table population
  • sync.py: UDP subscription and incremental updates
  • query.py: SQL validation and typed entity construction

A.6 Configuration Management

Some settings cannot be pre-configured in Docker Compose and must be set after Factorio starts:

# Configure snapshot UDP ports after server startup
from FactoryVerse.utils.port_config import configure_all_server_snapshot_ports
configure_all_server_snapshot_ports(num_servers=3)

This injects the correct per-instance port into the Lua mod's runtime settings via RCON.

A.7 FV Placement Hints Mod

The FV Placement Hints mod (src/fv_placement_hints/) is a utility mod that consolidates spatial reasoning logic for entity placement. Unlike the core mods (fv_embodied_agent and fv_snapshot), this mod represents an organizational decision about where certain code should live rather than a fundamental architectural choice.

Design Rationale:

The core fv_embodied_agent mod is intentionally action-oriented—it provides the primitives for what an agent can do (walk, place, craft, mine) and reads into action-level state (position, crafting status, research progress). It deliberately avoids opinionated decision-making logic about where or how to place entities.

Spatial reasoning—figuring out valid placements, connection points, and optimal positions—is a separate concern. Different research approaches might:

  • Use visual/screenshot-based reasoning entirely
  • Implement custom planning algorithms in Python
  • Build neural network-based placement policies
  • Use the database queries for spatial analysis

By extracting placement hints into a separate mod, the fv_embodied_agent stays minimal and unopinionated, while researchers who want engine-level placement validation can use fv_placement_hints without it being bundled into the core action interface.

What It Provides:

Category Methods Purpose
Validation validate_placement, validate_positions Check if positions are valid for entity placement
Area Scanning get_valid_placements, get_resource_placements, get_water_placements Find valid positions in an area
Connection Solving get_item_drop_connections, get_fluid_connections, get_inserter_placements, get_pole_connections Find positions that connect entities (drill→chest, pipe→machine, etc.)
Entity Info get_entity_output_info, get_fluid_connection_points, get_entity_footprint Query engine-level entity data (drop positions, fluidbox connections)

Key Principle: All methods use engine-provided values (e.g., entity.drop_position, entity.fluidbox.get_pipe_connections()) rather than reimplementing prototype calculations. This ensures accuracy across Factorio versions and mod configurations.

Usage via RCON:

-- Validate a single position
remote.call("placement_hints", "validate_placement", "iron-chest", {x=10, y=20})

-- Find valid drill placements on resources
remote.call("placement_hints", "get_resource_placements", "electric-mining-drill", area, {max_results=50})

-- Find where to place a chest to receive items from a drill
remote.call("placement_hints", "get_item_drop_connections", "electric-mining-drill", drill_pos, "iron-chest")

Independence: The fv_placement_hints mod has no dependency on fv_embodied_agent and vice versa. They can be used together or independently.

B. Spatial Types

Factorio is a 2D grid-based world. The relevant game world precision is integer level, if you ignore real player position, vehicle position, and mine placement. In other words, everything else on the map snaps to an integer coordinate grid, with the smallest 1×1 size of this world called a tile.

While entities have centers that can be at half-tile coordinates (e.g., x + 0.5), we can represent them using their bottom-left corner coordinates and dimensions instead. This transformation converts all spatial data to integer coordinates, enabling fast spatial indexing and efficient database queries.

Spatial Types (ST) Standards: PostGIS implements the OGC Simple Features specification, providing standardized geometry types for spatial databases. For Factorio's 2D grid world, the most relevant types are: Point (entity centers, resource deposits), Polygon (building footprints, resource patches), LineString (conveyor belts, pipe networks), and MultiPolygon (complex resource areas). These types enable precise spatial queries like proximity analysis, coverage calculations, and optimal routing—transforming Factorio's implicit spatial relationships into queryable database operations.


FactoryVerse: Complex Systems need Intelligent Analysis

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages