Skip to content

Repository files navigation

WorldgenKit

An open-source, generalized infinite-worldgen engine for UE 5.8 -- extracted from the Backrooms project's WORLDGEN V2 architecture (apps/games/Backrooms/harness/WORLDGEN_V2.md). The goal: let anyone build their own infinite, procedurally-generated, chunk-streamed space -- a liminal-space horror game, an open world, anything cell-based -- by authoring DATA (a district-type table + a few small DNA classes), not by touching this repo's engine code.

This is a first extraction pass, not a finished toolkit. See "Known gaps" below for an honest list of what is still rough.

The four layers

1. MacroField   District(seed, districtCoord) -> {Type, DNA, LightTint,
                CorruptionBias, CeilingScale, IntensityScale}. Pure function,
                order-free, infinite. Type chosen by author-defined Rarity +
                a deterministic hash, corrected by canon-adjacency scoring
                against neighbor districts (WORLDGEN_V2.md Layer 1).
                -> Plugins/WorldgenCore/.../WgMacroField.h

2. Graph        Portals/adjacency between districts. NOT ported in this pass
                -- the streaming shell still guarantees full traversal (every
                chunk gets its 8 neighbors built), but there is no explicit
                portal-alignment layer yet. See Known gaps.

3. DNA          Per-district-type layout generator: one interface,
                WallAt(cell,edge) / FloorZ(cell) / CeilingZ(cell), that you
                implement once per district TYPE. This is the one layer that
                stays code on purpose -- geometry generation genuinely needs
                to be code -- but WHICH DNA class runs for a given named type
                is a data-table lookup, not an engine change.
                -> Plugins/WorldgenCore/.../WgDistrictDNA.h

4. Director     Anomaly/story-beat placement per resolved district. Pluggable
                interface, ships with a no-op base class so a world with no
                Director assigned still boots. NOT wired into the demo in
                this pass -- see Known gaps.
                -> Plugins/WorldgenCore/.../WgDirector.h
       CHUNK (render/stream host, ported from Backrooms proven plumbing):
       ask MacroField what district this chunk is in, get its DNA, sample
       every cell/edge, emit ISM instances.
       -> Plugins/WorldgenCore/.../WgChunkActor.h, WgWorldSubsystem.h

Repo layout

WorldgenKit/
  WorldgenKit.uproject          -- minimal UE 5.8 project, no authored map
  Plugins/WorldgenCore/         -- the reusable engine (this is what you
                                    would drop into your own project)
    Source/WorldgenCore/Public/
      WgTypes.h                 -- FWgDistrictCtx, EWgEdge, WgMath::FloorDivI
      WgHash.h                  -- deterministic hash utilities for DNA authors
      WgDistrictTypeRow.h       -- the DataTable/CSV row struct you author
      WgDistrictDNA.h           -- the DNA contract (abstract UObject)
      WgDirector.h              -- the Director contract (abstract UObject)
      WgMacroField.h            -- UWgMacroFieldResolver (Layer 1)
      WgChunkActor.h            -- generic chunk actor (streaming host)
      WgWorldSubsystem.h        -- generic spawn-ring/despawn-budget subsystem
  Source/WorldgenKit/           -- the tiny demo GAME module (not part of the
                                    reusable engine -- this is example content)
    WgDemoGameMode.h/.cpp       -- boots one of the two demo DNA sets
    DNA/                        -- the six demo DNA classes (3 per set)
  Content/WorldgenDemo/DNASets/
    DT_LiminalDistricts.csv     -- demo Set A
    DT_OpenWorldDistricts.csv   -- demo Set B

Running the demo

There is no authored map -- same "Generative Law, nothing handcrafted" pattern as Backrooms: Config/DefaultEngine.ini boots the stock /Engine/Maps/Entry with WgDemoGameMode, which spawns the world procedurally at BeginPlay.

$UE = "E:\Programming\tools\UE_5.8\Engine"
$P  = "E:\Programming\apps\games\WorldgenKit\WorldgenKit.uproject"

# Compile the editor target
& "$UE\Build\BatchFiles\Build.bat" WorldgenKitEditor Win64 Development -project="$P" -waitmutex

# Play the liminal-space demo set (default)
& "$UE\Binaries\Win64\UnrealEditor.exe" "$P" -game -windowed -resx=1280 -resy=720

# Play the open-world demo set
& "$UE\Binaries\Win64\UnrealEditor.exe" "$P" -game -windowed -resx=1280 -resy=720 -WgDNASet=OpenWorld

ADefaultPawn (the engine built-in free-fly pawn) is the demo controller -- WASD + mouselook, no gravity. There is no custom character/input code; the point of this project is the worldgen, not a controller.

The two demo DNA sets, side by side

Both are driven by the exact same C++ (WgMacroFieldResolver, AWgChunkActor, UWgWorldSubsystem) -- only the CSV data table and which DNA classes it points at differ.

Set A -- liminal space (Content/WorldgenDemo/DNASets/DT_LiminalDistricts.csv):

TypeName Rarity DNAClass What it does
Hallway 60 WgDNA_LiminalHall dense seeded wall grid, small rooms/corridors, flat 300u ceiling
StaticVoid 15 WgDNA_LiminalVoid sparse walls, big empty rooms, lower 220u ceiling
PoolAtrium 25 WgDNA_LiminalPool sparse perimeter walls, periodic basin footprint dips floor -60u

Set B -- open world (Content/WorldgenDemo/DNASets/DT_OpenWorldDistricts.csv):

TypeName Rarity DNAClass What it does
ForestClearing 55 WgDNA_ForestClearing WallAt always false, gentle +/-15u terrain undulation, no ceiling (open sky)
RockyOutcrop 25 WgDNA_RockyOutcrop WallAt always false, sharp +/-70u per-cell terrain, no ceiling
RiverBank 20 WgDNA_RiverBank WallAt always false, floor dips toward a meandering channel, no ceiling

The genericness proof is literally in that WallAt column: Set A's types use walls as their primary structuring tool (indoor maze rooms); Set B's types never draw a single wall and instead use FloorZ height variation and the Wg::NoCeilingZ sentinel to read as open outdoor terrain -- same three-function interface, same chunk actor, same streaming subsystem, completely different result, driven by which CSV the GameMode points the resolver at (-WgDNASet=Liminal|OpenWorld, see WgDemoGameMode::InitGame).

Authoring your own DNA set

  1. Write one UWgDistrictDNA subclass per district type you want (see Source/WorldgenKit/DNA/* for six worked examples). Implement WallAt, FloorZ, CeilingZ as pure functions of (GlobalCell, Ctx) -- use WgHash::HS01(cellX, cellY, salt, Ctx.Seed) for any per-cell randomness so it stays deterministic.
  2. Author a row per type, either:
    • as a UDataTable asset in-editor (Content Browser -> Miscellaneous -> Data Table -> pick row struct WgDistrictTypeRow), the normal UE path, or
    • as a CSV file in the format the two demo sets use (see the table headers in DT_LiminalDistricts.csv) and load it at runtime with UWgMacroFieldResolver::LoadDistrictTypesFromCSV -- convenient for headless/CI runs and for authors who would rather hand-edit a text file.
  3. Point UWgWorldSubsystem::MacroField at a configured UWgMacroFieldResolver before OnWorldBeginPlay runs (see WgDemoGameMode::InitGame for the exact hook -- InitGame runs during map load, guaranteed before world BeginPlay).

That is the whole authoring loop. No engine code changes.

How streaming works

UWgWorldSubsystem is a generalized port of BackroomsWorldSubsystem's "kept, proven plumbing" (WORLDGEN_V2.md): the same tuned constants (SpawnRadius 4, DespawnRadius 5, MaxSpawnsPerTick 2, ShiftUnseenSecs 30 -- Backrooms arrived at these through real playtesting) and the same shape:

  • Hard invariant: the pawn's chunk and its 8 neighbors are force-built same-tick, no budget -- makes a reachable streaming edge structurally impossible.
  • Budgeted ring spawn: chunks out to SpawnRadius spawn a few per tick (nearest-first), so filling in a big ring never reads as a visible hitch.
  • Budgeted despawn: chunks beyond DespawnRadius that have gone unseen for ShiftUnseenSecs despawn a couple per tick.
  • Deferred-spawn chunk build: AWgChunkActor::SpawnChunkDeferred uses SpawnActorDeferred -> Build -> FinishSpawning and Static-root/Movable- children component mobility -- both fixes for real UE engine gotchas (SetStaticMesh silently refuses on a registered Static component after BeginPlay) that Backrooms hit and documented in BackroomsChunk.h/.cpp.

Dropped from the Backrooms original on purpose (game-specific, out of scope for a generic engine): the Organism/heat attention system, the replicated world-state ledger, multiplayer proxy rings, floor descent. The "mark observed" check here is also simplified to plain chunk-distance; Backrooms adds a forward-view-hemisphere test out to 4 chunks that this pass does not reproduce.

Known gaps -- what is rough or stubbed

Being honest about what a first extraction pass does NOT yet cover:

  • No Graph/portal layer. WORLDGEN_V2.md Layer 2 (guaranteed-aligned doorways between adjacent districts via a shared boundary hash) is not ported. The demo stays fully walkable because the chunk streaming guarantees every neighbor chunk exists, but nothing aligns doorways across a district boundary the way Backrooms' portal hash does.
  • Director is unused in the demo. UWgDirector exists as an interface and compiles, but WgDemoGameMode never instantiates one or calls PlaceBeats. It is scaffolding proven to compile, not proven to run.
  • No floor/vertical descent. Everything is one Z band. Backrooms' pit -> floor-N+1 descent mechanic did not make this pass.
  • Simplified "seen" check. Plain 2-chunk-radius distance test, not Backrooms' forward-view-hemisphere test -- fine for a free-fly demo pawn, would want revisiting for a real game.
  • No collision/geometry polish. The chunk actor draws plain tinted boxes (walls/floor/ceiling from /Engine/BasicShapes/Cube) -- there is no door kit, no prop scatter, no material system. This proves the pipeline end to end; it is not a shippable look.
  • CSV parser is intentionally minimal. No quoted-comma support (the format sidesteps this by using ; inside the AdjacencyWeights cell, never ,) -- fine for this repo's two demo files, would want a real CSV library for arbitrary user data.
  • Not exercised in a packaged (non-editor) build. The CSV runtime-loader path (LoadDistrictTypesFromCSV) does not depend on the editor DataTable importer, so it should work in a packaged build, but this has only actually been run via the editor-launched -game path documented above.

Provenance / clean-room notes

  • MacroField hashing (WgHash::H32/HS) is a standard 32-bit integer-hash finalizer (xorshift-multiply mix) ported byte-for-byte from apps/games/Backrooms/Source/Backrooms/MacroField.h -- this is generic utility math, not creative or copyrighted content.
  • The Voronoi-feature-point blob-clustering structure (nearest seeded feature point over a 3x3 neighborhood) is the same algorithmic shape as MacroField.h/tools/prototype_macrofield.py, generalized to read its type palette from data instead of a hardcoded Surface/Work/Home/DeepRed biome hierarchy + enum.
  • The chunk deferred-spawn pattern and streaming-subsystem tuning constants are ported from BackroomsChunk.h/.cpp and BackroomsWorldSubsystem.h/.cpp because they are documented fixes for real UE engine behavior, not Backrooms-specific content.
  • Zero Kane Pixels / Backrooms-canon content: no measured room dimensions, no wiki lore, no photoreal texture kit, no door/prop asset references. The demo geometry is placeholder tinted boxes only.
  • Zero Tencent / HY-World dependency -- that path was evaluated and rejected before this project started; nothing here touches it.

License

MIT -- see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages