Sleeping Bodies¶

what wakes a settled pile, and how to edit one without waking it.

â–¶ Run in browser

Tags: physics 3d sleeping

A pile of crates settles on the ground and falls asleep; a sleeping body is skipped by integration and the contact solve until something disturbs it, which is what keeps a large resting scene cheap. Bodies are tinted by their live sleep state, so the pile visibly dims as it settles and lights up the moment anything reaches it.

The interesting half is what counts as a disturbance. Changing a body wakes what it was holding up: shrink the pillar under the crate on top and the crate falls, because the seam wakes its riders rather than waiting for a contact that has already gone. The same edit with wake=False moves the pillar and leaves the crate hanging in mid-air, which is the picture of what suppression buys (level streaming and pooled respawns do not re-simulate a parked scene) and what it costs (you now own the consequences).

The other direction is handing a body TO the simulation. A crate can be streamed in parked – dynamic and asleep where it stands, costing nothing until something reaches it – which is what a level chunk arriving mid-game wants instead of paying to fall into place.

A pile sleeps as a whole and wakes as a whole. Pull the bottom crate out of a stack and every crate it was holding up wakes in the same step, not just the one directly above it: the crate above is falling by the time the next collision pass runs, so a wake that reached one level would leave the rest of the stack asleep in mid-air. The HUD’s drift readout is the other side of that bargain – while a crate is asleep its pose does not change at all, which is what makes a settled scene free rather than merely quiet.

Shows:

  • PhysicsBody3D.is_sleeping, wake() and sleep(): the read-back and the two explicit controls.

  • can_sleep: a body that must stay simulated however long it rests.

  • Re-shaping a support wakes the bodies resting on it, through the node API (assigning CollisionShape3D.shape).

  • The wake=False opt-out, which lives on the world seam rather than on the node: reached through the public body.world / body.handle pair.

  • The parked hand-over: a body freed into DYNAMIC and put straight back to sleep, which is what the seam does for a mode flip made with wake=False.

  • Island-atomic wake: one crate taken out from under a stack wakes all of it.

  • Asleep is frozen: the HUD reports how far any sleeping crate has moved since it fell asleep, which stays at zero.

  • Capability.SLEEP: a backend that does not advertise it never sleeps anything, so the HUD says so instead of showing a count that cannot move.

Controls: 1 - shrink the pillar (default): the crate on top wakes and falls 2 - slide the pillar away with wake=False: the crate stays asleep 3 - slide the pillar away normally: the crate wakes and falls 4 - stream a crate in parked: it hangs where it lands until it is woken 5 - yank the bottom crate out of a stack: the whole stack wakes W / S - wake / sleep the whole pile C - toggle can_sleep on the pile Arrows - orbit the camera R - rebuild the scene Escape - quit

Run: uv run python examples/features/physics/sleeping.py Headless self-check: uv run python examples/features/physics/sleeping.py –test

Source¶

  1"""Sleeping Bodies: what wakes a settled pile, and how to edit one without waking it.
  2
  3A pile of crates settles on the ground and falls asleep; a sleeping body is
  4skipped by integration and the contact solve until something disturbs it, which
  5is what keeps a large resting scene cheap. Bodies are tinted by their live sleep
  6state, so the pile visibly dims as it settles and lights up the moment anything
  7reaches it.
  8
  9The interesting half is what counts as a disturbance. Changing a body wakes what
 10it was holding up: shrink the pillar under the crate on top and the crate falls,
 11because the seam wakes its riders rather than waiting for a contact that has
 12already gone. The same edit with ``wake=False`` moves the pillar and leaves the
 13crate hanging in mid-air, which is the picture of what suppression buys (level
 14streaming and pooled respawns do not re-simulate a parked scene) and what it
 15costs (you now own the consequences).
 16
 17The other direction is handing a body TO the simulation. A crate can be streamed
 18in parked -- dynamic and asleep where it stands, costing nothing until something
 19reaches it -- which is what a level chunk arriving mid-game wants instead of
 20paying to fall into place.
 21
 22A pile sleeps as a whole and wakes as a whole. Pull the bottom crate out of a
 23stack and every crate it was holding up wakes in the same step, not just the one
 24directly above it: the crate above is falling by the time the next collision pass
 25runs, so a wake that reached one level would leave the rest of the stack asleep in
 26mid-air. The HUD's drift readout is the other side of that bargain -- while a
 27crate is asleep its pose does not change at all, which is what makes a settled
 28scene free rather than merely quiet.
 29
 30Shows:
 31  - ``PhysicsBody3D.is_sleeping``, ``wake()`` and ``sleep()``: the read-back and
 32    the two explicit controls.
 33  - ``can_sleep``: a body that must stay simulated however long it rests.
 34  - Re-shaping a support wakes the bodies resting on it, through the node API
 35    (assigning ``CollisionShape3D.shape``).
 36  - The ``wake=False`` opt-out, which lives on the world seam rather than on the
 37    node: reached through the public ``body.world`` / ``body.handle`` pair.
 38  - The parked hand-over: a body freed into ``DYNAMIC`` and put straight back to
 39    sleep, which is what the seam does for a mode flip made with ``wake=False``.
 40  - Island-atomic wake: one crate taken out from under a stack wakes all of it.
 41  - Asleep is frozen: the HUD reports how far any sleeping crate has moved since
 42    it fell asleep, which stays at zero.
 43  - ``Capability.SLEEP``: a backend that does not advertise it never sleeps
 44    anything, so the HUD says so instead of showing a count that cannot move.
 45
 46Controls:
 47    1       - shrink the pillar (default): the crate on top wakes and falls
 48    2       - slide the pillar away with wake=False: the crate stays asleep
 49    3       - slide the pillar away normally: the crate wakes and falls
 50    4       - stream a crate in parked: it hangs where it lands until it is woken
 51    5       - yank the bottom crate out of a stack: the whole stack wakes
 52    W / S   - wake / sleep the whole pile
 53    C       - toggle can_sleep on the pile
 54    Arrows  - orbit the camera
 55    R       - rebuild the scene
 56    Escape  - quit
 57
 58Run: uv run python examples/features/physics/sleeping.py
 59Headless self-check: uv run python examples/features/physics/sleeping.py --test
 60
 61# /// simvx
 62# tags = ["3d", "physics", "sleeping"]
 63# ///
 64"""
 65
 66from __future__ import annotations
 67
 68import math
 69
 70from simvx.core import (
 71    BodyMode,
 72    BoxShape3D,
 73    Camera3D,
 74    CollisionShape3D,
 75    DirectionalLight3D,
 76    Input,
 77    InputMap,
 78    Key,
 79    Material,
 80    Mesh,
 81    MeshInstance3D,
 82    Node,
 83    PhysicsBody3D,
 84    Text2D,
 85    Vec3,
 86)
 87from simvx.core.physics import Capability
 88from simvx.graphics import App
 89
 90#: Where the pillar stands, and how tall it is to begin with.
 91_PILLAR_X = 4.0
 92_PILLAR_HALF_HEIGHT = 2.0
 93_SHRUNK_HALF_HEIGHT = 0.5
 94#: How far along X the pillar is slid to take it out from under the crate: clear of
 95#: it by a wide margin, and still in shot so you can see where it went.
 96_SLIDE_DISTANCE = 8.0
 97#: The crate that rides the pillar starts just above its top face, and settles on it.
 98_RIDER_Y = _PILLAR_HALF_HEIGHT * 2 + 0.55
 99_RIDER_REST_Y = _PILLAR_HALF_HEIGHT * 2 + 0.5
100#: Where a streamed-in crate is handed over, well clear of the pile and the pillar.
101_STREAM_X = -0.5
102_STREAM_Y = 7.0
103#: How far forward the yanked crate is pulled: clear of the stack it was under,
104#: and still in shot next to it.
105_YANK_Z = -6.0
106
107_ASLEEP = Material(colour=(0.30, 0.34, 0.42, 1.0), roughness=0.9, metallic=0.0)
108_AWAKE = Material(colour=(0.95, 0.72, 0.25, 1.0), emissive_colour=(0.7, 0.45, 0.05, 0.8), roughness=0.4)
109#: A body that may never sleep is always drawn awake; a distinct tint says why.
110_NEVER_SLEEPS = Material(colour=(0.35, 0.80, 0.95, 1.0), emissive_colour=(0.1, 0.4, 0.5, 0.6), roughness=0.4)
111
112
113class SleepingScene(Node):
114    def on_ready(self):
115        InputMap.add_action("shrink_pillar", [Key.KEY_1])
116        InputMap.add_action("slide_quietly", [Key.KEY_2])
117        InputMap.add_action("slide_loudly", [Key.KEY_3])
118        InputMap.add_action("stream_in", [Key.KEY_4])
119        InputMap.add_action("yank_bottom", [Key.KEY_5])
120        InputMap.add_action("wake_all", [Key.W])
121        InputMap.add_action("sleep_all", [Key.S])
122        InputMap.add_action("toggle_can_sleep", [Key.C])
123        InputMap.add_action("orbit_left", [Key.LEFT])
124        InputMap.add_action("orbit_right", [Key.RIGHT])
125        InputMap.add_action("rebuild", [Key.R])
126        InputMap.add_action("quit", [Key.ESCAPE])
127
128        self._cam_angle = 0.7
129        self._cam = self.add_child(Camera3D())
130        self._update_camera()
131
132        sun = DirectionalLight3D(position=(6, 12, 8))
133        sun.colour = (1.0, 0.96, 0.85)
134        sun.intensity = 3.0
135        sun.look_at((0, 0, 0))
136        self.add_child(sun)
137
138        self._cube = Mesh.cube()
139        self._bodies: list[PhysicsBody3D] = []
140        self._stacks: list[list[PhysicsBody3D]] = []
141        self._visuals: dict[PhysicsBody3D, MeshInstance3D] = {}
142        # Where each crate was when it last fell asleep, so the HUD can report
143        # that a sleeper's pose really does stop changing.
144        self._asleep_at: dict[PhysicsBody3D, tuple[float, float, float]] = {}
145        self._ground: PhysicsBody3D | None = None
146        self._pillar: PhysicsBody3D | None = None
147        self._pillar_shape: CollisionShape3D | None = None
148        self._pillar_mesh: MeshInstance3D | None = None
149        self._rider: PhysicsBody3D | None = None
150        self._can_sleep = True
151        self._streamed = 0
152        self._build()
153
154        self._hud = Text2D(
155            text=(
156                "1 shrink pillar | 2 slide it (wake=False) | 3 slide it | 4 stream one in parked | "
157                "5 yank a stack's bottom crate | W/S wake/sleep | C can_sleep | R reset"
158            ),
159            position=(10, 10),
160            font_scale=1.3,
161        )
162        self.add_child(self._hud)
163        self._status = self.add_child(Text2D(text="", position=(10, 34), font_scale=1.1))
164        self._note = self.add_child(Text2D(text="", position=(10, 58), font_scale=1.0))
165
166    # -- scene construction -------------------------------------------------
167
168    def _crate(self, position, *, can_sleep: bool = True, mode: BodyMode = BodyMode.DYNAMIC) -> PhysicsBody3D:
169        body = PhysicsBody3D(mode=mode, position=position, mass=1.0, can_sleep=can_sleep)
170        body.add_child(CollisionShape3D(shape=BoxShape3D(half_extents=Vec3(0.5, 0.5, 0.5))))
171        # Kept beside the body rather than looked up each frame: the tint is
172        # rewritten whenever the sleep state changes.
173        self._visuals[body] = body.add_child(MeshInstance3D(mesh=self._cube, material=_AWAKE))
174        self.add_child(body)
175        self._bodies.append(body)
176        return body
177
178    def _build(self):
179        for body in self._bodies:
180            body.destroy()
181        self._bodies = []
182        self._stacks = []
183        self._visuals = {}
184        self._asleep_at = {}
185        self._streamed = 0
186        if self._pillar is not None:
187            self._pillar.destroy()
188        if self._ground is not None:
189            self._ground.destroy()
190
191        self._ground = PhysicsBody3D(mode=BodyMode.STATIC, position=(0, -0.5, 0))
192        self._ground.add_child(CollisionShape3D(shape=BoxShape3D(half_extents=Vec3(14, 0.5, 14))))
193        self._ground.add_child(
194            MeshInstance3D(
195                mesh=self._cube,
196                material=Material(colour=(0.22, 0.24, 0.28, 1.0), roughness=0.95),
197                scale=(28, 1, 28),
198            )
199        )
200        self.add_child(self._ground)
201
202        # A STATIC pillar with one crate settled on top: the support whose edits
203        # the number keys drive.
204        self._pillar = PhysicsBody3D(mode=BodyMode.STATIC, position=(_PILLAR_X, _PILLAR_HALF_HEIGHT, 0))
205        self._pillar_shape = CollisionShape3D(shape=BoxShape3D(half_extents=Vec3(1.0, _PILLAR_HALF_HEIGHT, 1.0)))
206        self._pillar.add_child(self._pillar_shape)
207        # Kept, not anonymous: shrinking the collider has to shrink the box the
208        # player can see, or the crate falls through a pillar that still looks solid.
209        self._pillar_mesh = self._pillar.add_child(
210            MeshInstance3D(
211                mesh=self._cube,
212                material=Material(colour=(0.45, 0.42, 0.55, 1.0), roughness=0.8),
213                scale=(2, _PILLAR_HALF_HEIGHT * 2, 2),
214            )
215        )
216        self.add_child(self._pillar)
217        self._rider = self._crate((_PILLAR_X, _RIDER_Y, 0), can_sleep=self._can_sleep)
218
219        # And a loose pile on the ground, so the sleep count has something to say.
220        # Three short stacks: enough for the sleep count to move visibly as they
221        # settle, few enough that each crate stays readable. Kept as stacks, not
222        # just as a list, because pulling one crate out from under its own stack
223        # is what shows the wake reaching all of it.
224        for column in range(3):
225            x = (column - 1) * 1.4 - 3.0
226            self._stacks.append(
227                [self._crate((x, 0.55 + level * 1.02, 0.0), can_sleep=self._can_sleep) for level in range(3)]
228            )
229
230    def _update_camera(self):
231        d, height = 20.0, 8.0
232        self._cam.position = (d * math.sin(self._cam_angle), height, d * math.cos(self._cam_angle))
233        self._cam.look_at((0, 2.0, 0), up=(0, 1, 0))
234
235    # -- the four disturbances the keys drive -------------------------------
236
237    def _shrink_pillar(self):
238        """Assigning a new shape wakes the bodies the old geometry was holding up."""
239        self._pillar_shape.shape = BoxShape3D(half_extents=Vec3(1.0, _SHRUNK_HALF_HEIGHT, 1.0))
240        self._pillar_mesh.scale = (2, _SHRUNK_HALF_HEIGHT * 2, 2)
241        self._note.text = "pillar shrank: the crate on top woke and is falling"
242
243    def _slide_pillar(self, *, wake: bool):
244        """Move the pillar clear, with the wake either honoured or suppressed.
245
246        ``wake=`` is a seam argument rather than a node property, so this reaches
247        the world through the body's own public ``world`` / ``handle`` pair. Writing
248        ``pillar.position`` instead would always wake, which is the right default
249        for game code and the wrong one for streaming.
250        """
251        pose = (Vec3(_PILLAR_X + _SLIDE_DISTANCE, _PILLAR_HALF_HEIGHT, 0.0), self._pillar.world_rotation)
252        self._pillar.world.set_body_transform(self._pillar.handle, pose, wake=wake)
253        # The pose went to the body, so the node has not moved and the mesh hanging
254        # off it has not either. Offsetting the mesh in its parent's space puts the
255        # drawn pillar back on its collider without touching the node's position.
256        self._pillar_mesh.position = (_SLIDE_DISTANCE, 0.0, 0.0)
257        if wake:
258            self._note.text = "pillar slid away: the crate woke and is falling"
259        else:
260            self._note.text = "pillar slid away with wake=False: the crate is still asleep, in mid-air"
261
262    def _stream_in_crate(self):
263        """Hand a crate to the simulation parked, the way a level chunk arrives.
264
265        The node form of the seam's ``set_body_mode(mode, wake=False)``: freeing the
266        body wakes it, as every write does, so it is put straight back to sleep
267        before another step can run. It then hangs exactly where it was placed,
268        costing nothing, until W (or anything reaching it) wakes it.
269        """
270        crate = self._crate((_STREAM_X + self._streamed * 1.3, _STREAM_Y, 0.0), mode=BodyMode.STATIC)
271        self._streamed += 1
272        crate.mode = BodyMode.DYNAMIC
273        crate.sleep()
274        self._note.text = "streamed a crate in parked: dynamic, asleep, and staying put until something wakes it"
275
276    def _yank_bottom_crate(self) -> list[PhysicsBody3D]:
277        """Pull the bottom crate out of a settled stack and wake all of it.
278
279        Sleep latches per contact island and the wake follows the same island, so
280        the crates above the one that left wake in the step it happens. Waking
281        only what the moved crate was recorded as touching would reach one level:
282        that crate is falling before the next collision pass, and the rest of the
283        stack would be left asleep in mid-air with nothing under it.
284
285        Returns the stack, bottom crate first, so a caller can check who woke.
286        """
287        stack = self._stacks[0]
288        bottom = stack[0]
289        bottom.position = (bottom.position[0], 0.55, _YANK_Z)
290        self._note.text = "yanked the bottom crate out: the whole stack woke, not only the crate above it"
291        return stack
292
293    def _sleeping_pose_drift(self) -> float:
294        """How far any crate has moved since it fell asleep, in metres.
295
296        Asleep means out of the step entirely: not integrated, not solved, and not
297        shifted by the contact position pass either. A crate that is awake has no
298        frozen pose to compare against and drops out of the reckoning.
299        """
300        drift = 0.0
301        for body in self._bodies:
302            if not body.is_sleeping:
303                self._asleep_at.pop(body, None)
304                continue
305            here = tuple(float(v) for v in body.position)
306            drift = max(drift, math.dist(here, self._asleep_at.setdefault(body, here)))
307        return drift
308
309    def _set_can_sleep(self, enabled: bool):
310        self._can_sleep = enabled
311        for body in self._bodies:
312            body.can_sleep = enabled
313        self._note.text = f"can_sleep = {enabled} on every crate"
314
315    # -- per-frame ----------------------------------------------------------
316
317    def on_update(self, dt):
318        if Input.is_action_just_pressed("quit"):
319            self.app.quit()
320            return
321        if Input.is_action_just_pressed("rebuild"):
322            self._build()
323            self._note.text = "rebuilt"
324        if Input.is_action_just_pressed("shrink_pillar"):
325            self._shrink_pillar()
326        if Input.is_action_just_pressed("slide_quietly"):
327            self._slide_pillar(wake=False)
328        if Input.is_action_just_pressed("slide_loudly"):
329            self._slide_pillar(wake=True)
330        if Input.is_action_just_pressed("stream_in"):
331            self._stream_in_crate()
332        if Input.is_action_just_pressed("yank_bottom"):
333            self._yank_bottom_crate()
334        if Input.is_action_just_pressed("wake_all"):
335            for body in self._bodies:
336                body.wake()
337            self._note.text = "woke every crate by hand"
338        if Input.is_action_just_pressed("sleep_all"):
339            for body in self._bodies:
340                body.sleep()
341            self._note.text = "parked every crate by hand (a no-op where can_sleep is False)"
342        if Input.is_action_just_pressed("toggle_can_sleep"):
343            self._set_can_sleep(not self._can_sleep)
344
345        self._cam_angle += Input.get_axis("orbit_left", "orbit_right") * 1.5 * dt
346        self._update_camera()
347
348        for body in self._bodies:
349            visual = self._visuals[body]
350            if not body.can_sleep:
351                visual.material = _NEVER_SLEEPS
352            else:
353                visual.material = _ASLEEP if body.is_sleeping else _AWAKE
354
355        asleep = sum(1 for b in self._bodies if b.is_sleeping)
356        world = self._bodies[0].world if self._bodies else None
357        if world is not None and Capability.SLEEP not in world.capabilities():
358            self._status.text = (
359                "this backend does not advertise Capability.SLEEP: nothing here ever sleeps "
360                f"({asleep}/{len(self._bodies)})"
361            )
362        else:
363            rider = "asleep" if self._rider.is_sleeping else "awake"
364            self._status.text = (
365                f"asleep: {asleep}/{len(self._bodies)} | rider on the pillar: {rider} | "
366                f"can_sleep: {self._can_sleep} | sleeper drift: {self._sleeping_pose_drift() * 1000:.2f} mm"
367            )
368
369
370def _selftest() -> bool:
371    """Headless: settle, then drive both directions of the wake gate and check them."""
372    from simvx.graphics.testing import assert_not_blank, save_png
373
374    app = App(title="Sleeping", width=1280, height=720, visible=False)
375    scene = SleepingScene(name="SleepingScene")
376    frames = app.run_headless(scene, frames=400, capture_frames=[399])
377    assert_not_blank(frames[0])
378    save_png(frames[0], "/tmp/physics_sleeping_test.png")
379
380    settled = sum(1 for b in scene._bodies if b.is_sleeping)
381    rider, world = scene._rider, scene._rider.world
382    rider_y = float(world.body_transform(rider.handle)[0][1])
383    print(f"settled asleep: {settled}/{len(scene._bodies)} ; rider y={rider_y:.3f}")
384
385    # The rest is driven straight on the world: a second run_headless would rebuild
386    # the scene, and nothing below needs a rendered frame.
387    def advance(steps: int) -> float:
388        for _ in range(steps):
389            world.step(1.0 / 60.0)
390            world.drain_contact_events()
391            world.drain_overlap_events()
392        return float(world.body_transform(rider.handle)[0][1])
393
394    def height(body) -> float:
395        return float(world.body_transform(body.handle)[0][1])
396
397    # Asleep is frozen: a settled pile's pose stops changing altogether, rather
398    # than drifting on under a position pass that still moves it.
399    frozen = {b: tuple(float(v) for v in world.body_transform(b.handle)[0]) for b in scene._bodies if b.is_sleeping}
400    advance(300)
401    drift = max(
402        (math.dist(tuple(float(v) for v in world.body_transform(b.handle)[0]), pose) for b, pose in frozen.items()),
403        default=1.0,
404    )
405    print(f"largest pose change over 300 steps asleep: {drift:.6f}")
406
407    # Island-atomic wake: the crates above the one pulled out wake in the same
408    # step, and the stack ends up on the ground rather than hanging in the air.
409    stack = scene._yank_bottom_crate()
410    still_asleep = [b.is_sleeping for b in stack[1:]]
411    print(f"after yanking the bottom crate, the crates above it asleep={still_asleep}")
412    advance(300)
413    stack_rest = [height(b) for b in stack[1:]]
414    print("the stack fell to " + ", ".join(f"{y:.3f}" for y in stack_rest))
415
416    # wake=False: the pillar leaves and the rider stays exactly where it was.
417    scene._slide_pillar(wake=False)
418    quiet_asleep = rider.is_sleeping
419    stranded_y = advance(60)
420    print(f"after wake=False slide: rider asleep={quiet_asleep} y={stranded_y:.3f}")
421
422    # And the default: the rider wakes and falls to the ground.
423    rider.wake()
424    fallen_y = advance(300)
425    print(f"after waking it by hand: rider y={fallen_y:.3f} asleep={rider.is_sleeping}")
426
427    # The other direction: a crate handed over parked stays where it was placed.
428    scene._stream_in_crate()
429    streamed = scene._bodies[-1]
430    parked_asleep = streamed.is_sleeping
431    for _ in range(60):
432        world.step(1.0 / 60.0)
433    parked_y = float(world.body_transform(streamed.handle)[0][1])
434    print(f"streamed in parked: asleep={parked_asleep} y={parked_y:.3f}")
435    streamed.wake()
436    for _ in range(300):
437        world.step(1.0 / 60.0)
438    dropped_y = float(world.body_transform(streamed.handle)[0][1])
439    print(f"after waking the streamed crate: y={dropped_y:.3f}")
440    print("screenshot: /tmp/physics_sleeping_test.png")
441
442    ok = (
443        settled == len(scene._bodies) - 1  # every crate but the one streamed in after the settle
444        and abs(rider_y - _RIDER_REST_Y) < 0.1
445        and drift < 1e-6  # asleep is frozen, not merely slow
446        and not any(still_asleep)  # the whole island woke, not just the crate above
447        and all(y < 1.6 for y in stack_rest)  # and it came down rather than hanging
448        and quiet_asleep
449        and abs(stranded_y - rider_y) < 0.01  # suppressed: it did not move at all
450        and fallen_y < 1.0  # woken: it fell to the ground
451        and parked_asleep
452        and abs(parked_y - _STREAM_Y) < 0.01  # parked: out of the step entirely
453        and dropped_y < 1.0  # and running once woken
454    )
455    print("SELFTEST:", "PASS" if ok else "FAIL")
456    return ok
457
458
459if __name__ == "__main__":
460    import sys
461
462    if "--test" in sys.argv:
463        sys.exit(0 if _selftest() else 1)
464    app = App(title="Sleeping Bodies", width=1280, height=720)
465    app.run(SleepingScene())