Skip to content

HammerForge MVP Guide

Last updated: September 3, 2026

This guide is for contributors implementing or extending the MVP.

Goals

  • Fast in-editor greyboxing with draft brushes.
  • Bake to optimized meshes only when needed.
  • Keep editor responsiveness high (no live CSG while editing).
  • Modular architecture with clear subsystem boundaries.

Architecture Overview

HammerForge uses a coordinator + subsystems pattern:

  • plugin.gd coordinates editor integration and routes to LevelRoot. Viewport arbitration, native RMB ownership, native object/Face Select pointer handling, EditorSelection synchronization and managed scope guards, undoable managed edit actions, entity/brush/prefab/material viewport drops, paint/draw/extrude pointer handling, numeric dimension entry, keyboard dispatch, vertex input, HUD state, command dispatch, overlay lifecycle/drawing, quick properties, and coach marks delegate to focused plugin_*.gd modules. It retains sticky active_root discovery with deep recursive tree search.
  • level_root.gd is a 2,851-line coordinator that owns containers, exports, and signals. Public methods delegate to subsystem classes; continued decomposition is tracked in the roadmap. Default UX is Draw → material → entity → bake → Test Level; power-user overlays are opt-in.
  • Subsystems (systems/*.gd) are RefCounted classes that do the real work. Each receives a LevelRoot reference in its constructor.
  • Runtime boundary: LevelRoot eagerly initializes only brush, entity, bake, paint, and file systems in export templates. Editor-only systems are loaded dynamically when Engine.is_editor_hint() or the editor feature is present, keeping the authoring graph out of exported games while preserving headless editor tests.
  • input_state.gd is a state machine managing drag/paint modes.
  • dock.gd presents 4 tabs (Build, Paint, Objects, Test) with programmatic, persisted collapsible sections. Tab workflows, file operations, visgroups/cordon, and signal wiring delegate to focused dock_*_handler.gd and dock_connections.gd modules while compatibility callbacks remain thin. Selection tools appear contextually in Build when brushes are selected. The primary toolbar exposes Draw, Select, Paint, More, and Help.

See DEVELOPMENT.md for the full file tree and architecture conventions.

Core Systems

Brush Workflow (HFBrushSystem + HFDragSystem + HFExtrudeTool)

  • DraftBrush nodes represent all authored geometry.
  • HFDragSystem manages the two-stage draw lifecycle (base drag -> height click) and owns the HFInputState instance.
  • HFExtrudeTool handles face extrusion: picks a face via FaceSelector, shows a preview, and commits a new DraftBrush on release. Supports Up (along face normal) and Down (opposite).
  • HFBrushSystem handles brush CRUD, pending/committed cuts, materials, picking, hollow, clip, tie/untie, move floor/ceiling, and UV justify. Failable operations (hollow, clip, delete) return HFOpResult with actionable fix hints.
  • HFSnapSystem provides centralized Grid, Vertex, Center, Edge-midpoint, and Perpendicular snapping. _snap_point() delegates to it.
  • Polygon and Path send their first point through the shared exact-surface raycast and _snap_point() pipeline. Subsequent points intersect the horizontal plane established by the first point before using the same snap pipeline.
  • HFInputState exposes get_drag_dimensions() for live W x H x D display during drag gestures.
  • PendingCuts allow staging subtract operations before applying.

Floor Paint (HFPaintSystem + paint/*.gd)

  • Paint layers store grid occupancy in chunked bitsets with per-cell material_ids and blend weights (blend_weights, blend_weights_2, blend_weights_3).
  • Layers optionally have heightmaps (Image FORMAT_RF) for vertex displacement.
  • Geometry synthesis runs per dirty chunk:
  • Flat layers: greedy rectangles for floors, boundary edges + merged segments for walls.
  • Heightmap layers: per-cell displaced quads via HFHeightmapSynth, walls still flat.
  • Per-chunk blend images drive a four-slot shader (hf_blend.gdshader).
  • Blend tool paints material blend weights (slots B/C/D) on already-filled cells.
  • Auto-connectors (HFConnectorTool) generate ramp/stair meshes between layers.
  • Foliage populator (HFFoliagePopulator) scatters MultiMeshInstance3D content with height/slope filtering and container-local transforms.
  • Stable IDs are used to reconcile generated nodes without churn.

Face Materials + Surface Paint (HFPaintSystem)

  • DraftBrush faces store material indices, UVs, and paint layers.
  • Materials are managed by a shared palette (MaterialManager) with library persistence (save/load JSON) and usage tracking.
  • HFPrototypeTextures provides 150 built-in SVG textures (15 patterns x 10 colors) loadable via the "Refresh Prototypes" button for rapid material testing.
  • Surface paint writes per-face weight images and updates previews.

Undo/Redo (HFUndoHelper + HFStateSystem)

  • HFUndoHelper.commit() wraps all editor actions with state snapshot restore on undo.
  • Command collation: pass a collation_tag for rapid operations (nudge, resize, paint). Consecutive actions with the same tag and same full_state scope within 1 second merge into one undo entry via MERGE_ENDS.
  • Transactions: state_system.begin_transaction() / commit_transaction() / rollback_transaction() for atomic multi-step operations.
  • State-tracked scaffolding: capture_state() / restore_state() covers brushes, entities, paint layers, TempFloor, and DefaultSun. All scaffolding created by Create Starter round-trips through undo/redo.

Entity Definitions (HFEntityDef)

  • Entity types and brush entity classes are data-driven via hf_entity_def.gd.
  • Loaded from entities.json or built-in defaults (func_detail, func_wall, trigger_once, trigger_multiple).
  • Dock brush entity class dropdown populated from definitions, not hardcoded.

Gesture Tracker (HFGesture)

  • Base class for encapsulated input gestures (hf_gesture.gd).
  • Holds root, camera, positions, numeric buffer. Subclasses override update(), commit(), cancel().
  • New tools should subclass this instead of adding modes to the HFInputState enum.

Bake (HFBakeSystem)

  • Assembles DraftBrushes (including generated flat paint geometry) into mesh output via CSG.
  • Heightmap floor meshes are duplicated directly into baked output (bypass CSG) with trimesh collision.
  • Supports single and chunked baking modes.
  • Optional: mesh merging, LODs, lightmap UV2, navmesh, collision.
  • Post-processing carries tied brush entities (func_detail, func_wall, and triggers) and their I/O metadata into runtime output without treating them as structural CSG.
  • Playtest export adds a player at the active spawn, preserves reparented world transforms, assigns recursive ownership to nested geometry/collision, and wires entity I/O when present.

Entities (HFEntitySystem)

  • Entities live under LevelRoot/Entities or is_entity meta.
  • Entities are excluded from bake.
  • Definitions are loaded from addons/hammerforge/entities.json.
  • Entity I/O: Source-style input/output connections stored as entity_io_outputs meta. Fields: output_name, target_name, input_name, parameter, delay, fire_once. Managed via add_entity_output(), remove_entity_output(), get_entity_outputs(). Connections serialize with point and tied-brush entity info in .hflevel, bake output, and playtest exports.

Subtract Preview (HFSubtractPreview)

  • RefCounted subsystem showing the live CSG cut between overlapping additive and subtractive DraftBrushes.
  • Uses live CSG intersection meshes when available, with an AABB wireframe as a fast fallback. The wireframe is one shared unit-box outline from HFOutlineUtil, placed per box with aabb_box_transform().
  • Debounced rebuild (0.15s), MeshInstance3D pool (max 50 entries).
  • Connects to brush_added, brush_removed, brush_changed signals for automatic updates.
  • Toggle via show_subtract_preview export on LevelRoot. Persisted in state settings.

Prefabs (HFPrefab + HFPrefabLibrary)

  • HFPrefab captures brush + entity selections with transforms relative to the group centroid.
  • capture_from_selection() computes centroid, strips brush_id/group_id, stores infos as dictionaries.
  • instantiate() assigns new IDs, offsets transforms by placement position, remaps entity I/O connections.
  • save_to_file() / load_from_file() use JSON with HFLevelIO encoding for Godot types.
  • HFPrefabLibrary (dock section) scans res://prefabs/ and provides drag-and-drop.
  • Plugin handles "hammerforge_prefab" drop type with raycast + snap + undo/redo.
  • Instance bookkeeping resolves brushes across DraftBrushes, PendingCuts, and CommittedCuts without adding frozen cutters to the editable brush cache. Point entities and tied brush entities resolve through HFEntitySystem. Missing recorded members produce warnings during removal and source updates.

Tutorial Wizard (HFTutorialWizard)

  • 2-step Draw → Test Level guide replacing the static welcome panel.
  • Each step listens for a specific LevelRoot signal (brush_added, paint_layer_changed, entity_added, bake_finished).
  • Optional validation (e.g. step 2 checks that the brush operation is SUBTRACTION).
  • Progress persisted via tutorial_step in user prefs; resumes on editor restart.
  • Dock highlight_tab() flashes the relevant tab on each step.

Brush Operations (HFBrushSystem extended)

  • Hollow (Ctrl+H): creates 6 wall brushes, deletes original. Configurable wall thickness.
  • Clip (Shift+X): splits a brush along an axis-aligned plane. Preserves material, entity class, visgroups, group ID.
  • Merge (Ctrl+Shift+M): combines 2+ selected brushes into a single brush. Applies full transform (rotation/scale) to face vertices, preserves per-brush materials via per-face material indices.
  • Tie/Untie: tag brushes as brush entity classes (func_detail, func_wall, trigger_once, trigger_multiple). Tagged brushes get color-coded overlays and may be excluded from structural bake.
  • Move to Floor/Ceiling (Ctrl+Shift+F/C): raycasts against other brush AABBs to snap vertically.
  • UV Justify: fit/center/left/right/top/bottom/stretch/tile alignment for selected faces.

Persistence (HFFileSystem + HFStateSystem)

  • HFStateSystem captures and restores brush/entity/paint/settings state for undo/redo.
  • HFFileSystem handles .hflevel save/load, .map import/export, and glTF export with threaded I/O.
  • See Data Portability for fidelity boundaries and current save-safety limitations.

High-Level Flow

  1. plugin.gd owns the EditorPlugin lifecycle and delegates input to focused plugin_*.gd adapters. The coordinator keeps typed LevelRoot and dock references; the static adapters receive the plugin as Object to avoid a circular script dependency.
  2. LevelRoot delegates to the appropriate subsystem:
  3. Draw tool -> HFDragSystem (drag lifecycle + preview)
  4. Extrude Up/Down -> HFExtrudeTool (face pick + drag + commit)
  5. Select tool -> HFBrushSystem (picking + selection)
  6. Paint (floor) -> HFPaintSystem -> HFPaintTool (layers + synth + reconcile)
  7. Paint (surface) -> HFPaintSystem -> SurfacePaint (per-face weight images)
  8. Face selection -> HFBrushSystem -> FaceSelector
  9. Bake -> HFBakeSystem (CSG assembly + mesh output)
  10. Save/load -> HFFileSystem (threaded I/O)
  11. State changes go through HFStateSystem for undo/redo capture.

Testing Checklist

  • Create and resize draft brushes (Draw tool).
  • Extrude Up (U) and Extrude Down (J) on brush faces; confirm new brushes appear with correct orientation.
  • Right-click during extrude to cancel; confirm preview is removed.
  • Apply/clear/commit subtract cuts.
  • Verify pending cuts appear orange-red, applied cuts turn standard red.
  • Bake (with and without chunking).
  • Enable Paint Mode and test Paint tab → Floor Paint section Brush/Erase/Line/Rect/Bucket.
  • Switch brush shape (Square/Circle) and verify radius fills correctly for each.
  • Test paint tool shortcuts: B/E/R/L/K in Paint Mode.
  • Verify live paint preview while dragging.
  • Switch paint layers and ensure isolation.
  • Enable Region Streaming, paint across regions, and verify .hfr files are created on save.
  • Import a heightmap (PNG/EXR) or generate procedural noise on a paint layer.
  • Verify displaced mesh appears under Generated/HeightmapFloors.
  • Adjust Height Scale and Layer Y spinboxes; confirm mesh updates.
  • Select Blend tool, choose Blend Slot B/C/D, paint blend weights on filled cells; verify four-slot shader blending.
  • Bake with heightmap floors and confirm baked output includes heightmap meshes with trimesh collision.
  • Save and load .hflevel with heightmap data; verify heightmap, material_ids, blend_weights, and terrain slot settings persist.
  • Click Refresh Prototypes in Paint tab → Materials section; confirm 150 prototype textures appear in the palette.
  • Assign a prototype texture to faces and verify preview updates.
  • Create a material resource (.tres/.material) in materials/ and add it to the palette.
  • Enable Face Select Mode and assign materials to faces.
  • Edit UVs and ensure preview updates.
  • Surface paint on a face with two layers and verify blending (Paint Target = Surface).
  • Toggle Use Face Materials and compare bake output.
  • Drag entities from the palette and check selection/exclusion from bake.
  • Verify shortcut HUD updates when switching tools/modes.
  • Verify tooltips appear on all dock controls.
  • Verify selection count appears in status bar.
  • Trigger a bake failure and confirm red error message with auto-clear.
  • Click Create Starter in the empty-state banner; confirm floor, sun, and spawn appear. Undo/redo and verify all three round-trip.
  • Run Test Level after Create Starter; confirm playtest lighting matches editor sun angle.

CI

Run gdformat --check addons/hammerforge/ tests/ and gdlint addons/hammerforge/ locally. These same checks run automatically on push/PR via .github/workflows/ci.yml.

Diagnostics

  • Enable Debug Logs in the Test tab → Settings section for runtime tracing.
  • Capture exit-time errors with:
Start-Process -FilePath "C:\Godot\Godot_v4.7-stable_win64.exe" `
  -ArgumentList '--editor','--path','C:\hammerforge' `
  -RedirectStandardOutput "C:\Godot\godot_stdout.log" `
  -RedirectStandardError "C:\Godot\godot_stderr.log" `
  -NoNewWindow

Resolved Issues

  • ~~Viewport drag-marquee selection is disabled.~~ Marquee selection for brushes, entities, and faces was added in the Improved Selection & Multi-Select wave.
  • ~~Multi-select can cap at 2 items in the viewport.~~ Multi-select works for arbitrary counts via marquee or Ctrl+Click.

Next Steps

  • Numeric input during draw
  • Material atlasing and compression
  • Additional import/export pipelines