s&box/field-guide
 field-guide / writing-gameplay
04/10
guides
Delta-log save for deterministic procedural worlds
When the world is deterministically generated from a spec, a save is {version, spec, edits[]} — never a geometry snapshot. Load = validate → regenerate → replay edits. Same format works for save, co-op edit sync, and late-join replay.
Networking methods — spec replication + host-authority patterns
Two proven architectures on top of the engine networking primitives: replicate the generator spec (not the geometry) for deterministic worlds, and retrofit local-only gameplay systems to host-authority without breaking single-player.
Parkour movement — the trace-mover traversal kit
The method for feel-first traversal on a kinematic trace-based character: coyote-time jumps, double jump, mantle, slide, wall run, node climbing, and rope swinging — plus the ground-contact quality work that makes all of it read smooth.
Part-kit assets — manifest-driven multi-part assembly
The higher-order asset layer for kits of parts (vehicles, procedural buildings): the generator emits a manifest next to the meshes, code consumes ONLY the manifest, pivots sit at joints, collision comes from metadata, and assembly is transactional.
Runtime terrain meshing — chunked greedy voxel/heightfield terrain
The method for large runtime-generated terrain in s&box: a persistent cell grid as the single source of truth, chunked greedy meshing (tops + skirts), collision decoupled from render grain, palette-atlas UVs, and dirty-chunk remesh for a live terrain brush.
Vehicle physics — the slip-curve raycast-wheel stack
The proven architecture for driving games in s&box: raycast wheels on a single chassis Rigidbody, substepped slip-ratio/slip-angle tire physics with peaked curves, a torque-curve drivetrain, layered assists, and arcade dials on top of a sim core.
26.07
"Double jump doesn't work with Jump() — set Velocity.z directly"

Jump() helpers clamp against rising velocity, eating the second impulse. Set Velocity.z directly for a reliable double jump.

26.07
"Edit-mode Destroy() is deferred — scene queries return stale objects"

GameObject.Destroy() in edit mode is deferred; a query fired right after returns the previous build's objects.

26.07
"Editor auto-exposure makes screenshot comparisons report false positives"

Two screenshots of byte-identical geometry differ by tens of percent because editor auto-exposure adapts over wall-clock frames — settle the viewport and exposure-normalize before comparing.

26.07
"GameObject.Destroy keeps rendering in edit mode — deferred queue not processed between regenerations"

A runtime-generated world root torn down with GameObject.Destroy() keeps rendering in edit mode because the deferred queue isn't flushed — use DestroyImmediate and sweep all matching roots.

26.07
"Greedy voxel mesher produces vertical stripes on cliff faces instead of horizontal strata"

Per-cell dither or contour-wander in a greedy voxel mesher turns cliff skirts into vertical stripes — detect walls and key strata on raw height bands with dither neutralised.

26.07
"Ground snap pops on rolling slopes — rate-limit the downward snap"

An idempotent ground-snap jitters on curved terrain because the slope curves away within each step — ease the downward snap at a bounded glue rate.

26.07
"Heavy work without frame hitches"

GameTask.RunInThreadAsync for parse/math; main thread only for engine objects; Yield every N items for loading UI.

26.07
"Kinematic movement that doesn't get stuck"

Always handle tr.StartedSolid — ignore that frame so an overlapped body can walk free; slide-trace the wish onto the hit plane.

26.07
"ModelRenderer.Tint on flat-color vmats causes purple/black corruption"

Per-instance Tint on a flat-color vmat (white PNG + g_vColorTint) rotates hue or crushes random instances to black — use scale/yaw jitter instead.

26.07
"My interface scan returns nothing at runtime"

OfType<IInteractable> on GetAllComponents<Component> returns nothing — enumerate concrete types and union them.

26.07
"New .razor.scss files are not applied until the editor restarts"

A newly created .razor.scss file is not picked up by a running editor session — the panel component works but is unstyled until the next restart.

26.07
"No public per-action analog trigger axis — triggers are digital-only"

GamepadCode-bound trigger actions are read as digital threshold presses only — there's no smooth 0..1 analog read per action. Use stick axes for continuous control.

26.07
"Noclip in a trace-based kinematic controller is a state, not a collider toggle"

Disabling a collider does nothing for a hand-integrated kinematic controller — noclip must be a movement state that skips traces, gravity, and ground-snap entirely.

26.07
"Orient a flat decal box to the hit normal"

Rotation.FromYaw alone leaves a decal flat on the floor — build a rotation that aligns the thin axis to the surface normal, then offset along it.

26.07
"Owner-simulated networking"

[Sync] owner→proxies, IsProxy early-out; FromHost for shared truth — don't NetworkSpawn scene-wide singletons.

26.07
"Per-cell white noise hash gives salt-and-pepper terrain instead of organic shade patches"

A per-cell white-noise hash for terrain shade choice reads as a 50/50 checkerboard — use a smooth low-frequency noise field instead, confining the hash to threshold-edge dithering.

26.07
"Rigidbody component API"

Rigidbody API verified across multiple projects — Gravity, MassOverride, Velocity, ApplyForce, and the recipe for a dynamic pushable prop.

26.07
"Rotation.FromYaw is counter-clockwise"

Rotation.FromYaw(+angle) is a LEFT (CCW) turn — get the sign right or steering, AI, and autopilot spiral the wrong way.

26.07
"Runtime world-building helpers"

FlatBox/Prop/Deco/Wire helpers plus Obstacle records as plain data beat physics queries for build validity.

26.07
"s&box component lifecycle in practice"

OnAwake runs synchronously inside Components.Create — set singletons there; derive from [Property] in OnStart after spawn helpers assign.

26.07
"Save/load without drift"

DTOs + one spawn path + deterministic static world — guard restored defaults; keep enums append-only.

26.07
"Scene.GetAllComponents skips disabled components"

A component with Enabled = false is invisible to GetAllComponents — search returns null even though the component exists.

26.07
"TextEntry.OnTextEdited needs an explicit-type block lambda"

An untyped or expression-bodied lambda on TextEntry.OnTextEdited fails with CS8917 or CS0029 — use an explicit param type plus a block body.

26.07
"The singleton pattern that removes reference-wiring"

static Instance set in OnAwake, cleared in OnDestroy — everything reads Foo.Instance with null-guards, no inspector wiring.

26.07
"Trace-based kinematic controllers don't fire triggers"

A hand-integrated trace mover has no collider component — ITriggerListener and OnTriggerEnter never fire. Use distance-polling instead.

26.07
"Whitespace next to a Razor tag or expression boundary collapses to nothing"

Literal spaces adjacent to a tag or @-expression boundary vanish in Razor markup — use a single interpolated string or CSS margin instead.