Commit graph

5 commits

Author SHA1 Message Date
Master290
c10719cb78 Measure the surface water condition against the column's own water
The water condition asked "is this block at or above sea level", which is only
the same question as vanilla's in an ocean. Vanilla asks how far the block sits
below the water directly above it, and there is now water that is nowhere near
y=63: the aquifer puts pools at their own levels, deep underground and up in the
hills. Against sea level every one of those read as dry stone, and the stone
above them read as lakebed.

waterHeight is already tracked down the column, so the condition becomes the
vanilla one: pass when there is no water above at all, otherwise when
blockY (+ stoneDepthAbove where the rule asks for it) clears
waterHeight + offset + surfaceDepth * multiplier. add_stone_depth was parsed and
then ignored; three rules in the overworld tree set it.

NoWaterAbove replaces a bare math.MinInt so a hand-built context cannot default
to "water at y=0" by leaving the field unset.
2026-07-27 02:05:20 +03:00
Master290
21a10ab65e Implement the vanilla Aquifer; stop flooding caves
Every air block below y=63 was turned into water. That is one line of code and
it cost the entire underground: no dry caves, no lava lakes, no air pockets, a
solid block of water from the sea floor to bedrock.

Vanilla decides fluid per position instead. Aquifer centres sit on a jittered
16x12x16 grid; each gets a fluid level and type from the floodedness and spread
noises, with centres near open sky inheriting the sea and buried ones getting a
much lower randomised level or nothing at all. A position takes its nearest
centre's fluid unless the barrier noise raises enough pressure between the two
or three nearest centres to seal it back to stone. Deep centres turn to lava.

Porting it means fixing the order of generation, not just adding a file. Vanilla
resolves stone/water/lava/air during the density pass and only then runs the
surface rules over a finished column; we did it the other way round, which is
what forced the unconditional flood in the first place. fillVanillaColumn now
asks the aquifer per position, and applySurfaceRule walks the finished column
carrying the bookkeeping SurfaceSystem carries: air resets the counters, a fluid
records its water height, and stone gets a depth from the top of its run plus
one from the bottom, found by looking ahead to the next non-stone block below.

That last one fixes stone_depth's ceiling form, which had no bottom-up depth to
work with and was testing the top-down one instead -- fourteen rules in the
overworld tree use it to dress cave roofs. The floor form is unchanged: vanilla
counts from 1 and compares against 1 + offset, we counted from 0 and compared
against offset.

The aquifer grid is built eagerly per chunk rather than lazily, because our
columns fill concurrently; every cell is a pure function of its grid coordinate
and every cell in the computed range gets consulted anyway. Cost is ~0.5% of
chunk generation, most of it absorbed by the shared preliminary-surface cache.

Inland caves go from 100% water to 3.8%, and lava exists for the first time.
cmd/gendump grows a census that would have failed loudly before, and
TestCavesAreDry guards it in the suite.
2026-07-27 02:02:12 +03:00
Master290
90e9380ae7 Stop the saved world from masking generator changes
chunkAt prefers the store over the generator, and nothing invalidated a stored
chunk when the generator changed. Combined with four region files committed to
the repo, the chunks around spawn were frozen output from an older generator:
worldgen fixes looked like they did nothing in exactly the area you land in when
you join, and a fresh clone inherited that world.

Chunks now carry a RegionIOGeneratorVersion stamp, written on save and checked on
load; a mismatch returns ErrChunkNotFound so the caller regenerates. Chunks
written before the stamp existed have no tag, decode as 0, and are invalidated
the same way. Bump the constant in any commit that changes generator output.

This is deliberately per-chunk and deliberately quiet. The world metadata file
already guards the seed, where a mismatch means two incompatible terrains and
refusing to open is right. A generator change is routine by comparison and should
just regenerate.

world/region/*.mca and chat.md are untracked (left on disk) and /world/ is
gitignored, superseding the narrower /world/regionio-world.json rule, along with
/.refjava/ and root-level session transcripts.

CLAUDE.md covers the parts of working here that README does not: the
no-dependencies rule, the version-stamp rule and why forgetting it looks like a
failed fix, where the vanilla ground truth lives and how to query the jar
directly with unzip and javap, the precedent for dumping runtime constants with a
throwaway Java program, and an honest list of which layers are bit-exact versus
approximated.
2026-07-27 01:28:05 +03:00
Master290
cae06eb97e Implement multiplayer persistence and vanilla lighting 2026-07-21 09:21:44 +03:00
Master290
d1cc29bb60 Chunk persistence to Anvil .mca region files
The world now survives restarts: chunks load from disk (read-through
cache) and player edits persist via async autosave + a final SaveAll on
shutdown. RegionIO finally does region I/O.

- world/regionfile.go: Anvil .mca container — 8192-byte header
  (offset + timestamp tables), 4096-byte sectors, zlib chunk records.
- world/compress.go: zlib deflate/inflate for chunk payloads.
- world/store.go: chunk <-> Level-nested NBT (per-section
  block_states/biomes palettes, WORLD_SURFACE heightmap, DataVersion
  4790, yPos -4) via the existing nbt package; Store opens one
  RegionFile per region with proper floor-division coords.
- world/state_names.go: id->name bridge from the embedded blocks.json
  report so network int-IDs round-trip through the disk named palette.
- world/encode.go: GetBiome read accessor for serialization.
- world/cache.go: read-through (disk then generation), dirty tracking,
  StartAutosave (returns a done channel so the saver exits before
  Close), SaveAll, NewCacheWithStore.
- server.go + main.go: Config.WorldDir (default "world"), -world flag,
  autosave loop every 30s, SaveAll + store Close on signal.
- Tests: region round-trip/absent/overwrite, chunk NBT round-trip,
  end-to-end save-reload, negative chunk coords, autosave persistence.
2026-06-25 00:40:14 +03:00