Skip to content
Felucca Online

Docs

UO Renaissance base (land) tiles

Ultima Online draws terrain from land tiles: 44 px diamonds, each tagged with a terrain type. Most types have several texture variants plus edge transitions toward neighbouring types. This is the set of base terrain types found on the Renaissance-era (2000) Britannia, Felucca/Trammel and T2A Lost Lands maps, mapped onto our 1×1 ground-tile sprites (32 px diamond, see decision #34).

Each type is baked as 8 interchangeable variants in one sprite sheet (decision #43): the client picks one per map cell, and any variant joins any other without a seam, so large areas show no tile grid and no obvious repetition. Transitions between types are a follow-up. Transitions will probably be blended in the terrain shader rather than baked as sprites, because the client terrain is a real 3D mesh (decision #19).

UO terrainWhere in UOSpriteGroupStatus
grassmeadows everywhereground_grassvegetationdone (earlier)
forestforest floor under trees (grass + leaf litter)ground_forestvegetationdone
jungleT2A jungle, Lost Landsground_junglevegetationdone
swampFens, Lost Lands bogsground_swampvegetationdone
dirtroads, paths, bare soilground_dirtsoildone (earlier)
furrowsploughed farm fieldsground_furrowssoildone
sanddeserts, beachesground_sandsoildone
snowDagger Isle, mountain peaksground_snowsoildone
rock / mountainmountain slopes, mining spotsground_rockmineraldone
cavecave and dungeon dirt floorground_cavemineraldone
lavaFire dungeon, Lost Lands volcanoground_lavamineraldone
waterseas, lakes, rivers (animated in UO)ground_watermineraldone
cobblestonescity streetsground_cobblestonespaveddone
flagstonedungeon and keep stone floorsground_flagstonepaveddone
wooden floorsome dungeon/interior land floorsfloor_woodpaveddone (raised floor item)
voidblack cave/void filler (NoDraw)——engine only, no sprite

Per-tile checklist

Each tile follows the ground-tile rules in asset-pipeline.md: 1×1, an open surface sheet at Z >= 0 (no slab, nothing under the edge), padding = 0, variants = 8, chunky details that survive 32 px (1 texel ≈ 0.045 units), Principled BSDF only. Seamless across variants: a shared, periodic edge band + a zero-mean per-variant interior (rules and helpers in the "variants" section of tools/blender/models/ground_kit.py, reference ground_grass.py).

For every tile:

  • model script tools/blender/models/ground_<type>.py (built on ground_kit.py where it fits)
  • reads as its UO terrain at 32 px: colour palette, roughness, relief
  • seamless: no edge-only features; heights periodic over the tile
  • material maps: roughness/metallic/specular set; emissive only where real (lava)
  • baked with uo-baker bake-all --only ground_<type> --preview, validation passes
  • uo-baker tile-check assets/sprites/ground_<type> --mosaic 8 passes, and mosaic.png (8×8 random mix, albedo | lit) shows no border lines, no lattice and no variant that stands out (tile-check --all checks every tile)
  • preview.png checked: normals mostly blue on top, lit composite reads well

Type-specific notes:

  • forest: darker grass than ground_grass, brown leaf litter, a few twigs.
  • jungle: saturated dark green, large leaves/ferns low to the ground, damp soil.
  • swamp: dark olive mud, glossy (low roughness) water puddles, reed tufts.
  • furrows: parallel ploughed ridges along one tile axis, period divides the tile.
  • sand: warm pale sand, wind ripples, small shells/pebbles.
  • snow: bright blue-white, soft drifts, slight sparkle (low roughness spots).
  • rock: grey-brown broken rock, cracks, chunky boulders flush with the surface.
  • cave: dark brown-grey packed earth, pebbles, darker than ground_dirt.
  • lava: dark basalt crust plates with glowing orange-yellow cracks (emissive).
  • water: deep blue-teal, gentle periodic ripples, low roughness, slight specular.
  • cobblestones: rounded grey stones in rows with dark grout, grout lines on the tile edges split half/half so neighbours join.
  • flagstone: large irregular-looking flat slabs (grey-tan) with grout, same seam rule as cobblestones.

Base tiles: crops of 8×8 random-variant mosaics, albedo and sun-lit

Crops of 8×8 random-variant mosaics (albedo | lit). Left column: grass, dirt, forest, jungle, swamp, furrows, sand; right: snow, rock, cave, lava, water, cobblestones, flagstone.

Swamp and forest are edge-matched (stochastic Wang tiles, decision #46): every map edge gets a random type and each cell uses the variant whose four edges match, so features cross tile edges. Swamp: an edge is wet with p = 0.15; a wet edge carries a water neck crossing it and the tile's channels join its wet edges at a pond, so water forms meandering chains over several tiles and about half of the tiles (all-dry edges) have no water at all (64 variants). Forest: two leaf-row layouts per edge (p = 0.5) with leaves cut at the edge line, and leaf drifts spread evenly over the tile (16 variants).

edge_max_z in the manifest sets how high the compared edge surface may be: furrows 0.2 (tall ridges), cobblestones/flagstone 0.012 (only the grout joint, stones are meant to differ). Rock and lava plates stand above the default 0.04, so their check covers the cracks/channels at the edge; the plates themselves match exactly on the edge line by construction.

Known follow-ups

  • Rare features (sand footprints, a sunken cobble, the cave bone) live in one fixed variant, so they appear on ~1 tile in 8 at the same spot. Weighted variant picking in the client would make them rarer.
  • Jungle keeps a very faint lattice in lit views (leaves cannot cover the last ~0.015 of the edge without crossing it); flagstone shows straight grout lines on tile edges, inherent to laid slabs.
  • morph_voronoi (rock, lava) and the variance-preserving blend (cave, water) are duplicated in two scripts each; move them into ground_kit.py when a third user appears.
  • The client terrain shader still uses 5 procedural terrain ids. Done (decision #45): uo_proto::map::Terrain has all 14 types and the terrain shader draws these tiles with a hashed variant per tile (crates/uo-client/src/terrain_tiles.rs, Terrain tiles in rendering.md).
  • Transitions between types are still the generic dithered border; UO-style per-pair transition rules (which type overlaps which) are not modelled.
  • The water sheet uses only the water tile's colour and keeps procedural ripple normals: under the hard toon specular the baked ripples read as rows of streaks.