Forge Port Guide
How to express a schema-based object (region, area, creature, vehicle, quest)
as a forge-backed kind. This guide uses plant_region as the reference
implementation; code paths point to the committed port.
Note: This is a living document. The forge-runner extraction plan (plan-forge-runner.md) will extend it once generic host concerns are separated from
plant_world.
Lifecycle Overview
Schema spec
|
v
ForgeGraph (graph.rs) -- nodes + edges describing the kind
|
v
run_generators (generators.rs) -- JTL generators populate ForgeHost buckets
|
v
compile_forge (compiler.rs) -- topological compile, assigns genomes
|
v
ForgeArtifacts -- compiled nodes, links, chem_state, provenance
|
v (domain-specific projection step)
Kind artifacts (e.g. CompiledRegionArtifacts)
|
v
ForgeRegistry (registry.rs) -- Pending -> Generating -> Compiled -> Registered -> Active
|
v
ForgeService (service.rs) -- ForgeService adapter; drives registry through frame phases
The domain-specific projection step is the only phase that knows about kind-specific types. Everything else is generic forge machinery.
Seam Types
ForgeSeedLike
// forge/src/compiler.rs
pub trait ForgeSeedLike: Copy + Send + Sync {
fn base_seed(&self) -> u64;
}
Implement this on your seed type. The base seed drives deterministic genome assignment across all nodes in the graph.
Region reference: region/src/seed.rs — impl ForgeSeedLike for RegionSeed
ForgeGraph
// forge/src/graph.rs
pub struct ForgeGraph { pub nodes: Vec<ForgeNode>, pub edges: Vec<ForgeEdge> }
pub struct ForgeNode { pub node_id: String, pub kind: ForgeNodeKind, pub params: BTreeMap<String,Value> }
pub struct ForgeEdge { pub from: String, pub to: String, pub kind: ForgeEdgeKind }
Build one from your spec type. The conversion belongs on the spec type as a
to_forge_graph(&self) -> ForgeGraph method.
Region reference: region/src/graph_spec.rs — RegionGraphSpec::to_forge_graph()
ForgeExtension
// forge/src/compiler.rs
pub trait ForgeExtension<S: ForgeSeedLike> {
fn on_node(&self, node: &mut CompiledForgeNode, seed: S);
fn on_link(&self, link: &mut ForgeLink, seed: S);
}
Optional. Pass None if no per-node overrides are needed. Region currently
passes None; extension hooks were kept for future use.
ForgeHost
The JTL host used during generator execution. Create with:
ForgeHost::new(genome_hex, trait_map)
After run_generators(program, host) returns, extract named buckets:
let config_bucket = host.take_bucket("config");
let graph_bucket = host.take_bucket("graph");
Region reference: region/src/generators.rs — run_region_generators()
ForgeRegistry / ForgeRecord
Generic over TCompiled (your kind's compiled artifact type). States advance
linearly: Pending -> Generating -> Compiled -> Registered -> Active.
Bulk advance via:
registry.advance_all_from_state(ForgeLifecycleState::Generating);
ForgeService
Wraps ForgeRegistry<TCompiled> and implements ForgeService. Frame hooks
drive state transitions automatically:
| Frame phase | Transition |
|---|---|
on_init |
Pending -> Generating |
on_active |
Generating -> Compiled |
on_aggregate |
Compiled -> Registered |
on_cleanup |
Registered -> Active |
Porting a New Kind: Step-by-Step
1. Add plant_forge dependency
# your-crate/Cargo.toml
[dependencies]
plant_forge = { workspace = true }
2. Implement ForgeSeedLike for your seed
impl plant_forge::ForgeSeedLike for YourSeed {
fn base_seed(&self) -> u64 {
self.primary_seed ^ self.secondary_seed.rotate_left(21)
}
}
3. Add to_forge_graph() to your spec type
Map your spec nodes/edges into ForgeNode / ForgeEdge. Node kinds that have
no direct forge equivalent use ForgeNodeKind::Generic.
4. Write a projection function
// your-crate/src/compiler/projection.rs
pub fn project_forge_artifacts(
spec: &YourSpec,
seed: YourSeed,
artifacts: ForgeArtifacts,
) -> Result<YourArtifacts, String> {
// Map CompiledForgeNode -> your domain type.
// Copy over chem_state, provenance, dirty, warnings.
}
Keeping the projection in its own file makes the seam visible and testable.
Region reference: region/src/compiler/projection.rs
5. Delegate compile and generators
// compile path
let forge_graph = spec.to_forge_graph();
let forge_artifacts = compile_forge(&your_id, &forge_graph, seed, None)?;
let your_artifacts = project_forge_artifacts(&spec, seed, forge_artifacts)?;
// generator path
let host = ForgeHost::new(genome_hex, trait_map);
let host = run_generators(program, host);
Region references:
region/src/compiler/compile.rs—compile_region_inner()region/src/generators.rs—run_region_generators()
6. Register with ForgeService (when forge_runner is available)
let service: ForgeService<YourArtifacts> = ForgeService::new("your_kind".to_string());
let registry = service.registry(); // Arc<RwLock<ForgeRegistry<YourArtifacts>>>
frame_manager.register_service(service);
Naming Conventions
| Concept | Name pattern |
|---|---|
| Seed type | YourKindSeed |
| Spec -> graph conversion | YourKindSpec::to_forge_graph |
| Forge -> kind artifact mapping | project_forge_artifacts |
| Generator host wrapper | YourKindGeneratorHost |
| Registry | ForgeRegistry<YourArtifacts> |
| Service | ForgeService<YourArtifacts> |
Source Map
| File | Role |
|---|---|
forge/src/graph.rs |
ForgeGraph, ForgeNode, ForgeEdge |
forge/src/compiler.rs |
compile_forge, ForgeSeedLike, ForgeArtifacts |
forge/src/host.rs |
ForgeHost, ForgeEvent |
forge/src/generators.rs |
run_generators |
forge/src/provenance.rs |
ProvenanceLog, CompileEvent |
forge/src/dirty.rs |
DirtyFlags |
forge/src/registry.rs |
ForgeRegistry, ForgeRecord, ForgeLifecycleState |
forge/src/service.rs |
ForgeService (ForgeService adapter) |
region/src/seed.rs |
ForgeSeedLike impl (reference) |
region/src/graph_spec.rs |
to_forge_graph() (reference) |
region/src/compiler/compile.rs |
compile path delegation (reference) |
region/src/compiler/projection.rs |
project_forge_artifacts (reference) |
region/src/generators.rs |
ForgeHost + run_generators (reference) |