BT Boss Demo
App: apps/bt_boss_demo/
The payoff for behaviour-tree authoring (engine PLM-219, ADR 0068): three
enemies that share one .bt.toml schema, loader and editor, differing only in
their data.
| Enemy | Tree | Shape |
|---|---|---|
| Grunt (regular) | trees/enemy.bt.toml | one root selector — chase if near, else patrol; no phases |
| Mini-boss | trees/miniboss.bt.toml | a phase_switch with 2 cases (opener → enraged) |
| Boss | trees/boss.bt.toml | the same phase_switch with 4 cases (opener → aggressive → desperate → last stand) |
That is the whole thesis of the block: authoring a harder fight is more data, not more machinery.
HP is game-owned — always
Section titled “HP is game-owned — always”The engine never reads an HP field. Each frame the demo’s Wren owns and drains _hp, feeds health01
to PhaseEvaluator.evaluate (forward-only — a heal never drops a phase), and writes the returned phase to
the tree’s blackboard int the phase_switch node reads. A phase change fires a game-side entry hook
(the HUD banner). The HUD shows, per enemy: an HP bar, the current phase, the current action (read from the
ticking tree’s blackboard), and a flash banner the instant a new phase begins.
Because the two bosses drain at the same rate but carry different thresholds, at (say) 68 % HP the boss has already crossed into phase 1 (its threshold is 75 %) and switched its subtree, while the mini-boss is still in phase 0 (its threshold is 50 %) — same HP, different phase, purely because the data differs.
Run from root
Section titled “Run from root”% ./plume3d bt_boss_demoVerify headless
Section titled “Verify headless”% ./plume3d --test apps/bt_boss_demoLoads all three trees under the same loader and walks the boss phase machine 0 → 3 forward-only. The
tick-time subtree swap is covered by tests/unit/test_bt_load_wren.cpp.