bevy_behave 0.3.0

A behaviour tree plugin for bevy with dynamic spawning.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
<div align="left">
<p>
    <strong>A behaviour tree plugin for bevy with dynamic spawning.</strong>
</p>
<p>
    <a href="https://crates.io/crates/bevy_behave"><img src="https://img.shields.io/crates/v/bevy_behave.svg" alt="crates.io"/></a>
    <a href="https://docs.rs/bevy_behave"><img src="https://img.shields.io/badge/docs-latest-blue.svg" alt="docs.rs"/></a>
    <a href="https://discord.com/channels/691052431525675048/1347180005104422942"><img src="https://img.shields.io/badge/discord-bevy_behave-blue" alt="discord channel"/></a>
    
</p>
</div>

`bevy_behave` is a behaviour tree plugin for bevy with a sensible API and minimal overheads.
No magic is required for the task components, they are are regular bevy components using triggers to report status.

When an action node (aka leaf node or task node) in the behaviour tree runs, it will spawn an entity with
the components you specified in the tree definition. The tree then waits for this entity to
trigger a status report, at which point the entity will be despawned.

You can also take actions without spawning an entity by triggering an observed `Event`, which can also be used as a conditional in a control node.


This tree definition is from the [chase example](https://github.com/RJ/bevy_behave/blob/main/examples/chase.rs):

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# fn get_enemy_entity() -> Entity { Entity::PLACEHOLDER }
# fn get_player_entity() -> Entity { Entity::PLACEHOLDER }
# #[derive(Component, Clone)]
# struct WaitUntilPlayerIsNear { player: Entity }
# #[derive(Component, Clone)]
# struct MoveTowardsPlayer { player: Entity, speed: f32 }
# #[derive(Clone)]
# struct RandomizeColour;
let npc_entity = get_enemy_entity();
let player = get_player_entity();
// The tree definition (which is cloneable).
// and in theory, able to be loaded from an asset file using reflection (PRs welcome).
// When added to the BehaveTree component, this gets transformed internally to hold state etc.
//
// These trees are `ego_tree::Tree<Behave>` if you want to construct them manually.
// Conventient macro usage shown below.
let tree = behave! {
    Behave::Forever => {
        Behave::Sequence => {
            Behave::spawn((
                Name::new("Wait until player is near"),
                WaitUntilPlayerIsNear{player}
            )),
            Behave::Sequence => {
                Behave::spawn((
                    Name::new("Move towards player while in range"),
                    MoveTowardsPlayer{player, speed: 100.0}
                )),
                // MoveTowardsPlayer suceeds if we catch them, in which randomize our colour.
                // This uses a trigger to take an action without spawning an entity.
                Behave::trigger(RandomizeColour),
                // then have a nap (pause execution of the tree)
                // NB: this only runs if the trigger_req was successful, since it's in a Sequence.
                Behave::Wait(5.0),
            }
        }
    }
};
```


<details>

<summary><small>You can also compose trees from subtrees</small></summary>

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# fn get_enemy_entity() -> Entity { Entity::PLACEHOLDER }
# fn get_player_entity() -> Entity { Entity::PLACEHOLDER }
# #[derive(Component, Clone)]
# struct WaitUntilPlayerIsNear { player: Entity }
# #[derive(Component, Clone)]
# struct MoveTowardsPlayer { player: Entity, speed: f32 }
# #[derive(Clone)]
# struct RandomizeColour;

let npc_entity = get_enemy_entity();
let player = get_player_entity();
// Breaking a tree into two trees and composing, just to show how it's done.
let chase_subtree = behave! {
    Behave::Sequence => {
        Behave::spawn((
            Name::new("Move towards player while in range"),
            MoveTowardsPlayer{player, speed: 100.0}
        )),
        // MoveTowardsPlayer suceeds if we catch them, in which randomize our colour.
        // This uses a trigger to take an action without spawning an entity.
        Behave::trigger(RandomizeColour),
        // then have a nap (pause execution of the tree)
        // NB: this only runs if the trigger_req was successful, since it's in a Sequence.
        Behave::Wait(5.0),
    }
};

let tree = behave! {
    Behave::Forever => {
        // Run children in sequence until one fails
        Behave::Sequence => {
            // WAIT FOR THE PLAYER TO GET CLOSE
            // Spawn with any normal components that will control the target entity:
            Behave::spawn((
                Name::new("Wait until player is near"),
                WaitUntilPlayerIsNear{player}
            )),
            // CHASE THE PLAYER
            @ chase_subtree
        }
    }
};
```

</details>

<br>

Once you have your tree definition, you spawn an entity to run the behaviour tree by adding a `BehaveTree` component:

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# fn setup_tree(mut commands: Commands, tree: ego_tree::Tree<Behave>, npc_entity: Entity) {
// Spawn an entity to run the behaviour tree.
// Make it a child of the npc entity for convenience.
// The default is to assume the Parent of the tree entity is the Target Entity you're controlling.
commands.spawn((
    Name::new("Behave tree for NPC"),
    BehaveTree::new(tree),
    ChildOf(npc_entity),
));
# }
```

If your behaviour tree is not a child of the target entity you want to control, you can specify the target entity explicitly:

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# fn get_entity_to_control() -> Entity { Entity::PLACEHOLDER }
# fn setup_tree(mut commands: Commands, tree: ego_tree::Tree<Behave>) {
let target = get_entity_to_control();
commands.spawn((
    Name::new("Behave tree for NPC"),
    BehaveTree::new(tree),
    BehaveTargetEntity::Entity(target),
));
# }
```

Or in case of a deeper hierarchy, you can use `BehaveTargetEntity::RootAncestor` to find the topmost entity.



## Control Flow Nodes

The following control flow nodes are supported. Control flow logic is part of the `BehaveTree` and doesn't spawn extra entities.

| Node                    | Description                                                                                                                       |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `Behave::Sequence`      | Runs children in sequence, failing if any child fails, succeeding if all children succeed.                                        |
| `Behave::Fallback`      | Runs children in sequence until one succeeds. If all fail, this fails. Sometimes called a Selector node.                          |
| `Behave::Invert`        | Inverts success/failure of child. Must only have one child.                                                                       |
| `Behave::AlwaysSucceed` | Succeeds instantly.                                                                                                               |
| `Behave::AlwaysFail`    | Fails instantly.                                                                                                                  |
| `Behave::While`         | Runs the second child repeatedly, provided the first child returns success. If only one child, runs it repeatedly until it fails. |
| `Behave::IfThen`        | If the first child succeeds, run the second child. (otherwise, run the optional third child)                                      |


### Control Flow Node Examples


#### Sequence

Use `Behave::Sequence` to run children in sequence, failing if any child fails, succeeding if all children succeed.

This example runs a trigger (and assuming it reports success..), waits 5 secs, then spawns an entity with an imagined `BTaskComponent` to do something.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Component, Default, Clone)]
# struct BTaskComponent;
# #[derive(Clone)]
# struct DoA;
let tree = behave! {
    Behave::Sequence => {
        Behave::trigger(DoA),
        Behave::Wait(5.0),
        Behave::spawn_named("B-Doer", BTaskComponent::default()),
    }
};
```


#### Fallback

Use `Behave::Fallback` to run children in sequence until one succeeds. If they all fail, the Fallback node also fails.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct TryA;
# #[derive(Clone)]
# struct TryB;
# #[derive(Clone)]
# struct TryC;
let tree = behave! {
    Behave::Fallback => {
        Behave::trigger(TryA),
        Behave::trigger(TryB),
        Behave::trigger(TryC),
    }
};
```

#### While (single child usage)

You can wrap a single node in a `Behave::While` node to repeat it until it fails.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct DoSlowThingUntilFailure;
let tree = behave! {
    Behave::While => {
        Behave::trigger(DoSlowThingUntilFailure),
    }
};
```

#### While (two child usage)

With two children, the first child is the conditional check. If it succeeds, the second child is run. And then the node repeats.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct AirbourneCheck;
# #[derive(Clone, Component, Default)]
# struct FlapWings;
# #[derive(Clone, Component, Default)]
# struct PointToes;
let tree = behave! {
    Behave::While => {
        Behave::trigger(AirbourneCheck),
        Behave::spawn_named("Fly!", (FlapWings::default(), PointToes::default())),
    }
};
```

#### IfThen (two child usage)

The first child is the conditional check, the second is only run if the condition succeeds.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct HungryCheck;
# #[derive(Clone, Component, Default)]
# struct MoveToFood;
# #[derive(Clone, Default)]
# struct EatFood;
let tree = behave! {
    Behave::IfThen => {
        Behave::trigger(HungryCheck),
        Behave::Sequence => {
            // move to food, but only allow 10 seconds to do so. Then eat, if we got there.
            Behave::spawn_named("Go to food", (MoveToFood::default(), BehaveTimeout::from_secs(10.0, false))),
            Behave::trigger(EatFood),
        },
    }
};
```

#### IfThen (three child usage)

An optional third child acts as the "else" clause, and is run if the conditional fails.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct HungryCheck;
# #[derive(Clone, Component, Default)]
# struct MoveToFood;
# #[derive(Clone, Default)]
# struct EatFood;
# #[derive(Clone)]
# struct TidyKitchen;
let tree = behave! {
    Behave::IfThen => {
        Behave::trigger(HungryCheck),
        Behave::Sequence => {
            Behave::spawn_named("Go to food", (MoveToFood::default(), BehaveTimeout::from_secs(10.0, false))),
            Behave::trigger(EatFood),
        },
        Behave::trigger(TidyKitchen),
    }
};
```


## Task Nodes

Task nodes are leaves of the tree which take some action, typically doing something to control your target entity, such as making it move.

#### Behave::Wait

Waits a given duration before Succeeding. The timer is ticked by the tree itself, so no entities are spawned.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
let tree = behave! {
    Behave::Wait(5.0),
};
```

#### Behave::spawn(...) and Behave::spawn_named(...)

When a `Behave::spawn_named` node runs, a new entity is spawned with the bundle of components you provided along with a
`BehaveCtx` component, used to get the target entity the tree is controlling, and the mechanism to generate status reports.

Once a result is reported, the entity is despawned.

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone, Component, Default)]
# struct WingFlapper;
// Flap our wings, and succeed (end the task) after 60 seconds.
let tree = behave! {
    Behave::spawn_named("Flying Task", 
        (WingFlapper::default(), BehaveTimeout::from_secs(60.0, true))
    )
};
```

Prefer the `Behave::spawn_named` variant, because in addition to adding a `Name` component to the spawned entity, it exposes this name in debug logging.

<details>

<summary>An example implementation (click to reveal)</summary>

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Component, Clone, Default)]
# struct Wings;
# impl Wings { fn flap(&mut self, speed: f32) { } }
# #[derive(Component, Clone, Default)]
# struct BirdMarker;
// An example plugin to provide a `WingFlapper` task component.

fn wing_flapper_task_plugin(app: &mut App) {
    app.add_systems(FixedUpdate, wing_flap_system);
}

#[derive(Component, Clone, Default)]
struct WingFlapper {
    speed: f32,
}

fn wing_flap_system(
    mut q_target: Query<&mut Wings, With<BirdMarker>>,
    flapper_tasks: Query<(&WingFlapper, &BehaveCtx)>,
    mut commands: Commands
) {
    // for each entity with a WingFlapper component and a BehaveCtx, flap the wings for its target entity
    for (flapper, ctx) in flapper_tasks.iter() {
        // the target entity is the one being controlled by the behaviour tree that spawned this task entity
        let target = ctx.target_entity();
        let Ok(mut target_wings) = q_target.get_mut(target) else {
            // Maybe the wings fell off? report task failure.
            commands.trigger(ctx.failure());
            continue;
        };
        target_wings.flap(flapper.speed);
    }
}
```
</details>


#### Behave::trigger(...)

When a `Behave::trigger` node runs, it will trigger an event, which the user observes and can either respond to with a success or failure immediately, or respond later from another system. You must specify an arbitrary `Clone` type which is passed along as
the payload of the trigger event, along with the `BehaveCtx`.

Here's how you might use a trigger conditional check to execute a specific task if a height condition is met:

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone)]
# struct HeightCheck { min_height: f32 }
# #[derive(Clone, Component, Default)]
# struct TakeActionWhenHigh;
let tree = behave! {
    Behave::IfThen => {
        Behave::trigger(HeightCheck { min_height: 10.0 }),
        Behave::spawn_named("High Thing", TakeActionWhenHigh::default()),
    }
};
```
<details>

<summary>And the implementation (click to reveal)</summary>


```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone, Component)]
# struct Position { x: f32, y: f32 }
// An example plugin to provide a `HeightCheck` trigger task

fn height_check_task_plugin(app: &mut App) {
    // add a global observer to answer conditional queries for HeightCheck:
    app.add_observer(on_height_check);
}

// Trigger payloads just need to be Clone.
// They are wrapped in a BehaveTrigger, which is a bevy Event.
#[derive(Clone)]
struct HeightCheck {
    min_height: f32,
}

// you respond by triggering a success or failure event created by the ctx:
fn on_height_check(trigger: Trigger<BehaveTrigger<HeightCheck>>, q: Query<&Position>, mut commands: Commands) {
    let ev = trigger.event();
    let ctx: &BehaveCtx = ev.ctx();
    let height_check: &HeightCheck = ev.inner();
    // lookup the position of the target entity (ie the entity this behaviour tree is controlling)
    let character_pos = q.get(ctx.target_entity()).expect("Character entity missing?");
    if character_pos.y >= height_check.min_height {
        commands.trigger(ctx.success());
    } else {
        commands.trigger(ctx.failure());
    }
}

```
</details>

<br>

If you respond with a success or failure from the observer you can treat the event as a conditional test as part of a control flow node. Alternatively, you can use it to trigger a side effect and respond later from another system. Just make sure to copy the `BehaveCtx` so you can generate a success or failure event at your leisure.



## Cargo Example

Have a look at the [chase example](https://github.com/RJ/bevy_behave/blob/main/examples/chase.rs) to see how these are used.
Run in release mode to support 100k+ enemies at once:
```bash
cargo run --release --example chase
```


## Utility components

For your convenience:

#### Triggering completion after a timeout

To trigger a status report on a dynamic spawn task after a timeout, use the `BehaveTimeout` helper component:

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# #[derive(Clone, Component, Default)]
# struct LongRunningTaskComp;
let tree = behave! {
    Behave::spawn_named("Long running task that succeeds after 5 seconds", (
        LongRunningTaskComp::default(),
        BehaveTimeout::from_secs(5.0, true)
    ))
};
```

This will get the `BehaveCtx` from the entity, and trigger a success or failure report for you after the timeout.



## `behave!` macro

The `behave!` macro is more powerful version of the `ego_tree::tree!` macro.
You can use ego_tree's `tree!` macro to build the tree, but this macro has some additional features
to make composing behaviours easier:

#### Merging in subtrees:

Use `@` to insert a subtree into the current tree:
```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
fn get_tree() -> Tree<Behave> {
    let subtree = behave! {
        Behave::Sequence => {
            Behave::trigger(A),
            Behave::Wait(1.0),
            Behave::trigger(B),
        }
    };

    behave! {
        Behave::Sequence => {
            Behave::Wait(5.0),
            @ subtree
        }
    }
}
```

Use `...` to insert multiple subtrees from an iterator of trees:
```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
fn get_tree() -> Tree<Behave> {
    let subtrees = [
        behave! { Behave::Wait(1.0) },
        behave! { Behave::Wait(2.0) },
        behave! { Behave::Wait(3.0) },
    ];

    behave! {
        Behave::Sequence => {
            Behave::Wait(5.0),
            ... subtrees
        }
    }
}
```


#### Inserting nodes from an iterator:

Use `@[ ]` to insert leaf nodes (`Behave` enum type, not a tree) from an iterator:
```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
fn get_tree() -> Tree<Behave> {
    let children = vec![
        Behave::trigger(A),
        Behave::Wait(1.0),
        Behave::trigger(B),
    ];
    behave! {
        Behave::Sequence => {
            @[ children ]
        }
    }
}
```

## Debug Logging

Call `BehaveTree::with_logging(true)` to enable debug verbose logging:

```rust
# use bevy_behave::prelude::*;
# use bevy::prelude::*;
# fn setup_tree(mut commands: Commands) {

let tree = behave! { Behave::Wait(5.0) }; // etc

commands.spawn((
    Name::new("Behave tree for NPC"),
    BehaveTree::new(tree).with_logging(true),
));
# }
```

<img src="https://github.com/RJ/bevy_behave/blob/main/examples/console_logging.png">

## Performance

is good.

* There's just one global observer for receiving task status reports from entities or triggers.
* Most of the time, the work is being done in a spawned entity using one of your action components,
and in this state, there is a marker on the tree entity so it doesn't tick or do anything until
a result is ready.
* Avoided mut World systems – the tree ticking should be able to run in parallel with other things.
* So a fairly minimal wrapper around basic bevy systems.

In release mode, i can happily toss 100k enemies in the chase demo and zoom around at max framerate.
It gets slow rendering a zillion gizmo circles before any bevy_behave stuff gets in the way.

**Chase example**

This is the chase example from this repo, running in release mode on an M1 mac with 100k enemies.
Each enemy has a behaviour tree child and an active task component entity. So 1 enemy is 3 entities.

https://github.com/user-attachments/assets/e12bc4dd-d7fb-4eca-8810-90d65300776d

**Video from my space game**

Here I have more complex behaviour trees managing orbits, landing, etc. Lots of PID controllers at work.
No attempts at optimising the logic yet, but I can add 5k ships running behaviours. Each is a dynamic avian physics object exerting forces via a thruster.




https://github.com/user-attachments/assets/ef4f0539-0b4d-4d57-9516-a39783de140f


## Bevy Version Compatibility

| bevy_behave | bevy |
| ----------- | ---- |
| 0.3         | 0.16 |
| 0.2.2       | 0.15 |


## Chat / Questions?

Say hi in the [bevy_behave discord channel](https://discord.com/channels/691052431525675048/1347180005104422942).

## Further Reading

* Cool interactive blog post using bevy_behave: https://www.hankruiger.com/posts/bevy-behave/
* [Wikipedia on Behavior Trees]https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)


## License

Same as bevy: MIT or Apache-2.0.

<hr>


#### Paths not taken

<details>

<summary>Alternative approach taking `IntoSystem` (not taken)</summary>

### Alternative approach for conditionals

I considered doing control flow by taking an `IntoSystem` with a defined In and Out type,
something like this:
```rust,ignore

pub type BoxedConditionSystem = Box<dyn System<In = In<BehaveCtx>, Out = bool>>;

#[derive(Debug)]
pub enum Behave {
    // ...
    /// If, then
    Conditional(BoxedConditionSystem),
}

impl Behave {
    pub fn conditional<Marker>(system: impl IntoSystem<In<BehaveCtx>, bool, Marker>) -> Behave {
        Behave::Conditional(Box::new(IntoSystem::into_system(system)))
    }
}
```

Then you could defined a cond system like, which is quite convenient:

```rust,ignore
fn check_distance(In(ctx): In<BehaveCtx>, q: Query<&Position, With<Player>>) -> bool {
    let Ok(player_pos) = q.get(ctx.target_entity).unwrap();
    player_pos.x < 100.0
}
```


However I don't think the resulting data struct would be cloneable, nor could you really read
it from an asset file for manipulation (or can you?)

I would also need mutable World in the "tick trees" system, which would stop it running in parallel maybe.
Anyway observers seem to work pretty well.
</details>