Summary
A track piece the ride style has no artwork for can be placed, tests fine, and
receives ratings, but renders as nothing at all: no track, no supports. Runs
containing such pieces produced park screenshots with large stretches of the
ride simply absent, which initially looked like a painter or sprite-sorting
bug.
The classic case is the wooden roller coaster (ride type 52) and the one-tile
flat↔60° transitions (flatToUp60, up60ToFlat, flatToDown60,
down60ToFlat, track group flatToSteepSlope). RCT2 only ever drew the
three-tile *LongBase variants for wooden, so
GetTrackPaintFunctionWoodenAndClassicWoodenRC has no case for the short ones
and falls through to default: return TrackPaintFunctionDummy.
Example: https://wseaton.github.io/CoasterBench/run-20260723-v2-design-claude-opus-4-8.html
Why placement allows it
flatToSteepSlope is in neither the wooden RTD's enabledTrackGroups nor its
extraTrackGroups, so the in-game construction window will never offer these
pieces. But the enabled-group gate (IsTrackEnabled /
UpdateEnabledRideGroups, RideData.cpp) only feeds that window.
TrackPlaceAction never consults it, so any programmatic placement is
accepted. RideSetSettingAction (rideType) is the same story from the other
direction: it sets ride->type and calls updateRideTypeForAllPieces() with
no check that the existing pieces are drawable by the new style.
Ratings still compute, because the physics reads the track element descriptor,
which exists for these pieces regardless of artwork.
How it was diagnosed
- Instrumented the two height culls in
PaintTileElementBase: 168 track
tiles visited, 168 painted, none culled.
- Logged every
ride paint struct at creation and at draw: 1166 made, 1166
drawn — nothing dropped during quadrant arrangement or sorting.
- Ablated
imageWithinRT creation-time culling entirely: 62 pixels changed,
so culling was not involved.
- Logged calls to
paintFunction (TrackPaint.cpp:2035) that left
session.LastPS untouched: 32 tiles. 16 were legitimate empty filler
sequences (banked five-tile turn sequences 1 and 4, large helix sequences
1, 4, 8, 11 have no sprites by design). The other 16 were exactly the
program's 16 steep-transition pieces.
- Minimal repro: the piece list
flat, flat_to_up_60, up_60, up_60_to_flat, flat, flat, flat_to_down_60, down_60, down_60_to_flat, flat renders whole
on ride type 51 and as disconnected floating fragments on 52.
Harness fix
CheckPieceDrawable in src/openrct2/rustbridge/RustBridge.cpp now asks the
same dispatch the renderer uses — GetTrackPaintFunction(style, type) compared
against TrackPaintFunctionDummy — before placing or querying a piece. The
ride's track groups are deliberately not used as the test, because they also
gate pieces that draw perfectly well. The check runs in both
orct2_host_track_place and orct2_host_track_query, so place_piece,
place_pieces, valid_next_pieces and the program executor all reject
undrawable pieces with an actionable message instead of building invisible
track.
Upstream
Worth reporting to OpenRCT2: the enabled-group check exists only in the UI
layer, and both TrackPlaceAction and the ride-type change bypass it. Any
plugin, imported track design, or park built through those paths can contain
track that renders as nothing for everyone who opens it.
Summary
A track piece the ride style has no artwork for can be placed, tests fine, and
receives ratings, but renders as nothing at all: no track, no supports. Runs
containing such pieces produced park screenshots with large stretches of the
ride simply absent, which initially looked like a painter or sprite-sorting
bug.
The classic case is the wooden roller coaster (ride type 52) and the one-tile
flat↔60° transitions (
flatToUp60,up60ToFlat,flatToDown60,down60ToFlat, track groupflatToSteepSlope). RCT2 only ever drew thethree-tile
*LongBasevariants for wooden, soGetTrackPaintFunctionWoodenAndClassicWoodenRChas no case for the short onesand falls through to
default: return TrackPaintFunctionDummy.Example: https://wseaton.github.io/CoasterBench/run-20260723-v2-design-claude-opus-4-8.html
Why placement allows it
flatToSteepSlopeis in neither the wooden RTD'senabledTrackGroupsnor itsextraTrackGroups, so the in-game construction window will never offer thesepieces. But the enabled-group gate (
IsTrackEnabled/UpdateEnabledRideGroups,RideData.cpp) only feeds that window.TrackPlaceActionnever consults it, so any programmatic placement isaccepted.
RideSetSettingAction(rideType) is the same story from the otherdirection: it sets
ride->typeand callsupdateRideTypeForAllPieces()withno check that the existing pieces are drawable by the new style.
Ratings still compute, because the physics reads the track element descriptor,
which exists for these pieces regardless of artwork.
How it was diagnosed
PaintTileElementBase: 168 tracktiles visited, 168 painted, none culled.
ridepaint struct at creation and at draw: 1166 made, 1166drawn — nothing dropped during quadrant arrangement or sorting.
imageWithinRTcreation-time culling entirely: 62 pixels changed,so culling was not involved.
paintFunction(TrackPaint.cpp:2035) that leftsession.LastPSuntouched: 32 tiles. 16 were legitimate empty fillersequences (banked five-tile turn sequences 1 and 4, large helix sequences
1, 4, 8, 11 have no sprites by design). The other 16 were exactly the
program's 16 steep-transition pieces.
flat, flat_to_up_60, up_60, up_60_to_flat, flat, flat, flat_to_down_60, down_60, down_60_to_flat, flatrenders wholeon ride type 51 and as disconnected floating fragments on 52.
Harness fix
CheckPieceDrawableinsrc/openrct2/rustbridge/RustBridge.cppnow asks thesame dispatch the renderer uses —
GetTrackPaintFunction(style, type)comparedagainst
TrackPaintFunctionDummy— before placing or querying a piece. Theride's track groups are deliberately not used as the test, because they also
gate pieces that draw perfectly well. The check runs in both
orct2_host_track_placeandorct2_host_track_query, soplace_piece,place_pieces,valid_next_piecesand the program executor all rejectundrawable pieces with an actionable message instead of building invisible
track.
Upstream
Worth reporting to OpenRCT2: the enabled-group check exists only in the UI
layer, and both
TrackPlaceActionand the ride-type change bypass it. Anyplugin, imported track design, or park built through those paths can contain
track that renders as nothing for everyone who opens it.