Skip to content

V0.5.0 - #8

Merged
ragoune merged 16 commits into
masterfrom
v0.5.0
Oct 4, 2026
Merged

ragoune merged 16 commits into
masterfrom
v0.5.0

Conversation

@ragoune

@ragoune ragoune commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Feather v0.5.0

New features

  • Kinematic bodies: BodyTypeKinematic, driven by a target per step (RigidBody.SetKinematicTarget), interpolated over the sub-steps and placed exactly on its target. A kinematic body pushes dynamic bodies, is never pushed back, gets speculative contacts from its speed, and stays awake while it moves. Also World.SetBodyType (kinematic ↔ dynamic, contacts, joints and island kept) and World.Teleport.
  • Continuous collision between dynamic bodies: any fast body (Box2D's rule) now gets a speculative margin from its relative speed and is swept against static, kinematic and dynamic bodies. No flag is needed: two 10 cm spheres at 20 m/s each bounce instead of tunnelling.
  • Convex hulls: actor.NewConvexHull (quickhull, coplanar faces merged into polygons, up to 256 vertices), with mass, center of mass and inertia.
  • Triangle meshes: actor.NewTriangleMesh, a static one-sided surface with a SAH AABB tree, welded vertices and active-edge classification. Rays, sweeps, overlaps and CCD all work against it (≈1.2 µs per ray on 100k triangles).
  • Terrain contacts rewritten: per-triangle contacts on heightfields and meshes, with Jolt's active-edge normals. Resting depth on hilly terrain drops from 3.3 mm to 0.28 mm.
  • Virtual character controller: CharacterVirtual (after Jolt's), a capsule moved by the game. It slides along walls, climbs slopes and stairs, sticks to the floor, rides moving platforms, pushes dynamic bodies, and is seen by rays, bodies and triggers.
  • Triggers and sleep: a body asleep in a trigger stays in it, kinematic bodies enter and leave triggers, and removing a body ends its pairs with an exit event.
  • Shape and density changed in place: World.SetShape and World.SetDensity keep the body's index, joints, layers and island, and update its mass and inertia.
  • Determinism, proven and documented: identical bits across runs and with 1, 4 and 8 workers, including after bodies are removed and added.

Breaking changes

  • RigidBody.IsBullet is removed: every fast body is now handled automatically.
  • Simulations are no longer bit-identical to v0.4.0: 13 bench fingerprints change, because of dynamic CCD and the new terrain contacts.

Performance

  • No measurable cost on the 2,000-body pile.
  • Scenes with many fast bodies cost more, about +35% per step in the worst bench case.
  • Still zero allocations per step, character updates included.

Notes

  • Known limit: terrain contacts are computed once per step.

ragoune added 16 commits October 4, 2026 18:57
… each of the 1000 steps of 200 bodies with joints, and of a pile of 600 bodies, the same between two runs and with 1, 4 and 8 workers, each scene checked to reach the parallel path of every stage), bodies removed and added again keep a defined order (the bodies left keep theirs, a body added is last, the trees, the pairs & the contacts follow), the cost of the sort of the pairs (BenchmarkSortPairs); green from the start: the engine is already deterministic
…ough AddBody & RemoveBody, what the sort of the pairs gives and its measured cost, 4.4 ns per pair, 0.1 to 1 % of a step, the rule of Box2D and the setting of Jolt, the tests which prove it, the same GOARCH)
…e folded by 0 to 10° rest on it within LinearSlop and stay there, their points on the ridge in a manifold and their vertices in another, the deepest point of a body is a contact on an inactive edge too, the normal of a contact on an edge by the rule of ActiveEdges::FixNormal of Jolt, a triangle seen from below is ignored and a body whose center is under the surface falls, on a flat terrain a body has its contact with a plane, a capsule & a box of the bench which sank in the hills keep their contact at each step); bench: the depth of a box in a terrain counts the terrain in its faces, along the edges of the triangles; the normal of a contact on an inactive edge is the one of the slope whatever the motion of the body (the part of ActiveEdges::FixNormal on the motion is not followed), the bodies on the flat terrain move with the margin of the world and keep their one contact straight up, a sphere in a valley folded by 3° touches both slopes (the closest point inside a triangle is a point of its face, kept), the sphere 28 of the pile 51 and the box 36 of the pile 22 on the flat grid land within a tenth of their old depth; red: under 5° the ridge is 6.6 to 10.8 mm in the body, the deepest point of a capsule is dropped (14 to 56 mm deep), a body under the surface is pushed up, a moving body on a flat terrain gets contacts tilted by the motion rule (253 bodies out of 300), the shallower slope of the valley has no contact, the sphere lands 3.4 mm deep and the box 0.9 mm
…e body over its face, as against a plane; the points where its face crosses a ridge too flat to be an active edge; the closest point of GJK/EPA with the normal of ActiveEdges::FixNormal of Jolt, kept when it is the deepest along its normal; on an active edge the points of the body across it; a triangle seen from below is ignored), the vertices of the body and the edges of the terrain in separate manifolds, the convex bits of the edges read; a closest point inside a triangle is a point of its face, kept whatever its depth (dropped in a patch which had a deeper point, a sphere rolling to a triangle folded by less than 5° had no contact with it and sank by 3 mm); the part of ActiveEdges::FixNormal on the motion of the body is not followed (the speculative margin follows the speed, and brought the edges of the triangles around under this rule: a box falling flat on a vertex of a flat terrain had 4 contacts tilted by 10 to 60°, and landed 2 mm deep); the docs, with the limit of the contacts computed once per step (as Jolt, Box3D and PhysX); the reference of the bench, machine at rest: the fingerprints of terrain piles and rain on terrain
…ts speed and its friction, nothing moves a kinematic plate, its velocity is the one of its motion to its target and it reaches the target bit for bit, Teleport pushes nothing, no contact with a static or a kinematic body, a leg at 5 m/s doesn't go through a resting capsule or box, SetBodyType keeps the contacts, the joints and the island, a kinematic sleeps with its island and a slow target keeps it awake, the joints pull, the forces are ignored, 1 and 8 workers give the same bits, a step allocates nothing, a target on a body which is not kinematic and SetBodyType from or to static or on a body without mass are refused with an error and allocate nothing); red: the type doesn't exist
… velocity of the motion deduced at the start of the step, the body interpolated to its target over the sub-steps, its position linearly and its rotation along the shortest arc, then put on it bit for bit; no velocity without target, as the kinematic actors of PhysX), without mass for the solver (its rows read its velocity and its motion and never write it, so it is colored as a static body where Box2D colors it as a dynamic one for its SIMD scatter), speculative contacts from its speed as against a static body (a leg at 5 m/s enters a resting capsule by 0.25 mm instead of going through), no contact with the static and the kinematic bodies (its proxy stays in the dynamic tree and never queries the static one), in the island of the bodies it touches (awake while a target moves it, however slowly, as PhysX; Box2D and Jolt let a kinematic body under their sleep threshold fall asleep), World.SetBodyType between kinematic and dynamic in place (index, proxy, pairs and contacts, joints, filters and island kept, as Body::SetMotionType of Jolt; a static body refused), World.Teleport without velocity; a misuse refused with an error, never in silence and without allocation (actor.ErrNotKinematic for a target on a body which is not kinematic, as the assert of PhysX; ErrStaticBody and ErrMasslessBody for SetBodyType); the docs
…0 cm thrown at 20 m/s each touch, bounce and never go through each other, a plate of 1 cm at 30 m/s pushes the resting plate it meets and never crosses it, a light plate kicked at 40 m/s by a heavy ball during the step is stopped on the plate 30 cm behind it, a fast body is stopped by a static, a kinematic, a dynamic, a sleeping and a fully locked body without any flag, 2 fast bodies which meet are stopped together at the same fraction of their motions, a dynamic body the fast body has a contact with is left to the solver and the sweep stops on the next body, the pairs of a fast body get the speculative margin of its speed where 2 slow bodies at the same distance have none, the fast body of Box2D by its translation or its rotation and never a kinematic or a sleeping body, 1 and 8 workers give the same bits and a step allocates nothing with fast bodies at every step); the tests of the filter and of the locks drive the continuous collision through the states of the solver instead of a helper; the sleep coverage of the mixed scene read over the run, not at its last step; red: 2 dynamic bodies rely on their 2 cm of margin, only a bullet is stopped by a dynamic body
…g: the pairs of a fast body (Box2D: it moves more than half of its smallest extent during the step, judged from its velocities at the start of the step) get the speculative margin of their relative speed, as the pairs with a static or a kinematic body (the speculative CCD of PhysX, Sc::BodySim::updateContactDistance, without its flag), so 2 spheres of 10 cm at 20 m/s each touch and bounce in the step they meet (they went through each other from 7.5 m/s of relative speed, 2 plates of 1 cm from 2.4 m/s); after the solver, a fast body is moved back to its first impact with every body it had no contact with (the LinearCast of Jolt: the static, kinematic and dynamic bodies where the step left them; 2 fast bodies swept once by their relative motion against the start pose of the other and stopped together, the fractions of all the fast bodies found before any body moves, so the order of the bodies doesn't matter), which catches a body accelerated by the solver during the step (a light plate kicked at 40 m/s stopped 0.8 mm from the plate behind it); a dynamic or kinematic body the fast body has a contact with is left to the solver (swept too, the sweep lifted the body off the overlap the soft contact allows every step: a swinging chain of 20 capsules stretched by 214 mm instead of 9, the cubes under the slab of high mass ratio 2 drifted by 34 mm instead of 18); IsBullet removed, every fast body is the bullet of Box2D; the docs with the measures (no measurable cost on the pile of 2 000 bodies, the step of the mixed scene with 42 fast bodies out of 200 from 1.8 to 2.4 ms, the reference of the bench regenerated machine at rest: the fingerprints of 13 scenes change (the worst stretch of the joint chain from 23.7 to 9.1 mm, the small cubes of high mass ratio 2 and 3 drift by 17.8 mm instead of 16.5 and sink 12 mm less, the least push of the locked bodies from -0.054 to -0.051 m))
…f a cube with points inside, on its faces and on its edges is the cube: 8 vertices, 6 faces of 4, the mass and the inertia of the Box within 1e-6, the center of mass at its center; random clouds far from the origin and on a sphere contained within the rounding, convex, closed, their vertices points of the cloud, the support point the furthest point; the vertex limit; the degenerate clouds refused; the ray of the hull of a cube is the ray of the Box; the supporting face of 8 vertices at most; a hull rests on the ground and on a terrain as the Box, is swept, overlapped and hit by a ray) and the triangle mesh (a soup of triangles welded, its edges classified as the heightfield: borders and folds active, flat edges inactive, an edge of 3 triangles and a sheet active; the degenerate triangles out of the tree; the tree gives the triangles of a box as the brute force, each in one leaf of at most 8, its height bounded; a ray gives the triangle of the brute force, the lowest at the same fraction, from the side of the normal only; the cast of a box gives every triangle its segment enters; a sphere rolls on a mesh plane as on a plane, a box slides over the inner edges, a ray on 100 000 triangles under 5 µs, cubes and a plank rest on a staircase for 10 s, a mesh is one-sided for the bodies, the rays and the sweeps and the continuous collision stops a ball on it, the sweep stops on the right tread, a pile of every shape on a mesh gives the same bits with 1 and 8 workers, a step and the queries allocate nothing); red: the types don't exist
…l tetrahedron, the thresholds and the tolerance of Box3D and Gregorius in double precision, the outside sets and the horizon of Jolt and Box3D), the coplanar triangles merged in polygons once at the end (the face of a cube is 1 polygon of 4 vertices: a cube resting has its 4 corners), the mass, the center of mass and the inertia from the tetrahedra of its faces (the covariance of Jolt after Blow & Binstock), the hull centered on its center of mass (CenterOfMass gives the offset), 256 vertices at most, the support point found among all of them (the hull of a cube costs as a box, 256 vertices twice as much), the vertex limit stopping the hull as Jolt and Box3D, the supporting face of 8 vertices at most for the contact clipping, the ray clipped by the planes of the faces; actor.TriangleMesh, a static surface of triangles with a tree of AABBs built top-down by the binned surface area heuristic of Box3D (8 bins, leaves of 4 triangles, the median when a split leaves less than a third on a side, the rule of Bullet: the height bounded for a fixed stack), the vertices welded within 0.1 mm (Jolt), the degenerate triangles kept out of the tree at their index, the edges classified as the heightfield by the triangles sharing them (a border, an edge of 3 triangles and a sheet active, as Jolt), OverlapTriangles and Cast (an iterator with its stack, as CellWalk), the ray by Möller-Trumbore front to back (1.2 µs per ray on 100 000 triangles); one generator of contacts for the heightfield and the mesh (collideTriangles: the surfaces differ by the triangles they give under the body, the cells of the grid or the leaves of the tree, as b3ComputeMeshManifolds of Box3D, CollideConvexVsTriangles of Jolt and PCMConvexVsMeshContactGeneration of PhysX; a switch, no interface: the arithmetic of the heightfield is unchanged, the 33 fingerprints of the bench identical), the sweep, the overlap and the continuous collision of the fast bodies share their test of a triangle the same way (a ball at 200 m/s stops on a mesh wall); a mesh is one-sided as the heightfield; the docs with the measures
…tatic zone stays in it for 10 s, one enter and no exit nor stay; pushed out it leaves once, when its face is past the zone; removed from the world while asleep in the zone, or the zone removed, it leaves once at the next step; a static zone moved away from a sleeping crate ends the pair, and moved back starts it again; a sleeping crate moved by hand out of the zone by 7 cm, within its enlarged AABB, leaves it; a static box made a trigger under a sleeping crate is entered and its contact with the crate ends; a body removed by a listener during the events gets its exit in the same flush; a kinematic cube driven through a static zone enters it once and leaves it once, stopped in it falls asleep and stays in it, removed leaves it; a kinematic trigger enters a static and a kinematic cube once and leaves them once, without contact; a crate asleep on a plane or on a static box keeps its contact for 10 s without stay, and waking up sends no enter; a crate removed from the ground, asleep or awake, or the ground removed, ends its contact once at the next step, in the same flush if a listener removed it; the pairs of a zone of 400 crates with kinematic cubes and a kinematic trigger are those of the brute force, in the same order; the events are the same with 1 and 8 workers; a crate falling through a trigger mesh and a kinematic cube driven through it enter it once and leave it once; a step with crates asleep in a zone, or kinematic cubes moving in it, allocates nothing); red: the test does not build (makeEventPair, detectsTrigger), and with both: the crate leaves the zone when it falls asleep and enters it again when it wakes up, a removal ends no pair, a zone moved away from a sleeping crate or a crate moved out keeps the pair, a box made a trigger under a sleeping crate is never entered, a crate asleep on the ground leaves it, a removed body ends no contact, a kinematic body never enters a trigger, a trigger mesh is never entered (its overlap tested by GJK on its null support point)
…er and a body which moves, dynamic or kinematic, is emitted by the broad phase whatever the sleep, as the sensors of Box2D v3 which "do not consider sleep"; it has no contact, only an overlap, the distance of the cores by GJK as World.Overlap and b2SensorQueryCallback, a plane, a heightfield or a triangle mesh keeping its real contacts as it has no core; while both bodies rest it keeps the overlap of its last test, as long as the AABB of each body is the same bit for bit, as the trigger pairs of PhysX not tested while both actors sleep and tested again when a pose is set; no stay while both rest); a kinematic body enters and leaves the triggers, static or not, and a kinematic trigger detects the static and the kinematic bodies, without contact (a moved kinematic proxy queries the static tree and the planes too, its pairs without a trigger never emitted; the sensors of Box2D v3 query its 3 trees, Unity: "A static trigger collider collides with any dynamic or Kinematic collider"); a contact whose bodies both rest, a body asleep on the ground included, is kept without stay until they move apart (the touching contacts of the sleeping set of Box2D v3, no collision stay for a sleeping rigidbody in Unity), a pair of the events keeps its kind so a body made a trigger ends its contacts; RemoveBody ends the pairs of the body, trigger or contact, with an exit at the next events (b2SensorEndTouchEvent, the b2ContactEndTouchEvent of b2DestroyContact, eNOTIFY_TOUCH_LOST, OnContactRemoved), an event added by a listener during the flush is sent in it; the pairs of a body sorted by slices.SortFunc instead of an insertion sort, quadratic for a body with hundreds of pairs (the same order: the keys of a body are unique; 400 crates asleep in a zone from 341 to 103 µs per step); the docs, with the references (Box2D, PhysX, Jolt, Godot, Unity) and the costs measured machine at rest
…s (it walks on a plane, a bumpy terrain and a hilly mesh without entering them deeper than 1 mm, stays supported and doesn't move once stopped; slides along a wall at 45° without entering it nor sticking, at the speed of the walk along it; climbs a slope of 30° and stops at the foot of a slope of 60°; walks steps of 0.2 m to the top and stops at a riser of 0.6 m; stays on the floor down a slope of 30° at 3 m/s and off a step of 0.3 m, and the floor it looks for is a setting: off a platform of 1 m it falls with 0 and with the 0.5 m of Jolt, and is put on the floor with 1.5 m; is carried by a kinematic platform sideways and up; pushes a ball and a crate ahead and is stopped by a crate of 8 t, which never enters it further than the slop; a crate of 8 t at 4 m/s doesn't go through it; 2 characters head on stop against each other, crossing they slide along each other, never within 2 radii; the layers and the ignored pairs of its inner body; Teleport and RemoveCharacter; walking through a static trigger zone it enters it once and leaves it once, standing in it it stays in it, and the zone never blocks it; 24 characters crossing a crowded terrain give the same bits with 1 and 8 workers, and their updates allocate nothing once their buffers have grown (the scene walked again by the same characters, bit for bit, 2 collections before each frame); the cost of 24 and 200 characters on the open terrain and in the crowd); red: the type CharacterVirtual doesn't exist
…etween 2 steps with the queries of the world (named as the CharacterVirtual of Jolt, which keeps Character for its character on a rigid body; the mover of Box2D v3, PxController and CharacterBody3D are of the same kind): the contacts of its capsule inflated by a padding of 2 cm, gathered within 10 cm by the contact generator of the world (GJK/EPA, the patches of the surfaces, one plane per manifold with the velocity of its body; on an inactive edge of a surface the normal of EPA is kept when it brakes the motion less than the normal of the triangle, the hint of ActiveEdges::FixNormal of Jolt which the pairs of a step don't use: a capsule level with the top edge of a riser had the riser as a wall at a distance of 0 and stuck at every step), the velocity slid along the planes by time of impact (15 iterations, a steep slope losing the velocity towards it first, the edge of 2 planes slid along, the push of a dynamic body met bounded by MaxStrength * dt and never downwards) and the path checked by a sweep which passes through the bodies the capsule starts in (5 loops); the ground among the contacts of the lower sphere of the capsule (on the ground, on steep ground, not supported, in the air; the normal, the velocity of a platform on its arc, the body, UpdateGroundVelocity), the weight of the character on a dynamic ground, the stick to the floor within StickToFloor (a setting, 0.5 m by default as Jolt, 0 for none) when it leaves the ground without going up, the stairs of StepHeight walked up, forward and down when a steep contact blocked the step (the edge of a step tested 15 cm further), the velocity towards the steep slopes cancelled, a character standing still on a walkable slope stopped dead (the samples of Jolt, floor_stop_on_slope of Godot); an inner kinematic body at the capsule, teleported after each update without velocity, so that the rays, the dynamic bodies (stopped by the solver: a crate of 8 t never goes through a character) and the other characters see it (2 characters block each other head on, slide along each other crossing); the layers and the ignored pairs of the inner body, the triggers entered and left by the inner body as by a kinematic body and never blocking the character (its contacts and its sweeps skip them); the defaults of Jolt (50°, 0.4 m, 100 N, 70 kg) and its constants, named; the docs with the measures (11 µs per update on the open terrain, 54 µs in a crowd against walls: 0.27 and 1.3 ms for 24 characters, 6.3 and 9.8 ms for 200, counted: 1.5 to 2 gatherings of contacts over 14 to 23 triangles and 1.9 to 2.3 sweeps per update, the counts of Jolt against a wall); 13 % taken off every update, identical to the bit: the supports of GJK skip the rotations of a shape at the identity (a triangle, a standing capsule), the cores of GJK are made once per pair instead of once per triangle, the proxy of a swept triangle once per scratch (A/B on the same machine: 24 characters alone 787 to 677 µs per frame, the crowd of 24 1 063 to 925 µs, 200 alone 7.64 to 6.58 ms, the crowd of 200 9.98 to 9.07 ms); measured and not kept: the rejection of a triangle by the exact distance of the segment before GJK (PhysX processTriangle: 7.6 of 14 triangles rejected, 0 to -2 %) and the triangles gathered once per update (PhysX findTouchedGeometry: a reuse of 1.3, the gathering cost what it saved); the buffers of the contacts and of the sweeps of a character its own (the contact generator takes them as a parameter, nil for the pools of the pairs of a step; epa.Scratch for EPA and the clipping of the features), not those of the pools of the world: a sync.Pool emptied by the garbage collector gave new buffers, which grew again, and an update allocated now and then; the 33 fingerprints of the bench unchanged
… cube made a box twice as big, a sphere and a taller capsule stays stable, never goes through the ground, rests at the height of its new shape and takes the mass and the inertia of the shape; a cube resting awake made a sphere has one contact point at the next step, not the four corners of the cube taken from the pair cache; a ball on a rope made a box keeps its joint; a sleeping plate made thinner wakes up with the crate it carries, which lands on it; a static pedestal shrunk under a sleeping crate lets it fall, the ground plane made a slab lifts it and made a plane again lets it down, the proxies in a consistent order; a trigger box holding a sleeping crate in its corner made its inscribed sphere, with the same AABB, tests its overlap again and the crate leaves it; no shape, a plane or a mesh on a dynamic or a kinematic body refused with an error, the body left as it is, a refusal without allocation; the density of a body changed in place gives the mass and the inertia of its shape, the same impulse half the velocity at twice the density, wakes a sleeping body, a static body keeps the density written and its infinite mass, a density which is not positive is refused on a dynamic body; the mixed scene with shapes and densities changed during the run gives the same bits with 1, 4 and 8 workers; a step after a change allocates nothing once the buffers have grown; RigidBody.SetShape and SetDensity without a world); red: the functions don't exist
…tShape, World.SetDensity: the body keeps its index, its proxy, its joints, its layers, the pairs it ignores and its island, takes the mass and the inertia of the shape at its density, wakes up with the sleeping bodies around its old and its new shape, and its pairs skip the pair cache at the next step, as BodyInterface::SetShape of Jolt with its mass properties updated and its contact cache invalidated, and PxRigidBodyExt::updateMassAndInertia of PhysX for the density; the body stays where it is, the shapes being centered; a plane, a heightfield or a mesh on a dynamic or a kinematic body is refused with ErrShapeNotConvex, no shape with ErrNoShape, a density which is not positive on a dynamic body with ErrMasslessBody, never in silence; a static body keeps the density written for the day it becomes dynamic; the broad phase moves the proxy to the tree of its new kind at the next step; the neighbours within reach of the old and the new AABB woken up as by Teleport and UpdateHeightfield); RigidBody.SetShape and SetDensity (the formulas of NewRigidBody); the docs; the 33 fingerprints of the bench unchanged
@ragoune
ragoune merged commit bb736d2 into master Oct 4, 2026
3 checks passed
@codecov

codecov Bot commented Oct 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.94492% with 155 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
charactervirtual.go 86.66% 31 Missing and 11 partials ⚠️
actor/convexhull.go 91.17% 19 Missing and 17 partials ⚠️
collision_triangles.go 92.74% 19 Missing and 4 partials ⚠️
charactervirtual_move.go 90.94% 15 Missing and 6 partials ⚠️
actor/trianglemesh.go 96.10% 10 Missing and 3 partials ⚠️
kinematic.go 92.94% 5 Missing and 1 partial ⚠️
epa/epa.go 33.33% 4 Missing ⚠️
collision.go 93.18% 1 Missing and 2 partials ⚠️
actor/rigidbody.go 94.28% 0 Missing and 2 partials ⚠️
epa/manifold.go 66.66% 2 Missing ⚠️
... and 2 more
Files with missing lines Coverage Δ
articulation.go 92.59% <100.00%> (ø)
ccd.go 100.00% <100.00%> (+2.58%) ⬆️
event.go 98.62% <100.00%> (+6.89%) ⬆️
gjk/gjk.go 72.58% <100.00%> (+0.28%) ⬆️
island.go 100.00% <100.00%> (ø)
joint.go 92.50% <100.00%> (ø)
query.go 100.00% <100.00%> (ø)
query_overlap.go 100.00% <100.00%> (ø)
query_sweep.go 98.47% <100.00%> (+0.11%) ⬆️
query_triangles.go 100.00% <100.00%> (ø)
... and 14 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ragoune
ragoune deleted the v0.5.0 branch October 4, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant