sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__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 (79) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/cli.py +14 -3
  21. sonolus/build/compile.py +8 -8
  22. sonolus/build/engine.py +10 -5
  23. sonolus/build/project.py +30 -1
  24. sonolus/script/archetype.py +429 -138
  25. sonolus/script/array.py +25 -8
  26. sonolus/script/array_like.py +297 -0
  27. sonolus/script/bucket.py +73 -11
  28. sonolus/script/containers.py +234 -51
  29. sonolus/script/debug.py +8 -8
  30. sonolus/script/easing.py +147 -105
  31. sonolus/script/effect.py +60 -0
  32. sonolus/script/engine.py +71 -4
  33. sonolus/script/globals.py +66 -32
  34. sonolus/script/instruction.py +79 -25
  35. sonolus/script/internal/builtin_impls.py +138 -27
  36. sonolus/script/internal/constant.py +139 -0
  37. sonolus/script/internal/context.py +14 -5
  38. sonolus/script/internal/dict_impl.py +65 -0
  39. sonolus/script/internal/generic.py +6 -9
  40. sonolus/script/internal/impl.py +38 -13
  41. sonolus/script/internal/introspection.py +5 -2
  42. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  43. sonolus/script/internal/native.py +3 -3
  44. sonolus/script/internal/random.py +67 -0
  45. sonolus/script/internal/range.py +81 -0
  46. sonolus/script/internal/transient.py +51 -0
  47. sonolus/script/internal/tuple_impl.py +113 -0
  48. sonolus/script/interval.py +234 -16
  49. sonolus/script/iterator.py +120 -167
  50. sonolus/script/level.py +24 -0
  51. sonolus/script/num.py +79 -47
  52. sonolus/script/options.py +78 -12
  53. sonolus/script/particle.py +37 -4
  54. sonolus/script/pointer.py +4 -4
  55. sonolus/script/print.py +22 -1
  56. sonolus/script/project.py +59 -0
  57. sonolus/script/{graphics.py → quad.py} +75 -12
  58. sonolus/script/record.py +44 -13
  59. sonolus/script/runtime.py +50 -1
  60. sonolus/script/sprite.py +198 -115
  61. sonolus/script/text.py +2 -0
  62. sonolus/script/timing.py +72 -0
  63. sonolus/script/transform.py +296 -66
  64. sonolus/script/ui.py +134 -78
  65. sonolus/script/values.py +6 -13
  66. sonolus/script/vec.py +118 -3
  67. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
  68. sonolus_py-0.1.6.dist-info/RECORD +89 -0
  69. sonolus/backend/dead_code.py +0 -80
  70. sonolus/backend/optimize.py +0 -37
  71. sonolus/backend/simplify.py +0 -47
  72. sonolus/script/comptime.py +0 -160
  73. sonolus/script/random.py +0 -14
  74. sonolus/script/range.py +0 -58
  75. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  76. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
  78. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
  79. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
sonolus/script/project.py CHANGED
@@ -1,11 +1,23 @@
1
+ from __future__ import annotations
2
+
1
3
  from os import PathLike
2
4
  from pathlib import Path
5
+ from typing import Self, TypedDict
3
6
 
7
+ from sonolus.script.archetype import ArchetypeSchema
4
8
  from sonolus.script.engine import Engine
5
9
  from sonolus.script.level import Level
6
10
 
7
11
 
8
12
  class Project:
13
+ """A Sonolus.py project.
14
+
15
+ Args:
16
+ engine: The engine of the project.
17
+ levels: The levels of the project.
18
+ resources: The path to the resources of the project.
19
+ """
20
+
9
21
  def __init__(
10
22
  self,
11
23
  engine: Engine,
@@ -15,3 +27,50 @@ class Project:
15
27
  self.engine = engine
16
28
  self.levels = levels or []
17
29
  self.resources = Path(resources or "resources")
30
+
31
+ def with_levels(self, levels: list[Level]) -> Self:
32
+ """Create a new project with the specified levels.
33
+
34
+ Args:
35
+ levels: The levels of the project.
36
+
37
+ Returns:
38
+ The new project.
39
+ """
40
+ return Project(self.engine, levels, self.resources)
41
+
42
+ def dev(self, build_dir: PathLike, port: int = 8080):
43
+ """Start a development server for the project.
44
+
45
+ Args:
46
+ build_dir: The path to the build directory.
47
+ port: The port of the development server.
48
+ """
49
+ from sonolus.build.cli import build_collection, run_server
50
+
51
+ build_collection(self, Path(build_dir))
52
+ run_server(Path(build_dir) / "site", port=port)
53
+
54
+ def build(self, build_dir: PathLike):
55
+ """Build the project.
56
+
57
+ Args:
58
+ build_dir: The path to the build directory.
59
+ """
60
+ from sonolus.build.cli import build_project
61
+
62
+ build_project(self, Path(build_dir))
63
+
64
+ def schema(self) -> ProjectSchema:
65
+ """Generate the schema of the project.
66
+
67
+ Returns:
68
+ The schema of the project.
69
+ """
70
+ from sonolus.build.project import get_project_schema
71
+
72
+ return get_project_schema(self)
73
+
74
+
75
+ class ProjectSchema(TypedDict):
76
+ archetypes: list[ArchetypeSchema]
@@ -7,16 +7,33 @@ from sonolus.script.vec import Vec2
7
7
 
8
8
 
9
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
+
10
18
  bl: Vec2
19
+ """The bottom-left corner of the quad."""
20
+
11
21
  tl: Vec2
22
+ """The top-left corner of the quad."""
23
+
12
24
  tr: Vec2
25
+ """The top-right corner of the quad."""
26
+
13
27
  br: Vec2
28
+ """The bottom-right corner of the quad."""
14
29
 
15
30
  @property
16
31
  def center(self) -> Vec2:
32
+ """The center of the quad."""
17
33
  return (self.bl + self.tr + self.tl + self.br) / 4
18
34
 
19
35
  def translate(self, translation: Vec2, /) -> Self:
36
+ """Translate the quad by the given translation and return a new quad."""
20
37
  return Quad(
21
38
  bl=self.bl + translation,
22
39
  tl=self.tl + translation,
@@ -25,6 +42,7 @@ class Quad(Record):
25
42
  )
26
43
 
27
44
  def scale(self, factor: Vec2, /) -> Self:
45
+ """Scale the quad by the given factor about the origin and return a new quad."""
28
46
  return Quad(
29
47
  bl=self.bl * factor,
30
48
  tl=self.tl * factor,
@@ -33,6 +51,7 @@ class Quad(Record):
33
51
  )
34
52
 
35
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."""
36
55
  return Quad(
37
56
  bl=(self.bl - pivot) * factor + pivot,
38
57
  tl=(self.tl - pivot) * factor + pivot,
@@ -41,6 +60,7 @@ class Quad(Record):
41
60
  )
42
61
 
43
62
  def scale_centered(self, factor: Vec2, /) -> Self:
63
+ """Scale the quad by the given factor about its center and return a new quad."""
44
64
  return Quad(
45
65
  bl=self.bl * factor,
46
66
  tl=self.tl * factor,
@@ -49,6 +69,7 @@ class Quad(Record):
49
69
  ).translate(self.center * (Vec2(1, 1) - factor))
50
70
 
51
71
  def rotate(self, angle: float, /) -> Self:
72
+ """Rotate the quad by the given angle about the origin and return a new quad."""
52
73
  return Quad(
53
74
  bl=self.bl.rotate(angle),
54
75
  tl=self.tl.rotate(angle),
@@ -62,6 +83,7 @@ class Quad(Record):
62
83
  /,
63
84
  pivot: Vec2,
64
85
  ) -> Self:
86
+ """Rotate the quad by the given angle about the given pivot and return a new quad."""
65
87
  return Quad(
66
88
  bl=self.bl.rotate_about(angle, pivot),
67
89
  tl=self.tl.rotate_about(angle, pivot),
@@ -70,17 +92,34 @@ class Quad(Record):
70
92
  )
71
93
 
72
94
  def rotate_centered(self, angle: float, /) -> Self:
95
+ """Rotate the quad by the given angle about its center and return a new quad."""
73
96
  return self.rotate_about(angle, self.center)
74
97
 
75
98
 
76
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
+
77
108
  t: float
109
+ """The top edge of the rectangle."""
110
+
78
111
  r: float
112
+ """The right edge of the rectangle."""
113
+
79
114
  b: float
115
+ """The bottom edge of the rectangle."""
116
+
80
117
  l: float # noqa: E741
118
+ """The left edge of the rectangle."""
81
119
 
82
120
  @classmethod
83
121
  def from_center(cls, center: Vec2, dimensions: Vec2) -> Rect:
122
+ """Create a rectangle from its center and dimensions."""
84
123
  return cls(
85
124
  t=center.y + dimensions.y / 2,
86
125
  r=center.x + dimensions.x / 2,
@@ -90,33 +129,41 @@ class Rect(Record):
90
129
 
91
130
  @property
92
131
  def w(self) -> float:
132
+ """The width of the rectangle."""
93
133
  return self.r - self.l
94
134
 
95
135
  @property
96
136
  def h(self) -> float:
137
+ """The height of the rectangle."""
97
138
  return self.t - self.b
98
139
 
99
140
  @property
100
141
  def bl(self) -> Vec2:
142
+ """The bottom-left corner of the rectangle."""
101
143
  return Vec2(self.l, self.b)
102
144
 
103
145
  @property
104
146
  def tl(self) -> Vec2:
147
+ """The top-left corner of the rectangle."""
105
148
  return Vec2(self.l, self.t)
106
149
 
107
150
  @property
108
151
  def tr(self) -> Vec2:
152
+ """The top-right corner of the rectangle."""
109
153
  return Vec2(self.r, self.t)
110
154
 
111
155
  @property
112
156
  def br(self) -> Vec2:
157
+ """The bottom-right corner of the rectangle."""
113
158
  return Vec2(self.r, self.b)
114
159
 
115
160
  @property
116
161
  def center(self) -> Vec2:
162
+ """The center of the rectangle."""
117
163
  return Vec2((self.l + self.r) / 2, (self.t + self.b) / 2)
118
164
 
119
165
  def as_quad(self) -> Quad:
166
+ """Convert the rectangle to a quad."""
120
167
  return Quad(
121
168
  bl=self.bl,
122
169
  tl=self.tl,
@@ -125,6 +172,7 @@ class Rect(Record):
125
172
  )
126
173
 
127
174
  def translate(self, translation: Vec2, /) -> Self:
175
+ """Translate the rectangle by the given translation and return a new rectangle."""
128
176
  return Rect(
129
177
  t=self.t + translation.y,
130
178
  r=self.r + translation.x,
@@ -133,6 +181,7 @@ class Rect(Record):
133
181
  )
134
182
 
135
183
  def scale(self, factor: Vec2, /) -> Self:
184
+ """Scale the rectangle by the given factor about the origin and return a new rectangle."""
136
185
  return Rect(
137
186
  t=self.t * factor.y,
138
187
  r=self.r * factor.x,
@@ -141,6 +190,7 @@ class Rect(Record):
141
190
  )
142
191
 
143
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."""
144
194
  return Rect(
145
195
  t=(self.t - pivot.y) * factor.y + pivot.y,
146
196
  r=(self.r - pivot.x) * factor.x + pivot.x,
@@ -149,6 +199,7 @@ class Rect(Record):
149
199
  )
150
200
 
151
201
  def scale_centered(self, factor: Vec2, /) -> Self:
202
+ """Scale the rectangle by the given factor about its center and return a new rectangle."""
152
203
  return Rect(
153
204
  t=self.t * factor.y,
154
205
  r=self.r * factor.x,
@@ -157,6 +208,7 @@ class Rect(Record):
157
208
  ).translate(self.center * (Vec2(1, 1) - factor))
158
209
 
159
210
  def expand(self, expansion: Vec2, /) -> Self:
211
+ """Expand the rectangle by the given amount and return a new rectangle."""
160
212
  return Rect(
161
213
  t=self.t + expansion.y,
162
214
  r=self.r + expansion.x,
@@ -165,6 +217,7 @@ class Rect(Record):
165
217
  )
166
218
 
167
219
  def shrink(self, shrinkage: Vec2, /) -> Self:
220
+ """Shrink the rectangle by the given amount and return a new rectangle."""
168
221
  return Rect(
169
222
  t=self.t - shrinkage.y,
170
223
  r=self.r - shrinkage.x,
@@ -174,27 +227,37 @@ class Rect(Record):
174
227
 
175
228
 
176
229
  class QuadLike(Protocol):
230
+ """A protocol for types that can be used as quads."""
231
+
177
232
  @property
178
- def bl(self) -> Vec2: ...
233
+ def bl(self) -> Vec2:
234
+ """The bottom-left corner of the quad."""
179
235
 
180
236
  @property
181
- def tl(self) -> Vec2: ...
237
+ def tl(self) -> Vec2:
238
+ """The top-left corner of the quad."""
182
239
 
183
240
  @property
184
- def tr(self) -> Vec2: ...
241
+ def tr(self) -> Vec2:
242
+ """The top-right corner of the quad."""
185
243
 
186
244
  @property
187
- def br(self) -> Vec2: ...
245
+ def br(self) -> Vec2:
246
+ """The bottom-right corner of the quad."""
188
247
 
189
248
 
190
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
191
254
  return (
192
- quad.bl.x,
193
- quad.bl.y,
194
- quad.tl.x,
195
- quad.tl.y,
196
- quad.tr.x,
197
- quad.tr.y,
198
- quad.br.x,
199
- quad.br.y,
255
+ bl.x,
256
+ bl.y,
257
+ tl.x,
258
+ tl.y,
259
+ tr.x,
260
+ tr.y,
261
+ br.x,
262
+ br.y,
200
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])
@@ -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
@@ -433,6 +433,7 @@ class _TutorialInstruction:
433
433
 
434
434
  @meta_fn
435
435
  def is_debug() -> bool:
436
+ """Check if the game is running in debug mode."""
436
437
  if not ctx():
437
438
  return False
438
439
  match ctx().global_state.mode:
@@ -450,6 +451,7 @@ def is_debug() -> bool:
450
451
 
451
452
  @meta_fn
452
453
  def aspect_ratio() -> float:
454
+ """Get the aspect ratio of the game."""
453
455
  if not ctx():
454
456
  return 16 / 9
455
457
  match ctx().global_state.mode:
@@ -465,6 +467,10 @@ def aspect_ratio() -> float:
465
467
 
466
468
  @meta_fn
467
469
  def audio_offset() -> float:
470
+ """Get the audio offset of the game.
471
+
472
+ Returns 0 in preview mode.
473
+ """
468
474
  if not ctx():
469
475
  return 0
470
476
  match ctx().global_state.mode:
@@ -480,6 +486,10 @@ def audio_offset() -> float:
480
486
 
481
487
  @meta_fn
482
488
  def input_offset() -> float:
489
+ """Get the input offset of the game.
490
+
491
+ Returns 0 in preview mode and tutorial mode.
492
+ """
483
493
  if not ctx():
484
494
  return 0
485
495
  match ctx().global_state.mode:
@@ -493,6 +503,10 @@ def input_offset() -> float:
493
503
 
494
504
  @meta_fn
495
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
+ """
496
510
  if not ctx():
497
511
  return False
498
512
  match ctx().global_state.mode:
@@ -504,6 +518,10 @@ def is_multiplayer() -> bool:
504
518
 
505
519
  @meta_fn
506
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
+ """
507
525
  if not ctx():
508
526
  return False
509
527
  match ctx().global_state.mode:
@@ -515,6 +533,10 @@ def is_replay() -> bool:
515
533
 
516
534
  @meta_fn
517
535
  def time() -> float:
536
+ """Get the current time of the game.
537
+
538
+ Returns 0 in preview mode.
539
+ """
518
540
  if not ctx():
519
541
  return 0
520
542
  match ctx().global_state.mode:
@@ -530,6 +552,10 @@ def time() -> float:
530
552
 
531
553
  @meta_fn
532
554
  def delta_time() -> float:
555
+ """Get the time elapsed since the last frame.
556
+
557
+ Returns 0 in preview mode.
558
+ """
533
559
  if not ctx():
534
560
  return 0
535
561
  match ctx().global_state.mode:
@@ -545,6 +571,10 @@ def delta_time() -> float:
545
571
 
546
572
  @meta_fn
547
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
+ """
548
578
  if not ctx():
549
579
  return 0
550
580
  match ctx().global_state.mode:
@@ -560,6 +590,7 @@ def scaled_time() -> float:
560
590
 
561
591
  @meta_fn
562
592
  def touches() -> VarArray[Touch, 999]:
593
+ """Get the current touches of the game."""
563
594
  if not ctx():
564
595
  return VarArray(0, Array[Touch, 0]())
565
596
  match ctx().global_state.mode:
@@ -571,6 +602,10 @@ def touches() -> VarArray[Touch, 999]:
571
602
 
572
603
  @meta_fn
573
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
+ """
574
609
  if not ctx():
575
610
  return False
576
611
  match ctx().global_state.mode:
@@ -582,6 +617,10 @@ def is_skip() -> bool:
582
617
 
583
618
  @meta_fn
584
619
  def navigation_direction() -> int:
620
+ """Get the navigation direction of the tutorial.
621
+
622
+ Returns 0 if not in tutorial mode.
623
+ """
585
624
  if not ctx():
586
625
  return 0
587
626
  match ctx().global_state.mode:
@@ -592,28 +631,34 @@ def navigation_direction() -> int:
592
631
 
593
632
 
594
633
  def skin_transform() -> Transform2d:
634
+ """Get the global skin transform."""
595
635
  return _SkinTransform.transform
596
636
 
597
637
 
598
638
  @meta_fn
599
639
  def set_skin_transform(value: Transform2d):
640
+ """Set the global skin transform."""
600
641
  _SkinTransform.transform._copy_from_(value)
601
642
 
602
643
 
603
644
  def particle_transform() -> Transform2d:
645
+ """Get the global particle transform."""
604
646
  return _ParticleTransform.transform
605
647
 
606
648
 
607
649
  @meta_fn
608
650
  def set_particle_transform(value: Transform2d):
651
+ """Set the global particle transform."""
609
652
  _ParticleTransform.transform._copy_from_(value)
610
653
 
611
654
 
612
655
  def background() -> Quad:
656
+ """Get the background quad."""
613
657
  return _Background.value
614
658
 
615
659
 
616
660
  def set_background(value: Quad):
661
+ """Set the background quad."""
617
662
  _Background.value = value
618
663
 
619
664
 
@@ -628,16 +673,20 @@ tutorial_ui_configs = _TutorialRuntimeUiConfigs
628
673
 
629
674
 
630
675
  def canvas() -> _PreviewRuntimeCanvas:
676
+ """Get the preview canvas."""
631
677
  return _PreviewRuntimeCanvas
632
678
 
633
679
 
634
680
  def screen() -> Rect:
681
+ """Get the screen boundaries as a rectangle."""
635
682
  return Rect(t=1, r=aspect_ratio(), b=-1, l=-aspect_ratio())
636
683
 
637
684
 
638
685
  def level_score() -> _LevelScore:
686
+ """Get the level score configuration."""
639
687
  return _LevelScore
640
688
 
641
689
 
642
690
  def level_life() -> _LevelLife:
691
+ """Get the level life configuration."""
643
692
  return _LevelLife