17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 00:59:18 +02:00
Commit Graph

3 Commits

Author SHA1 Message Date
maj00r
3876945ef3 Track what cooked data was made from, and cook it as a step of its own
A baked heightfield was trusted for as long as its file was readable, and the logic
container while the .scn was older than it: an edited include went unnoticed by both,
and node keys leaned on file dates, so merely touching a file changed them. And cooking
only ever happened as a side effect of starting a scenery.

Every cooked artifact now has a manifest beside it - <scenario>.ehf.deps,
<scenario>.logic.deps - listing each file it was made from with its size, modification
time and a hash of its content (simulation/cookdeps). The parser records what it
includes as it reads, so the list is exactly what the load read. An artifact is current
when every listed file still has its content: size and time are the quick check, and
when either differs the content is hashed, so a touched or copied file does not force a
rebuild and an edited one always does. A file added to the scenery cannot be in a
manifest; the files read are compared with it after the load, and a mismatch sends the
terrain to be baked on the next start - the new file's nodes, whose keys are unknown,
are drawn as geometry meanwhile - and the logic, cooked from the loaded state, is cooked
at once.

The terrain depends on what the scenario includes and not on the scenario file itself,
which the launcher rewrites on every start; the logic depends on both, since events can
be written in either, and costs milliseconds to cook again. With the manifest vouching
for file content, a triangle node's key is its file, line, include parameters and
placement, without the date.

-cook loads a scenery, brings whatever is stale up to date, and quits: the step that
prepares a scenery ahead of play. It closes the window directly, as there is no
simulation to wind down; a quit command waited a minute in the queue for one. Baking on
a first start stays as the fallback, and -nobaketerrain still turns it off.

Tried on a copy of baltyk with a terrain include of its own: a fresh cook bakes and
cooks everything, a second finds nothing to do, a touched file changes nothing, an
edited include is caught by its hash and both artifacts are rebuilt, and a new include
has the logic recooked at once and the terrain on the next run. A cooking run of baltyk
takes 5.5 s with nothing stale and 10.7 s with everything to do.
2026-09-17 17:45:23 +02:00
maj00r
951a54cde7 Bake only the ground a heightfield replaces, at a step suited to each triangle
Three changes to what goes into a baked heightfield and how it meets the geometry drawn
beside it.

The heightfield held every triangle laid in it, including those it does not replace and
that are therefore drawn as geometry too - so they were drawn twice, and a roof or a
bridge deck left in it bent the ground. Now the first pass lays everything and decides
what is replaced; the second lays the replaced triangles together with the neighbours
that support them - the owners of the corner samples of the cells around them, without
which those cells are not drawn - and only that is written. A first attempt that
repeated passes, dropping whatever bordered a hole, eroded the whole terrain away one
ring per pass: the edge of the ground is always a hole. The replaced triangles stay
replaced in the second pass, since their supports are all there and they have fewer
rivals. The triangles bake no longer keep in memory; they wait in a spill file beside
the heightfield while the scenery parses.

Triangles were refused for an edge longer than 200 m, meant for a water plane under the
whole map. On baltyk and tomaszewo that refused thousands of ordinary fields of a
hand-modelled terrain, and nothing there comes near a whole-map plane. Only triangles
standing on their edge - walls, trees, fences - are refused now. The bake log says why
the triangles not replaced are not: standing on edge, too small, under other surfaces,
next to a hole.

Hand-modelled terrain has triangles hundreds of metres long, elevation-model terrain
small ones along the line, and one grid step either loses the detail or samples a field
every metre. A bake now writes three heightfields a factor of four apart -
<scenario>.ehf, <scenario>.1.ehf, <scenario>.2.ehf - and each triangle goes to the
coarsest one that still puts sixteen samples along its longest edge, and to every
coarser one too, so the coarsest holds all the ground and a triangle at the edge of a
finer field can be replaced by a coarser field beneath it. All of them are picked up
together, and a missing or stale one sends the whole set to be baked again.

Where fields overlap each other or the geometry, the depth test decides: the terrain is
drawn with a polygon offset, negative for the reversed depth buffer and larger for each
coarser field, instead of being lowered by 2 cm - so geometry lying on the ground wins
at any distance and on any slope, and a finer field wins over a coarser one.

On tomaszewo the heightfields went from 176 MB to 10 MB, the load that bakes them from
5:16 to 3:11 and its peak memory from 15.7 GB to 8.5 GB - the scenery alone takes 8.0 -
while the triangles replaced went from 37,130 to 40,402. On baltyk from 19 MB to 2 MB,
with 6,670 triangles replaced against 4,040.
2026-09-17 17:24:25 +02:00
maj00r
2660d14565 Stream the scenery: a baked terrain heightfield, a logic container, and prefabs
Today's work on the binary scene format plan (binary-scene-format-plan.md), from the
first measurement to a terrain that bakes itself on first run and streams from a
thread. The step-by-step history is kept on backup/scene-streaming-format-presquash-0916.

Measuring first. -loadprofile times a scenery load by stage and by node kind and reports
node counts and resident memory. On l204 terrain is 95% of the bytes and 99.8% of the
nodes; on quarkmce2007 vehicles, not models, dominate what is left. The terrain turned
out to be a heightfield in all but name, a ribbon covering 12% of its bounding box.

The heightfield. scene/heightfieldformat.h is the .ehf layout: Morton-ordered tiles
of 128 samples with one shared edge sample, a point-sampled mip chain, zstd per tile,
a nodata sentinel, a material palette with a measured tiling per material, and a stamp
of the rules that decided what counted as terrain. scene/heightfieldreader.h reads it
for the tool, the renderer and the loader alike, and scene/terraincooker.h writes it,
shared by the terraincook tool and the engine so a first-run bake is the file the tool
would have made. The cooker rasterises every triangle where it is drawn (through
scene::place_in_world, the placement the region applies too), keeps an upper surface
laid within half a metre over another and the lower of two further apart, and then
asks each triangle whether the heightfield shows it: laid, reaching a sample, keeping
nearly all it claimed, and with the ground around it drawn at every level out to 2 km.

Baking on first run. simulation/terrainbake bakes <scenario>.ehf the first time a
scenery is opened, choosing the grid step from the first 200,000 vertices, and on later
loads leaves out the triangles its node table says the heightfield shows. Everything
else - trees, grass tufts, gantries, bridge decks - is drawn as written, so a bad bake
can at worst draw something twice, and the heightfield sits 2 cm under the triangles so
the original wins there. Nodes are keyed by file, line, include parameters and
placement; an edited file changes its keys and those nodes are drawn again. A bake under
older rules, or without its table, is thrown away and redone. A launcher copy named
$scenario shares the scenario's files. -nobaketerrain shows the triangles as written.
A scenery can still name hand-cooked fields, several at different steps.

Drawing and streaming. rendering/terrainclipmap draws resident tiles at the mip level
their distance earns, deriving the grid from gl_VertexID and texturing in world space
with the scenery's own materials. rendering/terraintileloader reads and decodes tiles on
a thread of its own; the renderer rescans what is in range only after moving a quarter
of a tile, asks for what is missing nearest first, uploads at most 32 tiles a frame,
drops arrivals the camera has moved past, and keeps drawing a tile at the level it has
until the right one arrives. The terrain has its own range, terrainrange in the ini and
a slider in the debug panel, 20 km by default and independent of basedrawrange; the far
plane of an ordinary view is pushed out to it. The debug panel shows the range, tiles in
view, on the gpu, wanted and queued, and how many sit at each level.

Events. The event queue moved out of the events into a binary heap with a deterministic
tie-break, and an event is handed its activator instead of storing it, so basic_event
is a definition and nothing else.

The logic container. scene/logicformat.h and scene/logiccontainer write a scenery's
events, their targets and sibling chains as flat, deterministic arrays with a schema
hash that refuses a mismatched build. simulation/logiccook cooks it from the loaded
state whenever it is missing, stale or unreadable, reads it straight back to check it,
and resolves each target to a position in the registry its event kind searches.
-cooklogic forces a cook, -verifylogic only checks. Nothing reads it at load yet.

Prefabs. scene/prefab describes a signal by the aspects it can show - lights, speeds,
animation channels, a sound - instead of pasting an .inc per variant, and expands an
instance to ordinary scenery text. Tried on a light signal and a shape signal.

Screenshots without a person present: -shot x,y,z[,yaw[,pitch]], -shotsettle, -shotstay.
2026-09-16 23:18:09 +02:00