scenario

package
v0.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: LGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package scenario holds the individual physics2d test scenarios. Each exported constructor returns one harness.Scenario; register.go lists them in run order.

Every scenario is written in lane-local coordinates — the harness offsets each scenario into its own slice of the world so bodies from different scenarios can never collide with, or be found by queries belonging to, another scenario.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func All

func All() []harness.Scenario

All returns every scenario in run order. Each gets its own lane in the world, so ordering only affects layout and report ordering, never behaviour.

func BodyTypes

func BodyTypes() harness.Scenario

BodyTypes covers the four body kinds and, just as importantly, the writeback rules that go with them. Static and manual bodies must never have their Transform2D or Velocity2D overwritten by the physics step; dynamic and kinematic bodies must.

Manual is the kind most likely to break in a port: it is a Box2D kinematic body underneath, but ECS owns its pose, Box2D is told its velocity is zero every tick, and writeback is skipped entirely.

func Compound

func Compound() harness.Scenario

Compound covers multi-fixture bodies: each child's LocalOffset and LocalRotation must place real geometry in the world, the slot index must stay attached to the child that produced a contact, and the children's densities must combine into one centre of mass.

Slot identity is the part worth guarding. The plugin documents shape identity as "index i in Shapes is fixture slot i", and every contact event a game receives is only useful if that mapping holds.

func Contacts

func Contacts() harness.Scenario

Contacts checks what a ContactBegin/ContactEnd event actually carries: the right pair of entities, the right shape slots, a usable normal and point, and the fixture filters that produced the contact. It also pins the event lifecycle — one Begin per touch, an End when the bodies part, and no per-tick re-firing while two bodies rest against each other.

The contact point is checked against world space on purpose. The manifold stores it as an anchor relative to body A's centre of mass, and turning that into the world point a game actually wants is a one-line addition the plugin has to make. Every scenario runs in its own lane hundreds of metres from the origin, which makes the two frames impossible to confuse.

func Defaults

func Defaults() harness.Scenario

Defaults covers the single most likely class of porting bug: a Go zero value silently standing in for a C default. Box2D's b2DefaultBodyDef enables the body, wakes it, allows sleeping and sets gravityScale to 1, but Go bool zero values are false and float zero values are 0. The plugin bridges that gap in two places — NewPhysicsBody2D and PhysicsBody2D.UnmarshalJSON — and this scenario pins both, then proves the flags actually reach Box2D by watching a constructor-built body fall while a struct-literal body does not.

func Filtering

func Filtering() harness.Scenario

Filtering covers collision filtering end to end: category/mask pairs, the asymmetric case where only one side agrees, group index overrides, and the full 64-bit width of the filter fields.

The width test matters for a port specifically: Box2D v2 used 16-bit filters and Box2D v3.1 widened them to uint64_t. A bridge that still marshals them through a uint16 or uint32 would behave perfectly for every category below bit 32 and silently stop colliding above it.

Every pair is a head-on approach on its own row with gravity switched off, so "did they collide" reduces to "did they stop or did they pass through".

func Flags

func Flags() harness.Scenario

Flags checks every boolean and scalar on PhysicsBody2D that has to survive the trip into Box2D: Active, Awake, SleepingAllowed, Bullet, FixedRotation, GravityScale, LinearDamping and AngularDamping.

Each one is checked by its observable consequence rather than by reading the component back, because the component is exactly where a flag that never reached C still looks correct.

func Hostile

func Hostile(name string) (harness.Scenario, bool)

Robustness holds the inputs a game can hand the plugin that are finite — and so pass ColliderShape.Validate — but malformed by the engine's rules: a chain with three points, a circle with no radius, a polygon with too few vertices, a box with no extent. Destroying an entity that still holds a live contact is here too, from a completely different direction.

A polygon with too MANY vertices used to be one of these. It no longer belongs here: Vertices holds only MaxPolygonVertices slots, so a polygon claiming more fails ColliderShape.Validate and never reaches Box2D — which puts it outside the family this file is about, inputs that PASS Validate and then misbehave. It is still rejected rather than fatal; see TestWithVertices_ReportsPastBoundInsteadOfPanicking.

Validate only checks that numbers are finite, so all of these reach the engine. Against the cgo bridge four of them tripped a fatal Box2D assertion and killed the shard; the pure-Go engine does not die, which is exactly what these cases exist to keep measuring.

Each still runs alone via -hostile <name>, because a case that does regress to killing the process would otherwise hide every case after it. Every case prints what it is about to do first, so the log says which one died even when the process never reaches the summary.

func HostileNames

func HostileNames() []string

HostileNames lists the available -hostile cases, in a stable order.

func Lifecycle

func Lifecycle() harness.Scenario

Lifecycle covers everything that can change after a body exists: entities created and destroyed mid-run, poses and velocities written from gameplay, geometry edited in place, shapes added and removed, filters and sensor flags flipped, materials retuned, and body types swapped.

The reconciler decides which of these can be applied to the live Box2D body and which need the fixture rebuilt, by diffing the component against a shadow copy. Anything the diff does not look at is a field a game can change with no effect and no error, so each mutation here is paired with an observation that would notice.

func Material

func Material() harness.Scenario

Material covers the three per-fixture material values — restitution, friction and density — and, more importantly, the rules Box2D uses to combine two fixtures' values on contact. Box2D v3 mixes friction as sqrt(fA*fB) and restitution as max(rA, rB); a port that reached for the other obvious choice (max for friction, multiply for restitution) would still look plausible in isolation and only show up in a test like this one.

func Queries

func Queries() harness.Scenario

Queries covers the three query entry points the plugin exposes — Raycast, OverlapAABB and CircleSweep — including the documented edge cases (zero-length rays, zero-area boxes, zero-radius sweeps, reversed AABB corners) and the sensor rule that all three share.

The scene is static and never moves, so every expected value below is exact geometry rather than a simulated outcome.

func Reset

func Reset() harness.Scenario

Reset covers Plugin.Reset, the call a game makes after restoring a snapshot or rebuilding its world. It throws away every derived Box2D object and lets the next PreUpdate rebuild the whole simulation from ECS, so it is the one operation that exercises the ECS -> Box2D path from a cold start with a world that is already full of moving bodies and live contacts.

The risks it has to avoid are all invisible ones: bodies that come back in the wrong place, contacts that replay as new Begin events, contacts that quietly disappear without an End, and queries that keep returning stale results.

Reset only touches this plugin instance's runtime, but the scenario still runs after every other one has finished asserting: the whole world shares a plugin, so the rebuild is visible to every lane.

func RestoreScene

func RestoreScene() harness.Scenario

RestoreScene is the world used by the crash-restore check. It is a scene, not a set of assertions: the check builds it in two worlds, snapshots one, restores it into the other, and compares.

Every body here carries at least one value that differs from the default it would fall back to. That is the whole design. A field that is dropped by MarshalWire, or defaulted by UnmarshalJSON when it should not be, or lost in the ECS -> Box2D rebuild, shows up as that field reverting — which the comparison names exactly.

So: no body has Active, Awake and SleepingAllowed all true; no body is left at GravityScale 1 alone; filters use high bits and non-zero group indices; materials avoid the defaults; and every shape type is present, because shape geometry travels as JSON too.

func SampleShape

func SampleShape(kind physics.ShapeType) physics.ColliderShape

SampleShape returns a small, valid collider of the given kind, for tests that enumerate shape types rather than describe geometry. Chains use four points, the engine's minimum.

func Sensors

func Sensors() harness.Scenario

Sensors covers the trigger half of the event system: a sensor fixture must report overlaps without blocking anything, must route to Trigger events rather than Contact events, and must keep working in the cases Box2D handles specially — sleeping bodies, static visitors, compound bodies and disabled bodies.

Overlaps that already exist when the world is first built are deliberately suppressed by the plugin (otherwise every rebuild would replay the whole world as new contacts), so every "does this overlap register" case here creates its second body partway through the run rather than at setup.

func Shapes

func Shapes() harness.Scenario

Shapes exercises all seven ColliderShape kinds end to end: each one must reach the C side (a query finds it), and each must actually collide the way its geometry says it should.

The four mass-bearing kinds (circle, box, convex polygon, capsule) are dropped onto a static floor and must come to rest at the height their geometry implies. The three static-only kinds (open chain, chain loop, edge) are built as world geometry and must catch a ball dropped onto them.

Chains in Box2D v3 are one-sided: the surface normal faces to the right of the segment direction. Points are therefore ordered right-to-left where an upward-facing normal is wanted, and the loop uses the winding that puts the normals on the inside.

func Stability

func Stability() harness.Scenario

Stability is the numerical-sanity scenario: the cases where a port that is otherwise correct starts producing garbage. A tall stack has to settle instead of jittering apart, deeply overlapped bodies have to push out at a bounded speed instead of exploding, very small and very large shapes have to behave, and float32 has to hold up thousands of metres from the origin.

The harness also watches every body in every scenario for NaN and Inf on every tick, so anything that does blow up is reported against the body that did it.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL