Repository navigation
The mod: hat-shaped cells, unbuildable bands, settings - #2
Merged
Merged
Conversation
added 17 commits
September 20, 2026 13:47
Refits the richness compensation against measured drillable fractions. The coefficients in the plan overestimated reachable ore by up to 26 percentage points; the fitted form is accurate to 3.8. A calibration test pins it to the measurements so it cannot drift silently.
Uses collision_mask_util.get_mask rather than assigning a fresh mask: a prototype that does not declare collision_mask inherits a per-type default, and overwriting it would strip the entity of every normal collision.
Rasterises each hat edge into its own bounding box rather than testing every tile against every edge: 39.7 ms/chunk -> 1.26 ms, a 31x speedup with identical output. The naive form would have stuttered visibly while exploring. tools/bench.lua now gates both stages.
Packaging is Python rather than a shell script calling zip(1), which is not installed everywhere. A test asserts the archive shape: one <name>_<version>/ directory with info.json at its root, the tiling core and its data present, and no spec, docs, tools or wiki files.
Bands now clone deepwater rather than stone-path: its transitions are authored for liquid meeting land, and it carries the only animated shader in the base game. Three styles select the effect over it -- liquid (animated), void (none), rift (lava, Space Age only, falls back to liquid and logs when absent). Adds the two settings that genuinely belong outside startup: a per-player cell outline overlay, and a map-scoped ore richness override where 0 means compensate automatically.
Playtesting found 26/2 cramped. Both settings are now dropdowns rather than sliders, labelled with the difficulty each implies -- the number alone does not convey it, and the interesting range is four values, not seventy-six. Still per planet. Also replaces the colour comparison sheet: the first one showed four near-blacks as two-tile lines, which were indistinguishable. The new one zooms in, widens the band and shows the shader highlight, which is what actually separates the options.
Colour is independent of style: the style picks the shader, the colour picks what it is tinted, so a glowing rift can be violet and a flat void can be ember. Colour is no longer per planet. It is one player-chosen look shared across every surface, so the mod reads as one thing rather than five. Reflected cells keep a stronger tint of the same colourway. Tests assert the colourways and the settings dropdown agree in both directions, that every one has a locale label, and that every body stays near-black with a lighter highlight -- without which an option is invisible.
The lava-shader style read as a hazard rather than a void and fought the calm look of the rest of the mod. Removing it also drops a Space Age conditional from the data stage. Highlights lifted about a third across every colourway, which is what the shader catches at a band's edges and the only thing that tells the options apart. Two tests now encode that: a highlight must be clearly lighter than its body, and no two colourways may have near-identical highlights.
Factorio's .cfg format documents ';' as a comment marker but does not say whether it is honoured mid-line. Vanilla never risks it: across 7,301 English values, zero contain a semicolon, while '#' appears four times. Cheaper to avoid than to depend on. Adds locale guards for the failure modes that are silent in-game -- a semicolon in a value, whitespace before '=' (which defines a key that never matches), duplicate keys, and keys outside any section.
The ore richness override could not be verified by observation: 0 means 'derive it from cell size and band width', and nothing reported what that derivation produced or whether an override took effect. Surface init now logs the effective multiplier and its source, and /hattorio-info reports the geometry and richness in force. Both read the same Surface.richness(), so the number shown is the number applied. Also warns when the mod is added to a world that already has terrain: existing chunks keep vanilla ground forever, leaving a permanent boundary that nothing can fix retroactively.
Checking all sixteen dropdown combinations found two that are broken: size 15 with band 4 leaves 46 buildable tiles and a 4x4 largest square, where a drill and an assembler are both 3x3, and 15/3 leaves 11% of ore reachable. The richness formula was worst exactly there -- it floored at 20% drillable where the truth is 5%, under-compensating where compensation matters most. Since the dropdowns offer exactly sixteen combinations and all sixteen are now measured, the table replaces the fit; the fit remains only as a fallback, refitted against all sixteen points (worst error 7.5pp, down from 14.6). Severe combinations now warn on load rather than being discovered an hour in.
The README claimed the mod was not written yet, which stopped being true several commits ago. Multiplayer is called out as unverified in both the README and the wiki, since the determinism argument has never been confirmed with two clients.
info.json is the single source of truth for the version. tools/check_version.py asserts the changelog documents it, leads with it, and opens with the 99 dashes Factorio requires, and that any release tag names it. That runs on every push, so drift is caught long before a release. Pushing a v* tag verifies the version, runs the full suite under Lua 5.2, builds the zip, attaches it to a GitHub release and uploads it to the mod portal via the documented two-step API. A portal release cannot be withdrawn, so the upload is gated three ways: only on a tag or an explicit dispatch, behind a mod-portal environment that can require a reviewer, and after every other check has passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Turns the finished tiling core into a playable mod. Terrain, build restrictions and settings all work and have been tested in game.
How building is blocked
The design spec called for script-side rejection — hooking build events and removing the entity afterwards. Factorio 2.0 turned out to offer something better: mods can declare custom collision layers, and two prototypes collide only if they share one.
So band tiles carry a
hattorio_bandlayer thatdata-final-fixesadds to buildable structures but not to the character, vehicles or rails. The game refuses placement itself: red preview, blueprints skip those tiles, bots never attempt them, no ghosts. Script rejection would have placed each entity and then removed it, needing four event handlers plus ghost cleanup to approximate one prototype field.It also removed the need for a per-chunk band bitmap in
storage— the collision system does that lookup.Performance
Band rasterisation started at 39.7 ms/chunk — fifty times the tiling descent, enough to stutter visibly while exploring. Rasterising each edge into its own bounding box instead of testing every tile against every edge brought it to 1.26 ms, a 31× speedup with byte-identical output.
tools/bench.luagates both stages.Settings
Per planet: cell size and band width, both dropdowns labelled with the difficulty they imply. Default 41/3, chosen by playtest after 26/2 proved cramped.
Global: band style (animated liquid / flat void) and colour (five options, deep violet default). Map-scoped: ore richness override. Per player: cell outline overlay.
Bugs caught along the way
collision_maskdefaults per entity type. Assigning a fresh one would have stripped entities of every normal collision. Fixed by usingcollision_mask_util.get_mask.LuaSurfacemethods take dot notation. A cachedget_tilewould have passed the surface wherexbelongs.;is a comment marker in.cfg; vanilla never uses one inside a value across 7,301 strings.Testing
82 Lua + 46 Python tests, luacheck clean.
mod/config.luaandmod/bands.luahave no Factorio dependencies, so they run in CI under Lua 5.2 likehat/does.Verified in game: loads clean (135 prototypes patched, 0 skipped), terrain generates without chunk-boundary seams, lakes survive, blocking works, rails cross, undergrounds tunnel, outline overlay works, richness override applies.
Not verified
Multiplayer. The determinism argument is sound — correctly-rounded IEEE operations, no
sin/cos/pow, one VM instruction per operation — but has never been confirmed with two clients. Plan 2's Task 10 is deferred by decision, and the README and wiki both say to play single player for now.