Skip to content

Combat Demo

App: apps/combat_demo/

Demonstrates the combat substrate (engine PLM-196 / ADR 0066 + 0069) with hand-built boxes: an attacker’s sword Hitbox lunges at a target’s body Hurtbox. Each box is a Jolt sensor riding its node; Jolt reports the overlap and the engine calls Combat.onHit. The callback reads the hitbox’s opaque payload as damage, subtracts it from a plain Wren health field, and on death disables the hurtbox so the corpse stops taking hits.

For the Blender-authored path — hit/hurt meshes authored in Blender and reached via node.hitboxes — see the Blender Combat Demo. This demo builds its boxes by hand in Wren instead, which is the simplest way to see the surface.

There is no HP/damage/death type in the engine. Health here is a plain _hp field — exactly how a real game (Hadal, LowTide) implements it. The engine’s job ends at detection.

Terminal window
% ./plume3d combat_demo

A GUI window shows the target’s HP (a game-side bar), whether the swing is active, and a running hit log.

  • On init, builds a scene with an attacker node and a target node, a sword Hitbox (owner = 1, tag = "sword", payload = 25) on the attacker, and a body Hurtbox (owner = 2) on the target, then registers Combat.onHit(scene) { |hit| … }.
  • The callback is the game’s health model: _hp = _hp - hit.payload. When _hp reaches 0 it sets _dead and _body.enabled = false — a corpse stops taking hits, a game decision the engine knows nothing about.
  • In update, a 1.5 s swing cycle lunges the attacker into overlap with the target, holds briefly, then retreats. Toggling _sword.enabled is a game-driven “active window” — deactivating re-arms the next swing (a hitbox strikes each victim once per active period). A real game would instead bindWindow(controller, "swing_hit") to an animation hit-window authored as Blender pose-markers.
import "engine" for Scene, Hitbox, Hurtbox, Combat
var sword = Hitbox.sphere(attacker, null, "", 0.6)
sword.owner = 1
sword.tag = "sword"
sword.payload = 25 // opaque to the engine — this game reads it as damage
var body = Hurtbox.box(target, null, "", 0.5, 1.0, 0.5)
body.owner = 2
var hp = 100 // health is 100% game-side
Combat.onHit(scene) { |hit|
hp = hp - hit.payload // engine detected; the game decides the cost
if (hp <= 0) body.enabled = false // death is a game decision
}
Terminal window
% ./plume3d --test apps/combat_demo

Checks the frozen surface is bound + callable and the game-side health model is sound. The detection mechanics (sensor overlap, dedup, i-frame/window gating, owner/layer filtering) are covered by tests/unit/test_combat_wren.cpp; that combat sensors never leak into Physics.* world queries is covered by tests/unit/test_combat_queries.cpp.