AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Zlib Self-run

Dev Physics Lesson

skill-nebulavenus-forge-gpu-dev-physics-lesson · by Nebulavenus

Add a physics lesson — particle dynamics, rigid bodies, collisions, constraints, rendered with SDL GPU

No reviews yet
0 installs
27 views
0.0% view→install

Install

$ agentstack add skill-nebulavenus-forge-gpu-dev-physics-lesson

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-nebulavenus-forge-gpu-dev-physics-lesson)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Dev Physics Lesson? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Every physics lesson produces two things: library code and a demo program. The library — common/physics/forge_physics.h — is the crown jewel. The lessons teach concepts; the library is what remains when the learning is done. It must be thorough, correct, performant, tested, safe, and valid. The demo program visualizes the library in action, rendered in real time with SDL GPU.

When to use this skill:

  • You need to teach particle dynamics, rigid body physics, or collision detection
  • A learner wants to understand integration, forces, impulses, or constraints
  • The concept benefits from a live 3D visualization showing behavior over time
  • New functionality needs to be added to common/physics/forge_physics.h

Smart behavior:

  • Before creating a lesson, check if an existing physics lesson already covers it
  • Library first, demo second. Design, implement, document, and test the

library code before writing a single line of the demo program. The demo exercises the library — it does not replace it.

  • Physics lessons are visual — every concept must be observable in the running program
  • Focus on why the math works, not just the code — connect equations to behavior
  • Use simple geometric shapes (cubes, spheres, capsules) — the physics is the star
  • All scene geometry comes from common/shapes/forge_shapes.h — never write

inline geometry generation functions

  • Cross-reference math lessons (vectors, matrices, quaternions) and GPU lessons

where relevant

Arguments

The user (or you) can provide:

  • Number: two-digit lesson number (e.g. 01, 02)
  • Topic name: kebab-case (e.g. point-particles, springs-and-constraints)
  • Description: what this teaches (e.g. "Symplectic Euler integration, gravity, drag")

If any are missing, infer from context or ask.

Steps

1. Analyze what's needed

  • Check existing physics lessons: Is there already a lesson for this topic?
  • Check common/physics/: Does relevant library code already exist?
  • Check common/shapes/forge_shapes.h: Does the shapes library already

provide every shape the lesson needs? If a required shape is missing, plan to add it to forge_shapes.h (not inline in the lesson).

  • Identify the scope: What specific physics concepts does this lesson cover?
  • Find cross-references: Which math/GPU lessons relate?
  • Check PLAN.md: Where does this lesson fit in the physics track?

2. Create the lesson directory

lessons/physics/NN-topic-name/

With subdirectories:

lessons/physics/NN-topic-name/
  main.c
  CMakeLists.txt
  README.md
  assets/

Baseline rendering shaders (shadow, scene, grid, sky, UI) are provided by forge_scene.h — no per-lesson copies needed. If the lesson introduces a topic-specific shader (e.g. a debug visualization pass), add a shaders/ subdirectory for those files only.

3. Design and implement the physics library code

Every physics lesson adds to common/physics/forge_physics.h. This is not optional — it is the primary deliverable. The demo program exists to exercise and visualize the library; the library is what ships.

For the first physics lesson, create the file. For subsequent lessons, extend it. Design the API before writing the demo. The demo calls the library — never the reverse.

Library standards

These standards are non-negotiable. The physics library is safety-critical code — incorrect integration, missed edge cases, or division by zero produce simulations that explode, tunnel through geometry, or silently drift. Every function must be defensible.

Correctness:

  • Every function must implement a named, well-understood algorithm (symplectic

Euler, Verlet, GJK, sequential impulse). Cite the source in the doc comment — a textbook, paper, or established reference.

  • Equations in the README must correspond exactly to the code. If the README

shows $v(t + \Delta t) = v(t) + a \cdot \Delta t$, the code must compute that expression in that order. No silent rearrangements.

  • Collision detection must handle degenerate cases: zero-length normals,

coincident positions, zero-radius shapes, zero-mass bodies. Document what each function does when given degenerate input.

  • Integration must preserve physical invariants where the algorithm guarantees

it. Symplectic Euler preserves phase-space volume — verify this in tests by checking energy over long runs.

Numerical safety:

  • Never divide without checking the denominator. Guard 1.0f / mass with an

inv_mass field precomputed at construction, where mass == 0 means infinite mass (static object) and inv_mass == 0.

  • Normalize vectors only after checking length > epsilon. Use vec3_length()

and compare against 1e-6f before calling vec3_normalize().

  • Clamp values that have physical bounds: restitution to [0, 1], damping

to [0, 1], penetration depth to >= 0. Document the valid range in the parameter comment.

  • Use SDL_fabsf() for float comparisons, not ==. Two floats are "equal" if

`SDL_fabsf(a - b) 64.

Safety and validation:

  • Every public function documents its preconditions. If dt must be positive,

say so. If a pointer must be non-NULL, say so.

  • Init functions (forge_physics_particle_create, etc.) must produce a valid

object with all fields initialised — no partially constructed state.

  • Functions that mutate state (integration, impulse application) must leave

the object in a valid state even if called with extreme inputs (very large dt, very large forces). Clamp or early-return rather than producing NaN or infinity.

  • Never read uninitialised memory. Every struct field must have an explicit

initial value in the init function.

Header-only implementation:

  • static inline for all functions — no separate .c compilation unit
  • Guard with #ifndef FORGE_PHYSICS_H / #define / #endif
  • Include "math/forge_math.h", "containers/forge_containers.h", and ` — never include , , or ` directly
  • Use dynamic arrays (forge_containers.h) for variable-size outputs (contacts, SAP pairs)
  • Deterministic: identical inputs and fixed timestep produce identical outputs

Documentation (every function, no exceptions):

/* Apply gravitational acceleration to a particle.
 *
 * Adds gravity * mass to the particle's force accumulator. Static
 * particles (inv_mass == 0) are unaffected.
 *
 * This uses Newton's second law: F = m * g. The force accumulator
 * stores the total force; integration divides by mass to get
 * acceleration.
 *
 * Parameters:
 *   p       — particle to apply gravity to (must not be NULL)
 *   gravity — gravitational acceleration, typically (0, -9.81, 0)
 *
 * Usage:
 *   forge_physics_apply_gravity(&particle, (vec3){0, -9.81f, 0});
 *
 * See: Physics Lesson 01 — Point Particles
 * Ref: Millington, "Game Physics Engine Development", Ch. 3
 */
static inline void forge_physics_apply_gravity(ForgePhysicsParticle *p,
                                               vec3 gravity)
{
    if (p->inv_mass == 0.0f) return;  /* static — no forces apply */
    /* F = m * g, but we accumulate force and divide by mass later */
    p->force_accum = vec3_add(p->force_accum,
                              vec3_scale(gravity, p->mass));
}

Every doc comment must include:

  • Summary (one sentence — what it does)
  • Algorithm or physical law being implemented
  • Parameters with types, valid ranges, and nullability
  • Return value (if any) with units
  • Usage example
  • Cross-reference to the lesson that introduces it
  • Reference to the source material (textbook, paper)

Naming:

  • Functions: forge_physics_verb_noun() — e.g. forge_physics_integrate(),

forge_physics_apply_gravity(), forge_physics_collide_sphere_plane()

  • Types: ForgePhysicsNoun — e.g. ForgePhysicsParticle,

ForgePhysicsContact, ForgePhysicsRigidBody

  • Constants: FORGE_PHYSICS_UPPER — e.g. FORGE_PHYSICS_MAX_VELOCITY

Core types to establish in Lesson 01:

#ifndef FORGE_PHYSICS_H
#define FORGE_PHYSICS_H

#include 
#include "math/forge_math.h"
#include "containers/forge_containers.h"
#include "arena/forge_arena.h"

/* --- Particle ----------------------------------------------------------- */

typedef struct ForgePhysicsParticle {
    vec3  position;
    vec3  velocity;
    vec3  acceleration;    /* accumulated forces / mass this frame         */
    vec3  force_accum;     /* forces accumulated before integration        */
    float mass;            /* kg — zero means infinite mass (immovable)    */
    float inv_mass;        /* 1/mass — precomputed, 0 for static objects   */
    float damping;         /* velocity damping per frame [0..1]            */
    float restitution;     /* coefficient of restitution [0..1]            */
} ForgePhysicsParticle;

/* ... functions grow lesson by lesson ... */

#endif /* FORGE_PHYSICS_H */
Testing the library (MANDATORY)

The physics library is tested independently of the demo program. Tests validate correctness, edge cases, numerical stability, and determinism. Every function added to forge_physics.h must have corresponding tests in tests/physics/test_physics.c.

Test categories (every function must have all applicable categories):

  1. Basic correctness — Known inputs produce expected outputs within

tolerance. Use hand-computed reference values, not "whatever the code outputs."

``c /* Gravity on a 2 kg particle for 1 second should produce v = -9.81 m/s */ ForgePhysicsParticle p = forge_physics_particle_create( (vec3){0, 10, 0}, /* position */ 2.0f, /* mass */ 0.0f, /* damping */ 1.0f); /* restitution */ forge_physics_apply_gravity(&p, (vec3){0, -9.81f, 0}); forge_physics_integrate(&p, 1.0f); ASSERT_NEAR(p.velocity.y, -9.81f, 1e-4f); ``

  1. Edge cases — Zero mass (static), zero dt, zero-length vectors,

coincident positions, maximum velocity, very large forces.

``c /* Static particle (inv_mass == 0) must not move under any force */ ForgePhysicsParticle p = forge_physics_particle_create( (vec3){5, 0, 0}, /* position */ 0.0f, /* mass (0 → static) */ 0.0f, /* damping */ 1.0f); /* restitution */ forge_physics_apply_gravity(&p, (vec3){0, -9.81f, 0}); forge_physics_integrate(&p, 1.0f); ASSERT_NEAR(p.position.x, 5.0f, 1e-6f); ASSERT_NEAR(p.velocity.y, 0.0f, 1e-6f); ``

  1. Conservation and stability — Run a closed system for thousands of

steps. Total energy (kinetic + potential) should remain bounded for symplectic integrators. Explicit check: no NaN, no infinity, no position > 1e6.

``c /* 10000 steps of a bouncing ball — energy must not grow unboundedly */ for (int i = 0; i accumulator += dt; while (state->accumulator >= PHYSICS_DT) { physics_step(state, PHYSICS_DT); state->accumulator -= PHYSICS_DT; } float alpha = state->accumulator / PHYSICS_DT; /* Interpolate positions for rendering: lerp(prev, curr, alpha) */ ``

  • Force accumulator pattern — Forces are accumulated each step, then

cleared after integration:

```c / Apply forces / forgephysicsapplygravity(&particle, (vec3){0, -9.81f, 0}); forgephysicsapplydrag(&particle, drag_coeff);

/ Integrate (symplectic Euler) / forgephysicsintegrate(&particle, dt);

/ Clear forces for next step / particle.forceaccum = vec3zero(); ```

  • Reset key — Press R to reset the simulation to its initial state. This

is essential for physics demos where objects settle or leave the scene.

  • Pause key — Press P to pause/resume the simulation. Rendering

and camera controls continue while paused.

  • Slow motion — Press T to toggle half-speed simulation for observing

fast phenomena.

Scene geometry via forge_shapes.h (MANDATORY):

All scene geometry must come from common/shapes/forge_shapes.h. Never write inline geometry generation functions — use the shared library so every lesson benefits from the same tested, documented shapes.

  • Sphere: forge_shapes_sphere(32, 16) or forge_shapes_icosphere(1)
  • Box/Cube: forge_shapes_cube(1, 1) — use forge_shapes_compute_flat_normals() for face normals
  • Capsule: forge_shapes_capsule(32, 8, 8, 1.0f)
  • Cylinder: forge_shapes_cylinder(32, 1)
  • Torus: forge_shapes_torus(32, 16, major, tube)
  • Ground plane: Handled by the shader grid (no geometry needed)

If the lesson requires a shape that forge_shapes.h does not yet provide, add it to the library first — following the existing API conventions (unit-scale, centred at origin, CCW winding, struct-of-arrays layout). Update common/shapes/README.md and add tests under tests/shapes/.

#include "shapes/forge_shapes.h"

/* Generate shapes at init time, upload to GPU, then free CPU copies.
 * Always check vertex_count — generators return an empty shape
 * (all pointers NULL, counts 0) if allocation fails.
 *
 * CRITICAL: ForgeShape uses struct-of-arrays (separate positions[] and
 * normals[]), but forge_scene.h shaders expect interleaved ForgeSceneVertex.
 * You MUST convert via upload_shape_vb() — never upload raw SoA data. */

/* Helper: convert ForgeShape SoA to ForgeSceneVertex AoS and upload */
static SDL_GPUBuffer *upload_shape_vb(ForgeScene *scene,
    const ForgeShape *shape)
{
    if (!shape || !shape->positions || shape->vertex_count == 0) return NULL;
    ForgeSceneVertex *verts = SDL_calloc(
        (size_t)shape->vertex_count, sizeof(ForgeSceneVertex));
    if (!verts) return NULL;
    for (int i = 0; i vertex_count; i++) {
        verts[i].position = shape->positions[i];
        verts[i].normal   = shape->normals
            ? shape->normals[i]
            : (vec3){ 0.0f, 0.0f, 0.0f };
    }
    SDL_GPUBuffer *buf = forge_scene_upload_buffer(scene,
        SDL_GPU_BUFFERUSAGE_VERTEX, verts,
        (Uint32)shape->vertex_count * (Uint32)sizeof(ForgeSceneVertex));
    SDL_free(verts);
    return buf;
}

/* In SDL_AppInit: */
ForgeShape sphere = forge_shapes_sphere(32, 16);
if (sphere.vertex_count == 0) {
    SDL_Log("Failed to generate sphere");
    return SDL_APP_FAILURE;
}
state->sphere_vb = upload_shape_vb(&state->scene, &sphere);
state->sphere_ib = forge_scene_upload_buffer(&state->scene,
    SDL_GPU_BUFFERUSAGE_INDEX, sphere.indices,
    (Uint32)sphere.index_count * (Uint32)sizeof(uint32_t));
state->sphere_idx_count = (int)sphere.index_count;
forge_shapes_free(&sphere);
if (!state->sphere_vb || !state->sphere_ib) {
    SDL_Log("Failed to upload sphere mesh");
    return SDL_APP_FAILURE;
}

ForgeShape box = forge_shapes_cube(1, 1);
if (box.vertex_count == 0) {
    SDL_Log("Failed to generate box");
    return SDL_APP_FAILURE;
}
forge_shapes_compute_flat_normals(&box);  /* face normals for rigid bodies */
state->box_vb = upload_shape_vb(&state->scene, &box);
state->box_ib = forge_scene_upload_buffer(&state->scene,
    SDL_GPU_BUFFERUSAGE_INDEX, box.indices,
    (Uint32)box.index_count * (Uint32)sizeof(uint32_t));
state->box_idx_count = (int)box.index_count;
forge_shapes_free(&box);
if (!state->box_vb || !state->box_ib) {
    SDL_Log("Failed to upload box mesh");
    return SDL_APP_FAILURE;
}

Template structure:

/*
 * Physics Lesson NN — Topic Name
 *
 * Demonstrates: [what this shows]
 *
 * Controls:
 *   WASD / Arrow keys — move camera
 *   Mouse             — look around
 *   R                 — reset simulation
 *   P                 — pause / resume
 *   T                 — toggle slow motion
 *   Escape            — release mouse / quit
 *
 * SPDX-License-Identifier: Zlib
 */

#define SDL_MAIN_USE_CALLBACKS 1
#include 
#include 
#include 

#include "math/forge_math.h"
#include "physics/forge_physics.h"
#include "shapes/forge_shapes.h"

#define FORGE_SCENE_IMPLEMENTATION
#include "scene/forge_scene.h"

/* ── Constants ─────────────────────────────────

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Nebulavenus](https://github.com/Nebulavenus)
- **Source:** [Nebulavenus/forge-gpu](https://github.com/Nebulavenus/forge-gpu)
- **License:** Zlib

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.