Skip to content

[pull] master from beyond-all-reason:master - #216

Merged
pull[bot] merged 3 commits into
ExaDev:masterfrom
beyond-all-reason:master
Sep 9, 2026
Merged

pull[bot] merged 3 commits into
ExaDev:masterfrom
beyond-all-reason:master

Conversation

@pull

@pull pull Bot commented Sep 9, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

Ruwetuin and others added 3 commits September 9, 2026 12:47
**Player Facing Summary**
- After 15 minutes into the game, zombies will swarm all teams evenly
once they reach 10% of the value of all players combined.
- Zombies spawn with XP skewed to the minimum veterancy. 
- When zombie revive timer has been reset, a purple poof now appears
above it.
- Units that don't leave corpses like the Fiend will no longer respawn
as zombies.
- Zombie constructors get a boosted capture range of a minimum of 300.
This makes them capable of capturing aircraft.
- Zombies now can control aircraft when they're captured or produced.


Zombie ai is now moved into its own gadget because it was getting too
confusing to have it all in one place.

**AI Rework**
- reworked the zombie ai to have a persistent move goal that it
remembers and falls back to when it's done fighting things. Changes when
it gets stuck or some conditions change
- zombie aggro is distributed fairly. All zombies are ranked by most to
least powerful then random-sequentially picked for all alive allyTeams.
- when aggro'd, zombies will move towards a random target's position
belonging to the aggro'd allyteam. It'll change targets when it gets
close enough.
- aggro behavior triggers when zombies have 10% of the value of what all
the players combined have. This is to force a desparate survival
endgame. This can only happen after 15 minutes

**Other Changes**
- Zombies will now have a drastically lower chance of spawning with max
veterency. It's now biased using disadvantage mechanics (like from DnD)
to select the lowest of a few randomly generated numbers between
0.0-1.5.
- there's now a little purple "poof" that emits above a corpse after its
timer has been reset by reclaiming or resurrecting it a little bit.
- fiends will no longer resurrect
- constructors now have at least 300 range as zombies. This makes them
capable of potentially capturing aircraft. oh yeah,
- aircraft can now become zombies and can be controlled as zombies.
They'll fly around fighting things.

LLM Disclosure:
Used Cursor's Grok 4.6, Cursor's auto mode, Fable 5.1 a bit, GPT 5.6 a
bit. The AI did what I told it, I tested and reviewed code as I went. I
did a final pass checking for hallucinatory stuff and it seemed alright
and human readable to me.
…box (#9087)

Branch: `tf-brush-improvements-14` 

Continues the Terraform Brush work. Two threads this time. The first is
the campaign terrain tutorial MrBob recorded for the art team: watching
a whole map get sculpted with one brush highlighted some potential
improvements in handing brush strokes. The second is a batch of asks
from using the editor on actual maps: soft influence bands for
texturing, sun presets that survive a project load, an Open Project
browser that copes with a cloned maps repository, and editor sessions
that stop spawning commanders into the canvas.

## Features

**FOLLOW STROKE.** A chip in SHAPE turns the brush shape to the
direction you are dragging, so a square, hexagon or triangle leaves a
ribbon with its flat sides along the path instead of a chain of
same-angle stamps. The angle is smoothed over the last few dabs so mouse
jitter cannot spin the shape, and quantised to 2 degrees, the step the
brush's own falloff cache keys on. The protractor snaps it to the spoke
grid when both are on, the brush ring shows the angle that will land,
and your own rotation comes back when the stroke ends. Offered for the
sculpt modes only: the other tools sharing the SHAPE row stamp rather
than stroke. A **CLAY SCULPT** preset ships the whole setup MrBob makes
a map with: clay on, intensity at the floor, the sharpest falloff,
square with FOLLOW.

**PASSABILITY overlay.** Every DISPLAY row gains a chip that tints
ground steeper than a move class in the engine's impassable purple, so
cliff height can be judged while sculpting without selecting a unit and
pressing F6. Clicking cycles BOT, VEH, HOVER, AMPH and off, and the
slopes are read off those units' real movedefs so the band matches what
F6 draws. Needs the tileset shader.

**INFLUENCE bands in SURFACE and LAYERS.** An altitude band and a slope
band, each with a feather, that scale a stroke instead of cutting it the
way the FILTERS do, so a texture does more of its thing in the lowlands
or on the flats and fades out beyond them. SURFACE remembers a profile
per texture (it follows the texture across slots and biome swaps,
projects keep it in `surface.lua`, and Copy to all stamps it onto every
slot); LAYERS keeps one per channel. Erasing is never scaled, and the
Ctrl sneak peek shows the band, so what you see is what lands.

**SURFACE SCATTER.** Position, size and strength jitter per stamp, on
top of SPACING. With spacing near one and a half brush widths, one drag
lays the hand-placed dot field the texturing pass wants instead of a
solid band. The DOT preset now carries a generous falloff and the
scatter, which is the texturing brush from the tutorial: dots with gaps,
never fills.

**Sun & Shadows PRESETS.** Three sun-only times of day (Canonical, Dusk,
Overcast: direction, intensity, sun colours and shadow densities,
nothing else), the harvested map moods the New Map wizard offers, and
your own saved files. Save writes the whole live environment under a
name to `Terraform Brush/Environments/`, so a look carries to every map
and session; a SUN ONLY / FULL ENVIRONMENT switch decides what a click
applies. The sun direction also gains AZIMUTH and ELEVATION sliders next
to the vector rows.

**Open Project browser.** A search box (matching the name, the folder
path or the NxN size), RECENT / NAME / SIZE sort chips and a folder
tree. Projects may live in subfolders of `MapProjects/` up to four
levels deep, and a name may contain "/" to save into one, so a git clone
of a maps repository placed inside `MapProjects/` lists as it is on disk
and pulls straight into the browser. The date column reads as an age,
and a project saved this session lists even while the engine's folder
snapshot cannot see it yet.

**AUTOMATIC DEPOSIT.** A SURFACE variant slot the shader claims on its
own where wind-blown sand would gather: on the lee side of slopes
relative to a wind direction, and in the pockets the intermediary
already reads. It only fills ground no stroke claimed, so paint wins,
cliffs stay clean, and it is off until a slot is chosen.

**METAL SPOTS glow controls and BAND WIDTH.** The glow light gets the
LIGHTS tool's point-light controls (intensity, radius, height, colour
swatches with a preview bar and RGB sliders), graying out while the glow
is off. BAND WIDTH sets how far the silhouette band, the rubble skirt
Band layer and Band tone shape, reaches around a spot.

Also: per-texture SELECTED SLOT tints in SURFACE > GRADING, SURFACE
palette captions that name each texture's role (TINT, NORMAL and so on),
`/tf_sunlog` for finding what reset a sun.

## Focus mode

An eye button next to the pause button in the panel header. It hides the
game interface the way F5 does but leaves the editor alive: the panel,
the brush ring, the grid and water overlays and skybox switching all
keep working, so a map can be looked at, sculpted and screenshotted
without the HUD. RmlUi documents are rendered by the engine outside the
hidden-interface gate, so the panel survives on its own; the brush
widget reads the focus flag to keep drawing its overlays, and the flag
is set with the explicit `hideinterface 1` / `0` forms so it cannot
desync from F5.

Skybox picks used to wait for the HUD to come back: the widget handler
skips `DrawScreen` while the interface is hidden, and that was the one
draw call-in the pending skybox apply lived in. The panel's deferred
applies (skybox, New Map environment preset, fog-off, forcestart) now
drain from `DrawScreenPost`, and the same audit moved two more jobs that
would have stalled the same way: the brush widget's heightmap import and
export and New Map drive, and the map project widget's Save / Open
driver.

Exits: the button, closing the panel or deactivating every tool, a
`/luaui reload`, and F5 from outside all hand the interface back. Hiding
the panel with its hotkey does not, so focus plus a hidden panel is the
clean-screenshot setup. A map capture started in focus mode still comes
out clean and leaves the interface hidden afterwards.

## Editor auto-open on New Map and Open Project

A New Map and an opened project bring the Terraformer up by themselves:
the brush arms in RAISE the moment the canvas is playable, at the New
Map's forcestart and at the end of a project's load phases, unless a
tool is already up. A plain `/luaui reload` on a running canvas does not
trigger it.

## WORLDSPACE TINT (WIP)

A new section in the Tileset window (after BIOME TINT) that makes
world-height tinting one system with a wide look range, from the old
three-colour grade to a layered canyon. One shared AXIS feeds every
lever: Auto rides the map's height reference, so a flat canvas stays
untinted; Manual pins it to elmos and can be tilted along a direction
and wobbled. GRADE is the old three stops as colour chips with a movable
split. STRATA lays repeating tint beds along the axis (one to eight,
period, phase, hardness from a soft crossfade to a knife edge, wobble,
per-bed jitter), with BASE / INTER / CLIFF / PLAT chips choosing which
layers take them and a colour chip per bed. GRADIENT STOPS is MrBob's
ask from Discord: an array of two to eight hue / saturation / value
entries spread evenly over the height, in Multiply or Colorize mode.
RAMP samples a painted gradient image along the axis (a PNG from
`Terraform Brush/Ramps/`, picked from a file list with Rescan / Clear)
in Multiply or Tint mode, with a strength and a repeat count. SATURATION
scales colour from base to top, TIDE RINGS darkens rings above the
waterline, and SNOW LINE whitens the tops above a height with a band, a
slope limit and its own colour.

Every colour chip (grade stops, beds, gradient stops, snow) selects what
one shared COLOUR editor edits: the swatch palette, a preview bar, and
Red / Green / Blue plus Hue / Sat / Value sliders that work for every
chip whichever way it is stored, so a bed can be dialled in HSV and a
stop picked from the RGB palette. Each sub-group has its own RESET and
the header RESET takes them all. The chips are painted from the knob
table, so a preset, a project load or a console `/tileset set` lands in
the UI. The ramp file is saved with the project in `tileset.lua` next to
the biome key and applied on Open. Needs the tileset shader (0.27); with
the shipped defaults a map renders exactly as before. The section is
marked WIP in the panel: it will get more work in a later release.

## IMAGE overlay

Every DISPLAY row gains an IMAGE chip: lay a reference image over the
whole map, a real coastline to sculpt against or a transparent sheet of
guide lines, and paint over it. Images are read from `Terraform
Brush/Overlays/` (PNG, JPG, TGA, BMP or DDS; alpha is kept, so only the
lines of a transparent sheet show). The gear next to the chip, or a
right click on it, opens the shared IMAGE OVERLAY window: the file list
and the placement controls, OPACITY, OFFSET X / Y and SCALE sliders that
preview live on the ground while you drag, STRETCH or KEEP ASPECT, FLIP
H / V and RESET; a left click on the chip toggles the overlay once an
image is loaded. The image is projected onto the ground through the
map's depth buffer, so it hugs every slope at any map size for one
fullscreen pass, and units and features draw over it (it needs
`AllowDeferredMapRendering`, the BAR default). The pick and placement
survive a reload; `/tfimage <file>` picks a file from the console,
`/tfimage` alone toggles it, `/tfimage off` unloads it. New files:
`luaui/Widgets/cmd_terraform_image_overlay.lua` and the two
`luaui/Shaders/terraform_image_overlay.*.glsl`.

## Sculpt performance

huskyvoiceyui and Moose reported 10 to 15 fps lost while dragging the
clay brush on a big map, closer to 8 fps with FOLLOW STROKE on a square
brush, and concentric rings on every clay stroke. Four things were
behind it. The gadget committed one `SetHeightMapFunc` and one undo
entry per dab, up to 48 a tick on footprints overlapping about 85 %, so
the engine recalculated the terrain (mip heightmaps, normals, slopes,
pathing, LOS, the normal and shading textures, mesh patches) up to 48
times a tick. The falloff-stamp cache keyed the rotation-invariant
circle on angle and held four entries, so FOLLOW STROKE, whose angle
changes every dab, rebuilt a stamp per dab. The widget forced a mesh
re-tessellation for ten frames after every tick, which at 20 Hz is every
frame of a drag. And the panel mirrored terraform state at frame rate
with about fifty unguarded data-model writes and nineteen raw slider
writes.

- **A tick's dabs apply as one batch.** The dabs of a
`$terraform_stroke$` message work against a copy of the cells they touch
and the tick commits once: one heightmap write, one undo entry. Dab k
still sees dab k-1's writes. A stub-engine harness fed both versions the
same strokes: heightmaps identical to float noise, engine recalcs and
undo entries per stroke down 6x, Lua time down about 3x, undoing a
stroke 4x faster.
- **Stamp cache.** Circle and ring are keyed angle-free, the cache holds
stamps by a 3M-cell budget instead of four slots, and only very large
shaped stamps quantise the angle coarser (2 degrees up to 40k cells, up
to 30 degrees beyond), so a FOLLOW drag at big radius cannot rebuild a
100k-cell stamp per dab.
- **Mesh refresh** is armed for two frames from the engine's own
heightmap-update event, i.e. once per landed edit, instead of ten frames
per brush tick.
- **Panel** mirrors terraform state on every 4th frame while the brush
is down on the world (frame rate again the moment the mouse is over it),
and its per-frame slider writes go through the dirty-check helper.
- **Performance mode** (Settings > General, persisted) for big maps and
slower machines: wider dab spacing where the falloff can take it (a
quarter radius for soft curves and clay, a fifth up to curve 2.0,
unchanged above), 32 dabs per tick instead of 48, 6-degree FOLLOW steps,
and the panel mirror strided all the time. Its description names the two
free levers, pausing the game and Focus mode.
- **Clay rings, at the root.** The clay plane was the live centre height
plus the layer step, re-measured every tick, so from the second tick on
it sat on the disc the previous tick had just raised: every tick stacked
another disc one layer up, and the disc edges read as rings (a slow
40-tick drag at intensity 10 piled 2700 elmos). The plane now comes from
the pre-stroke surface, averaged over the dab centre and four taps half
a radius out, so every dab of a stroke agrees on one plane and the
stroke lays exactly one layer of INTENSITY x 8 elmos. Settings > Stroke
> Clay build-up restores the old stacking for anyone who wants a held
brush to keep piling.

## Improvements

- **Sculpt strokes follow the path the mouse actually drew.** The brush
sampled the cursor 20 times a second and bridged a straight chord
between samples, so a fast scribble arrived with its corners cut and its
dabs delivered in visible bursts. Every mouse move is now recorded and
the tick walks that polyline, dropping dabs at the brush's own spacing.
If the callin never fires, the walk degrades to exactly the old chord.
- **Clay strokes deposit per distance travelled, not per tick.** A
tick's intensity was divided by the number of dabs it contained, and
each dab re-derived its target plane from the already-raised centre, so
the deposit fell with the square of the dab count: at the lowest
intensity a slow drag laid about 0.08 elmo per dab and a fast tick with
48 dabs about 0.00003. Speed removed the stroke. A whole tick now goes
out as one message and the gadget derives every clay plane from the
heightmap as it was before the tick, so nothing compounds and the
per-tick rise is still bounded by one brush step. It also cuts up to 48
network messages a tick down to one, which the multiplayer co-editing
work will want.
- **SURFACE brush size steps proportionally** (a tenth of the current
size per scroll notch instead of a flat 8 elmos), so a small brush can
be tuned.
- **SAMPLE buttons on the SURFACE altitude bounds**, like the raise
tool's HEIGHT CAP: the next click on the terrain writes that height into
the bound, pushes the other bound along if the band would invert, and
switches the filter or band on.
- Project folders and names may contain spaces; Open Project rows are
larger and the window wider.
- The sun sliders keep the applied intensity on every nudge, re-assert
ground and unit shadow densities separately, and restamp each other so
the vector rows and the azimuth/elevation rows never disagree.

## Fixes

- **The SURFACE Ctrl sneak peek works at every zoom again.** It only
exists in the tileset shader's live path, and 1.13's far cache and
clipmap take over the ground as soon as the camera pulls back, so the
preview quietly stopped working past a close zoom. A pixel under the
brush now takes the live path whatever the distance, and the cache-only
shader program stands down while a peek is up.
- **The sun config survives a project open and a preset.** Three paths
could still lose it: the ENV panel's RESET buttons returned to the
lighting the engine held when the panel first opened, which on a canvas
is the flat blank-map default; a preset without an intensity forced it
back to 1.0; and a skybox fade in flight restored the sun colours it had
captured over a config applied mid-fade.
- **Editor sessions no longer spawn commanders.** New Map and Open
Project start scripts carry an `editor_sandbox` flag; the initial-spawn
gadget skips the commander for those sessions and the game-end gadgets
stand down on the same flag. Editor canvases also leave pregame on their
own a few frames after boot, so terrain raised above the canvas base is
clickable without a manual start.
- **DISPLAY chip rows wrap** onto a second line instead of squeezing
every label onto two lines once a tool has five chips.

## Also

- 1.14 changelog section in `doc/TerraformBrush-changelog.md`; header
badge bumped to 1.14.
- The gadget gains a `$terraform_stroke$` packet carrying a whole brush
tick. The per-dab packet is unchanged and still used by sticky-measure
replay.
- Three gadgets outside the Terraform Brush are touched, all for the
commander fix and nothing else: `game_initial_spawn.lua` skips the
commander when the start script carries `editor_sandbox`, and
`game_end.lua` / `game_team_com_ends.lua` stand down on the same flag so
a commanderless editor session is not scored as a loss. Each is a single
guarded block.
- One whitespace-only commit reindents a block in
`game_initial_spawn.lua`. It was already unformatted on master, and the
format check only looks at the files a pull request touches, so it
stayed green until this branch edited that file.

## Testing

Tested locally on generated canvases and real maps with the tileset
shader running: fast and slow sculpt strokes with clay at the intensity
floor, square and circle with FOLLOW on and off, the protractor and
symmetry with FOLLOW, the PASSABILITY band against F6 for a bot and a
vehicle, SURFACE scatter and the retuned DOT preset over a sculpt,
INFLUENCE bands in both SURFACE and LAYERS including the sneak peek at
several zoom levels, sun presets across project loads and skybox fades,
the Open Project browser against a cloned maps repository with spaces
and subfolders, and editor sessions starting without commanders.

## AI/LLM usage statement

Written and implemented with Claude Code under my direction; I reviewed
the diff.

---------

Co-authored-by: Floris <ruwetuin@hotmail.com>
@pull pull Bot locked and limited conversation to collaborators Sep 9, 2026
@pull pull Bot added the ⤵️ pull label Sep 9, 2026
@pull
pull Bot merged commit da09265 into ExaDev:master Sep 9, 2026
3 of 5 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants