sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

Files changed (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
sonolus/script/quad.py ADDED
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol, Self
4
+
5
+ from sonolus.script.record import Record
6
+ from sonolus.script.vec import Vec2
7
+
8
+
9
+ class Quad(Record):
10
+ """A quad defined by its four corners.
11
+
12
+ Usage:
13
+ ```
14
+ Quad(bl: Vec2, tl: Vec2, tr: Vec2, br: Vec2)
15
+ ```
16
+ """
17
+
18
+ bl: Vec2
19
+ """The bottom-left corner of the quad."""
20
+
21
+ tl: Vec2
22
+ """The top-left corner of the quad."""
23
+
24
+ tr: Vec2
25
+ """The top-right corner of the quad."""
26
+
27
+ br: Vec2
28
+ """The bottom-right corner of the quad."""
29
+
30
+ @property
31
+ def center(self) -> Vec2:
32
+ """The center of the quad."""
33
+ return (self.bl + self.tr + self.tl + self.br) / 4
34
+
35
+ def translate(self, translation: Vec2, /) -> Self:
36
+ """Translate the quad by the given translation and return a new quad."""
37
+ return Quad(
38
+ bl=self.bl + translation,
39
+ tl=self.tl + translation,
40
+ tr=self.tr + translation,
41
+ br=self.br + translation,
42
+ )
43
+
44
+ def scale(self, factor: Vec2, /) -> Self:
45
+ """Scale the quad by the given factor about the origin and return a new quad."""
46
+ return Quad(
47
+ bl=self.bl * factor,
48
+ tl=self.tl * factor,
49
+ tr=self.tr * factor,
50
+ br=self.br * factor,
51
+ )
52
+
53
+ def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Self:
54
+ """Scale the quad by the given factor about the given pivot and return a new quad."""
55
+ return Quad(
56
+ bl=(self.bl - pivot) * factor + pivot,
57
+ tl=(self.tl - pivot) * factor + pivot,
58
+ tr=(self.tr - pivot) * factor + pivot,
59
+ br=(self.br - pivot) * factor + pivot,
60
+ )
61
+
62
+ def scale_centered(self, factor: Vec2, /) -> Self:
63
+ """Scale the quad by the given factor about its center and return a new quad."""
64
+ return Quad(
65
+ bl=self.bl * factor,
66
+ tl=self.tl * factor,
67
+ tr=self.tr * factor,
68
+ br=self.br * factor,
69
+ ).translate(self.center * (Vec2(1, 1) - factor))
70
+
71
+ def rotate(self, angle: float, /) -> Self:
72
+ """Rotate the quad by the given angle about the origin and return a new quad."""
73
+ return Quad(
74
+ bl=self.bl.rotate(angle),
75
+ tl=self.tl.rotate(angle),
76
+ tr=self.tr.rotate(angle),
77
+ br=self.br.rotate(angle),
78
+ )
79
+
80
+ def rotate_about(
81
+ self,
82
+ angle: float,
83
+ /,
84
+ pivot: Vec2,
85
+ ) -> Self:
86
+ """Rotate the quad by the given angle about the given pivot and return a new quad."""
87
+ return Quad(
88
+ bl=self.bl.rotate_about(angle, pivot),
89
+ tl=self.tl.rotate_about(angle, pivot),
90
+ tr=self.tr.rotate_about(angle, pivot),
91
+ br=self.br.rotate_about(angle, pivot),
92
+ )
93
+
94
+ def rotate_centered(self, angle: float, /) -> Self:
95
+ """Rotate the quad by the given angle about its center and return a new quad."""
96
+ return self.rotate_about(angle, self.center)
97
+
98
+
99
+ class Rect(Record):
100
+ """A rectangle defined by its top, right, bottom, and left edges.
101
+
102
+ Usage:
103
+ ```
104
+ Rect(t: float, r: float, b: float, l: float)
105
+ ```
106
+ """
107
+
108
+ t: float
109
+ """The top edge of the rectangle."""
110
+
111
+ r: float
112
+ """The right edge of the rectangle."""
113
+
114
+ b: float
115
+ """The bottom edge of the rectangle."""
116
+
117
+ l: float # noqa: E741
118
+ """The left edge of the rectangle."""
119
+
120
+ @classmethod
121
+ def from_center(cls, center: Vec2, dimensions: Vec2) -> Rect:
122
+ """Create a rectangle from its center and dimensions."""
123
+ return cls(
124
+ t=center.y + dimensions.y / 2,
125
+ r=center.x + dimensions.x / 2,
126
+ b=center.y - dimensions.y / 2,
127
+ l=center.x - dimensions.x / 2,
128
+ )
129
+
130
+ @property
131
+ def w(self) -> float:
132
+ """The width of the rectangle."""
133
+ return self.r - self.l
134
+
135
+ @property
136
+ def h(self) -> float:
137
+ """The height of the rectangle."""
138
+ return self.t - self.b
139
+
140
+ @property
141
+ def bl(self) -> Vec2:
142
+ """The bottom-left corner of the rectangle."""
143
+ return Vec2(self.l, self.b)
144
+
145
+ @property
146
+ def tl(self) -> Vec2:
147
+ """The top-left corner of the rectangle."""
148
+ return Vec2(self.l, self.t)
149
+
150
+ @property
151
+ def tr(self) -> Vec2:
152
+ """The top-right corner of the rectangle."""
153
+ return Vec2(self.r, self.t)
154
+
155
+ @property
156
+ def br(self) -> Vec2:
157
+ """The bottom-right corner of the rectangle."""
158
+ return Vec2(self.r, self.b)
159
+
160
+ @property
161
+ def center(self) -> Vec2:
162
+ """The center of the rectangle."""
163
+ return Vec2((self.l + self.r) / 2, (self.t + self.b) / 2)
164
+
165
+ def as_quad(self) -> Quad:
166
+ """Convert the rectangle to a quad."""
167
+ return Quad(
168
+ bl=self.bl,
169
+ tl=self.tl,
170
+ tr=self.tr,
171
+ br=self.br,
172
+ )
173
+
174
+ def translate(self, translation: Vec2, /) -> Self:
175
+ """Translate the rectangle by the given translation and return a new rectangle."""
176
+ return Rect(
177
+ t=self.t + translation.y,
178
+ r=self.r + translation.x,
179
+ b=self.b + translation.y,
180
+ l=self.l + translation.x,
181
+ )
182
+
183
+ def scale(self, factor: Vec2, /) -> Self:
184
+ """Scale the rectangle by the given factor about the origin and return a new rectangle."""
185
+ return Rect(
186
+ t=self.t * factor.y,
187
+ r=self.r * factor.x,
188
+ b=self.b * factor.y,
189
+ l=self.l * factor.x,
190
+ )
191
+
192
+ def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Self:
193
+ """Scale the rectangle by the given factor about the given pivot and return a new rectangle."""
194
+ return Rect(
195
+ t=(self.t - pivot.y) * factor.y + pivot.y,
196
+ r=(self.r - pivot.x) * factor.x + pivot.x,
197
+ b=(self.b - pivot.y) * factor.y + pivot.y,
198
+ l=(self.l - pivot.x) * factor.x + pivot.x,
199
+ )
200
+
201
+ def scale_centered(self, factor: Vec2, /) -> Self:
202
+ """Scale the rectangle by the given factor about its center and return a new rectangle."""
203
+ return Rect(
204
+ t=self.t * factor.y,
205
+ r=self.r * factor.x,
206
+ b=self.b * factor.y,
207
+ l=self.l * factor.x,
208
+ ).translate(self.center * (Vec2(1, 1) - factor))
209
+
210
+ def expand(self, expansion: Vec2, /) -> Self:
211
+ """Expand the rectangle by the given amount and return a new rectangle."""
212
+ return Rect(
213
+ t=self.t + expansion.y,
214
+ r=self.r + expansion.x,
215
+ b=self.b - expansion.y,
216
+ l=self.l - expansion.x,
217
+ )
218
+
219
+ def shrink(self, shrinkage: Vec2, /) -> Self:
220
+ """Shrink the rectangle by the given amount and return a new rectangle."""
221
+ return Rect(
222
+ t=self.t - shrinkage.y,
223
+ r=self.r - shrinkage.x,
224
+ b=self.b + shrinkage.y,
225
+ l=self.l + shrinkage.x,
226
+ )
227
+
228
+
229
+ class QuadLike(Protocol):
230
+ """A protocol for types that can be used as quads."""
231
+
232
+ @property
233
+ def bl(self) -> Vec2:
234
+ """The bottom-left corner of the quad."""
235
+
236
+ @property
237
+ def tl(self) -> Vec2:
238
+ """The top-left corner of the quad."""
239
+
240
+ @property
241
+ def tr(self) -> Vec2:
242
+ """The top-right corner of the quad."""
243
+
244
+ @property
245
+ def br(self) -> Vec2:
246
+ """The bottom-right corner of the quad."""
247
+
248
+
249
+ def flatten_quad(quad: QuadLike) -> tuple[float, float, float, float, float, float, float, float]:
250
+ bl = quad.bl
251
+ tl = quad.tl
252
+ tr = quad.tr
253
+ br = quad.br
254
+ return (
255
+ bl.x,
256
+ bl.y,
257
+ tl.x,
258
+ tl.y,
259
+ tr.x,
260
+ tr.y,
261
+ br.x,
262
+ br.y,
263
+ )
sonolus/script/record.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import inspect
4
4
  from collections.abc import Iterable
5
5
  from inspect import getmro
6
- from typing import Any, ClassVar, Self, dataclass_transform, get_origin
6
+ from typing import Any, ClassVar, Self, TypeVar, dataclass_transform, get_origin
7
7
 
8
8
  from sonolus.backend.place import BlockPlace
9
9
  from sonolus.script.internal.context import ctx
@@ -22,15 +22,33 @@ from sonolus.script.num import Num
22
22
 
23
23
  @dataclass_transform(eq_default=True)
24
24
  class Record(GenericValue):
25
+ """Base class for user-defined data structures.
26
+
27
+ Usage:
28
+ A regular record:
29
+ ```python
30
+ class MyRecord(Record):
31
+ field1: int
32
+ field2: bool
33
+ ```
34
+
35
+ A generic record:
36
+ ```python
37
+ class MyGenericRecord[T, U](Record):
38
+ field1: T
39
+ field2: U
40
+ ```
41
+ """
42
+
25
43
  _value: dict[str, Value]
26
- _fields: ClassVar[list[RecordField] | None] = None
44
+ _fields: ClassVar[list[_RecordField] | None] = None
27
45
  _constructor_signature: ClassVar[inspect.Signature]
28
46
 
29
47
  @classmethod
30
- def _validate__type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
48
+ def _validate_type_args_(cls, args: tuple[Any, ...]) -> tuple[Any, ...]:
31
49
  if cls._fields is None:
32
50
  raise TypeError("Base Record class cannot have type arguments")
33
- return super()._validate__type_args_(args)
51
+ return super()._validate_type_args_(args)
34
52
 
35
53
  def __init_subclass__(cls, **kwargs):
36
54
  super().__init_subclass__()
@@ -43,7 +61,7 @@ class Record(GenericValue):
43
61
  for generic_field in cls._fields:
44
62
  resolved_type = validate_and_resolve_type(generic_field.type, cls._type_vars_to_args_)
45
63
  resolved_type = validate_concrete_type(resolved_type)
46
- field = RecordField(generic_field.name, resolved_type, generic_field.index, offset)
64
+ field = _RecordField(generic_field.name, resolved_type, generic_field.index, offset)
47
65
  fields.append(field)
48
66
  setattr(cls, field.name, field)
49
67
  offset += resolved_type._size_()
@@ -70,7 +88,7 @@ class Record(GenericValue):
70
88
  if hasattr(cls, name):
71
89
  raise TypeError("Default values are not supported for Record fields")
72
90
  type_ = validate_type_spec(hint)
73
- fields.append(RecordField(name, type_, index, offset))
91
+ fields.append(_RecordField(name, type_, index, offset))
74
92
  if isinstance(type_, type) and issubclass(type_, Value) and type_._is_concrete_():
75
93
  offset += type_._size_()
76
94
  setattr(cls, name, fields[-1])
@@ -111,7 +129,7 @@ class Record(GenericValue):
111
129
  values[field.name] = value._get_()
112
130
  for type_param in cls.__type_params__:
113
131
  if type_param not in type_vars:
114
- raise TypeError(f"Type parameter {type_param} is not used")
132
+ raise TypeError(f"Type parameter {type_param} cannot be inferred and must be provided explicitly")
115
133
  type_args = tuple(type_vars[type_param] for type_param in cls.__type_params__)
116
134
  if cls._type_args_ is not None:
117
135
  parameterized = cls
@@ -168,10 +186,10 @@ class Record(GenericValue):
168
186
  iterator = iter(values)
169
187
  return cls(**{field.name: field.type._from_list_(iterator) for field in cls._fields})
170
188
 
171
- def _to_list_(self) -> list[float | BlockPlace]:
189
+ def _to_list_(self, level_refs: dict[Any, int] | None = None) -> list[float | BlockPlace]:
172
190
  result = []
173
191
  for field in self._fields:
174
- result.extend(self._value[field.name]._to_list_())
192
+ result.extend(self._value[field.name]._to_list_(level_refs))
175
193
  return result
176
194
 
177
195
  @classmethod
@@ -194,7 +212,7 @@ class Record(GenericValue):
194
212
  field.__set__(self, field.__get__(value))
195
213
 
196
214
  def _copy_(self) -> Self:
197
- return type(self)(**{field.name: self._value[field.name]._copy_() for field in self._fields})
215
+ return type(self)._raw(**{field.name: self._value[field.name]._copy_() for field in self._fields})
198
216
 
199
217
  @classmethod
200
218
  def _alloc_(cls) -> Self:
@@ -214,7 +232,7 @@ class Record(GenericValue):
214
232
  )
215
233
 
216
234
  @meta_fn
217
- def __eq__(self, other):
235
+ def __eq__(self, other: Any) -> bool:
218
236
  if not isinstance(other, type(self)):
219
237
  return False
220
238
  result: Num = Num._accept_(True)
@@ -223,7 +241,7 @@ class Record(GenericValue):
223
241
  return result
224
242
 
225
243
  @meta_fn
226
- def __ne__(self, other):
244
+ def __ne__(self, other: Any) -> bool:
227
245
  if not isinstance(other, type(self)):
228
246
  return True
229
247
  result: Num = Num._accept_(False)
@@ -232,10 +250,23 @@ class Record(GenericValue):
232
250
  return result
233
251
 
234
252
  def __hash__(self):
235
- raise TypeError("Record is not hashable")
253
+ return hash(tuple(field.__get__(self) for field in self._fields))
254
+
255
+ @classmethod
256
+ @meta_fn
257
+ def type_var_value(cls, var: TypeVar, /) -> Any:
258
+ """Return the value of a type variable.
259
+
260
+ Args:
261
+ var: The type variable to get the value of.
262
+
263
+ Returns:
264
+ The value of the type variable.
265
+ """
266
+ return super().type_var_value(var)
236
267
 
237
268
 
238
- class RecordField(SonolusDescriptor):
269
+ class _RecordField(SonolusDescriptor):
239
270
  def __init__(self, name: str, type_: type[Value], index: int, offset: int):
240
271
  self.name = name
241
272
  self.type = type_
@@ -259,7 +290,7 @@ class RecordField(SonolusDescriptor):
259
290
  instance._value[self.name]._copy_from_(value)
260
291
 
261
292
 
262
- ops_to_inplace_ops = {
293
+ _ops_to_inplace_ops = {
263
294
  "__add__": "__iadd__",
264
295
  "__sub__": "__isub__",
265
296
  "__mul__": "__imul__",
@@ -277,7 +308,7 @@ ops_to_inplace_ops = {
277
308
 
278
309
 
279
310
  def _add_inplace_ops(cls):
280
- for op, inplace_op in ops_to_inplace_ops.items():
311
+ for op, inplace_op in _ops_to_inplace_ops.items():
281
312
  if hasattr(cls, op) and not hasattr(cls, inplace_op):
282
313
  setattr(cls, inplace_op, _make_inplace_op(op))
283
314
  return cls
sonolus/script/runtime.py CHANGED
@@ -28,10 +28,10 @@ from sonolus.script.globals import (
28
28
  _watch_runtime_ui_configuration,
29
29
  _watch_runtime_update,
30
30
  )
31
- from sonolus.script.graphics import Quad, Rect
32
31
  from sonolus.script.internal.context import ctx
33
32
  from sonolus.script.internal.impl import meta_fn
34
33
  from sonolus.script.num import Num
34
+ from sonolus.script.quad import Quad, Rect
35
35
  from sonolus.script.record import Record
36
36
  from sonolus.script.transform import Transform2d
37
37
  from sonolus.script.vec import Vec2
@@ -308,6 +308,7 @@ class _SkinTransform:
308
308
  a12=Num(values[1 * 4 + 3]),
309
309
  a20=Num(values[3 * 4 + 0]),
310
310
  a21=Num(values[3 * 4 + 1]),
311
+ a22=Num(values[3 * 4 + 3]),
311
312
  )
312
313
 
313
314
 
@@ -328,6 +329,7 @@ class _ParticleTransform:
328
329
  a12=Num(values[1 * 4 + 3]),
329
330
  a20=Num(values[3 * 4 + 0]),
330
331
  a21=Num(values[3 * 4 + 1]),
332
+ a22=Num(values[3 * 4 + 3]),
331
333
  )
332
334
 
333
335
 
@@ -431,6 +433,7 @@ class _TutorialInstruction:
431
433
 
432
434
  @meta_fn
433
435
  def is_debug() -> bool:
436
+ """Check if the game is running in debug mode."""
434
437
  if not ctx():
435
438
  return False
436
439
  match ctx().global_state.mode:
@@ -448,6 +451,7 @@ def is_debug() -> bool:
448
451
 
449
452
  @meta_fn
450
453
  def aspect_ratio() -> float:
454
+ """Get the aspect ratio of the game."""
451
455
  if not ctx():
452
456
  return 16 / 9
453
457
  match ctx().global_state.mode:
@@ -463,6 +467,10 @@ def aspect_ratio() -> float:
463
467
 
464
468
  @meta_fn
465
469
  def audio_offset() -> float:
470
+ """Get the audio offset of the game.
471
+
472
+ Returns 0 in preview mode.
473
+ """
466
474
  if not ctx():
467
475
  return 0
468
476
  match ctx().global_state.mode:
@@ -478,6 +486,10 @@ def audio_offset() -> float:
478
486
 
479
487
  @meta_fn
480
488
  def input_offset() -> float:
489
+ """Get the input offset of the game.
490
+
491
+ Returns 0 in preview mode and tutorial mode.
492
+ """
481
493
  if not ctx():
482
494
  return 0
483
495
  match ctx().global_state.mode:
@@ -491,6 +503,10 @@ def input_offset() -> float:
491
503
 
492
504
  @meta_fn
493
505
  def is_multiplayer() -> bool:
506
+ """Check if the game is running in multiplayer mode.
507
+
508
+ Returns False if not in play mode.
509
+ """
494
510
  if not ctx():
495
511
  return False
496
512
  match ctx().global_state.mode:
@@ -502,6 +518,10 @@ def is_multiplayer() -> bool:
502
518
 
503
519
  @meta_fn
504
520
  def is_replay() -> bool:
521
+ """Check if the game is running in replay mode.
522
+
523
+ Returns False if not in watch mode.
524
+ """
505
525
  if not ctx():
506
526
  return False
507
527
  match ctx().global_state.mode:
@@ -513,6 +533,10 @@ def is_replay() -> bool:
513
533
 
514
534
  @meta_fn
515
535
  def time() -> float:
536
+ """Get the current time of the game.
537
+
538
+ Returns 0 in preview mode.
539
+ """
516
540
  if not ctx():
517
541
  return 0
518
542
  match ctx().global_state.mode:
@@ -528,6 +552,10 @@ def time() -> float:
528
552
 
529
553
  @meta_fn
530
554
  def delta_time() -> float:
555
+ """Get the time elapsed since the last frame.
556
+
557
+ Returns 0 in preview mode.
558
+ """
531
559
  if not ctx():
532
560
  return 0
533
561
  match ctx().global_state.mode:
@@ -543,6 +571,10 @@ def delta_time() -> float:
543
571
 
544
572
  @meta_fn
545
573
  def scaled_time() -> float:
574
+ """Get the current time of the game affected by the time scale.
575
+
576
+ Returns the unscaled time in tutorial mode and 0 in preview mode.
577
+ """
546
578
  if not ctx():
547
579
  return 0
548
580
  match ctx().global_state.mode:
@@ -558,6 +590,7 @@ def scaled_time() -> float:
558
590
 
559
591
  @meta_fn
560
592
  def touches() -> VarArray[Touch, 999]:
593
+ """Get the current touches of the game."""
561
594
  if not ctx():
562
595
  return VarArray(0, Array[Touch, 0]())
563
596
  match ctx().global_state.mode:
@@ -569,6 +602,10 @@ def touches() -> VarArray[Touch, 999]:
569
602
 
570
603
  @meta_fn
571
604
  def is_skip() -> bool:
605
+ """Check if there was a time skip this frame.
606
+
607
+ Returns False if not in watch mode.
608
+ """
572
609
  if not ctx():
573
610
  return False
574
611
  match ctx().global_state.mode:
@@ -580,6 +617,10 @@ def is_skip() -> bool:
580
617
 
581
618
  @meta_fn
582
619
  def navigation_direction() -> int:
620
+ """Get the navigation direction of the tutorial.
621
+
622
+ Returns 0 if not in tutorial mode.
623
+ """
583
624
  if not ctx():
584
625
  return 0
585
626
  match ctx().global_state.mode:
@@ -590,28 +631,34 @@ def navigation_direction() -> int:
590
631
 
591
632
 
592
633
  def skin_transform() -> Transform2d:
634
+ """Get the global skin transform."""
593
635
  return _SkinTransform.transform
594
636
 
595
637
 
596
638
  @meta_fn
597
639
  def set_skin_transform(value: Transform2d):
640
+ """Set the global skin transform."""
598
641
  _SkinTransform.transform._copy_from_(value)
599
642
 
600
643
 
601
644
  def particle_transform() -> Transform2d:
645
+ """Get the global particle transform."""
602
646
  return _ParticleTransform.transform
603
647
 
604
648
 
605
649
  @meta_fn
606
650
  def set_particle_transform(value: Transform2d):
651
+ """Set the global particle transform."""
607
652
  _ParticleTransform.transform._copy_from_(value)
608
653
 
609
654
 
610
655
  def background() -> Quad:
656
+ """Get the background quad."""
611
657
  return _Background.value
612
658
 
613
659
 
614
660
  def set_background(value: Quad):
661
+ """Set the background quad."""
615
662
  _Background.value = value
616
663
 
617
664
 
@@ -626,16 +673,20 @@ tutorial_ui_configs = _TutorialRuntimeUiConfigs
626
673
 
627
674
 
628
675
  def canvas() -> _PreviewRuntimeCanvas:
676
+ """Get the preview canvas."""
629
677
  return _PreviewRuntimeCanvas
630
678
 
631
679
 
632
680
  def screen() -> Rect:
681
+ """Get the screen boundaries as a rectangle."""
633
682
  return Rect(t=1, r=aspect_ratio(), b=-1, l=-aspect_ratio())
634
683
 
635
684
 
636
685
  def level_score() -> _LevelScore:
686
+ """Get the level score configuration."""
637
687
  return _LevelScore
638
688
 
639
689
 
640
690
  def level_life() -> _LevelLife:
691
+ """Get the level life configuration."""
641
692
  return _LevelLife