Entry 33
The field generator ported to Rust, heights bit for bit
Documented in Field generation.
Filestools/field_tables.pytools/test_field_rs.pycrates/piney-data/src/fieldcrates/piney-data/tests/field.rscrates/piney-data/examples/field_snapshot.rsdocs/engine/field.md
19 reproduced WORLD::Generate in tools/field.py; 25 checked it
on every volume. This entry carries it into the port as piney_data::field,
the same way 30 carried the dungeon generator. A helper agent did the
port; I re-ran its checks.
The tables
tools/field_tables.py rust ELF OUT writes
crates/piney-data/src/field/inf.rs (21 KB, exempt from rustfmt, with
--check). It holds:
- The object tables. The six tables, each row array written once: 55 arrays, 168 rows.
- Models. The field CCS file per type (
field_a...field_p;WORLD's constructor setsisHacked = 2, which picks that set overfield_a1), and the ground and cover model names. - Draw counts.
WORLD::Init's count per field type and weather, taken fromfield.py, which measured them in eemu. - Constants from
Generate's code. Each is checked againstfield.py:- the story areas without an entrance;
- the lake types and the tree-row type;
- the object counts 20/30/35/35, the per-type divisions and the 0.8/0.9/1.0 factors;
- the hill counts and sizes.
- Also: the lake water animation (
field_eff/ANM_sfwat1_1a), and which square root the executable'sccGetDistuses (newlib'ssqrtfin Infection).
Typed rows
The user asked why a row's type was an i32. The rows now carry:
ObjectClass { Key, Sub, Base, Enter }fortype;- a
Flatnewtype forflat, with theLEVEL,LAKEandSLOPEbits; rotflagas au8, because nothing gives it a meaning.
Tabulating them corrected the old comment: lake rows are 1 (sub), not 0.
Tree rows are 2 (base), and three of BaseObjTBL_I's rows are 1.
The agent then looked for readers.
typeandrotflag: none. The rows are reached only through the six tables. OnlyWORLD::InitandGenerate's setters use those, and they read the names, the size andflat.- Rotation.
FOBJECTandFOBJECT2keep no pointer to their row, and their constructors zero the rotation. So no field object is rotated, whateverrotflagsays. flat. Every row sets exactly one bit (118 / 5 / 45 rows for 0x2 / 0x4 / 0x8). The code tests them as bits, so the type keeps them as bits.
The port
generate(&INF, &Params) returns a Field, and ee does the float work.
- Params: the seed, field type, weather, ground, object setting, story area and protect flag.
- Field: the height map as the game's float bits, the four chip grids, hills, objects, cover tiles, entrance, start, the draw count and the final RNG.
ee: the FPU operations on bit patterns (truncation, no denormals, no infinities,maddrounding its product first). It never uses the host's IEEE arithmetic.
For a viewer, vertex(x, y) gives the ground in world units, and
placements(&INF) gives each object's CCS file, animation or clump, and
position.
Checked
tools/test_field_rs.py builds examples/field_snapshot and compares it
with field.py field by field. The height maps are compared bit for bit.
- Story areas. 104: every typeable story area with a field. That is 112, less 7 of type 4, less event 67, which shares 66's words. 10,500 objects and 155,113 cover tiles, with 0 mismatches.
- Random fields. 220, covering every field type, weather, ground and object setting; 48 carry a story area number and 17 have no entrance. 0 mismatches.
- The FPU. 36,000 EE operations against
eemu.py's, on random and edge operands, all equal. - The comparison bites. With
ee's add swapped for ordinaryf32addition, all 220 random fields differ. The EE model is needed, and the test notices when it is gone.
tests/field.rs pins one story area and three random fields to numbers
from field.py, including a hash of the height map. It also checks on the
disc that all 359 object, ground and cover names exist in their field's CCS
file.
On integration:
test_field_rs.py: 4 tests OK.field_tables.py rust --check: current.cargo test -p piney-data --lib --test field: 17 and 5 passed.- clippy: clean.
- Object heights. The game takes z from
WORLD::GetHeight, a collision query that is not ported.placementsuses 0 for levelled objects and otherwise estimates z bilinearly from the height map. - Drawing. How the ground mesh is built and where the cover is drawn. - After the start position. Anything rolled then, plus the weather effects andEntryGimmick's circles and treasure. - Other volumes and the area generator. The other volumes' tables, and the keyword-to-seed step, which is not ported. - Endless loops. Where the game would loop forever, the port gives up after a cap withNoSite. No case has reached it.