sonolus.py 0.3.2__tar.gz → 0.3.4__tar.gz

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 (179) hide show
  1. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/PKG-INFO +1 -1
  2. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/doc_stubs/math.pyi +33 -0
  3. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/builtins.md +8 -1
  4. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/cli.md +1 -1
  5. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/constructs.md +3 -1
  6. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/types.md +10 -4
  7. sonolus_py-0.3.4/docs/index.md +27 -0
  8. sonolus_py-0.3.4/docs/overview.md +174 -0
  9. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/mkdocs.yml +1 -0
  10. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/pyproject.toml +1 -1
  11. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/finalize.py +16 -4
  12. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/visitor.py +25 -9
  13. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/archetype.py +40 -22
  14. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/array.py +8 -5
  15. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/array_like.py +44 -19
  16. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/containers.py +24 -4
  17. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/debug.py +4 -4
  18. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/engine.py +2 -2
  19. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/instruction.py +1 -1
  20. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/builtin_impls.py +10 -5
  21. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/context.py +5 -1
  22. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/math_impls.py +17 -0
  23. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/native.py +3 -3
  24. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/range.py +30 -7
  25. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/tuple_impl.py +3 -1
  26. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/value.py +1 -1
  27. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/interval.py +61 -3
  28. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/num.py +13 -13
  29. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/pointer.py +1 -1
  30. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/quad.py +39 -3
  31. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/record.py +7 -0
  32. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/runtime.py +28 -0
  33. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/stream.py +25 -16
  34. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/transform.py +4 -4
  35. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/vec.py +19 -7
  36. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_array.py +56 -0
  37. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_array_map.py +96 -0
  38. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_array_set.py +17 -0
  39. sonolus_py-0.3.4/tests/script/test_interval.py +572 -0
  40. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_operator.py +104 -0
  41. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_range.py +73 -0
  42. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_tuple.py +70 -0
  43. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_var_array.py +98 -7
  44. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_vec.py +20 -0
  45. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/uv.lock +1 -1
  46. sonolus_py-0.3.2/docs/index.md +0 -22
  47. sonolus_py-0.3.2/tests/script/test_interval.py +0 -223
  48. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/.github/workflows/publish.yaml +0 -0
  49. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/.gitignore +0 -0
  50. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/.python-version +0 -0
  51. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/.run/Python tests in tests.run.xml +0 -0
  52. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/LICENSE +0 -0
  53. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/README.md +0 -0
  54. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/doc_stubs/__init__.py +0 -0
  55. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/doc_stubs/builtins.pyi +0 -0
  56. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/doc_stubs/num.pyi +0 -0
  57. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/doc_stubs/random.pyi +0 -0
  58. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/CNAME +0 -0
  59. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/index.md +0 -0
  60. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/project.md +0 -0
  61. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/concepts/resources.md +0 -0
  62. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/builtins.md +0 -0
  63. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/index.md +0 -0
  64. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/math.md +0 -0
  65. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/random.md +0 -0
  66. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.archetype.md +0 -0
  67. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.array.md +0 -0
  68. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.array_like.md +0 -0
  69. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.bucket.md +0 -0
  70. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.containers.md +0 -0
  71. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.debug.md +0 -0
  72. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.easing.md +0 -0
  73. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.effect.md +0 -0
  74. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.engine.md +0 -0
  75. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.globals.md +0 -0
  76. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.instruction.md +0 -0
  77. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.interval.md +0 -0
  78. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.iterator.md +0 -0
  79. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.level.md +0 -0
  80. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.metadata.md +0 -0
  81. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.num.md +0 -0
  82. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.options.md +0 -0
  83. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.particle.md +0 -0
  84. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.printing.md +0 -0
  85. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.project.md +0 -0
  86. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.quad.md +0 -0
  87. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.record.md +0 -0
  88. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.runtime.md +0 -0
  89. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.sprite.md +0 -0
  90. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.stream.md +0 -0
  91. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.text.md +0 -0
  92. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.timing.md +0 -0
  93. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.transform.md +0 -0
  94. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.ui.md +0 -0
  95. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.values.md +0 -0
  96. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/docs/reference/sonolus.script.vec.md +0 -0
  97. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/generate.py +0 -0
  98. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/runtimes/Engine/Tutorial/Blocks.json +0 -0
  99. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/runtimes/Functions.json +0 -0
  100. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/runtimes/Level/Play/Blocks.json +0 -0
  101. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/runtimes/Level/Preview/Blocks.json +0 -0
  102. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/scripts/runtimes/Level/Watch/Blocks.json +0 -0
  103. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/__init__.py +0 -0
  104. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/__init__.py +0 -0
  105. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/blocks.py +0 -0
  106. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/excepthook.py +0 -0
  107. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/interpret.py +0 -0
  108. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/ir.py +0 -0
  109. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/mode.py +0 -0
  110. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/node.py +0 -0
  111. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/ops.py +0 -0
  112. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/__init__.py +0 -0
  113. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/allocate.py +0 -0
  114. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/constant_evaluation.py +0 -0
  115. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/copy_coalesce.py +0 -0
  116. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/dead_code.py +0 -0
  117. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/dominance.py +0 -0
  118. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/flow.py +0 -0
  119. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/inlining.py +0 -0
  120. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/liveness.py +0 -0
  121. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/optimize.py +0 -0
  122. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/passes.py +0 -0
  123. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/simplify.py +0 -0
  124. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/optimize/ssa.py +0 -0
  125. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/place.py +0 -0
  126. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/backend/utils.py +0 -0
  127. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/__init__.py +0 -0
  128. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/cli.py +0 -0
  129. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/collection.py +0 -0
  130. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/compile.py +0 -0
  131. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/engine.py +0 -0
  132. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/level.py +0 -0
  133. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/node.py +0 -0
  134. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/build/project.py +0 -0
  135. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/py.typed +0 -0
  136. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/__init__.py +0 -0
  137. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/bucket.py +0 -0
  138. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/easing.py +0 -0
  139. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/effect.py +0 -0
  140. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/globals.py +0 -0
  141. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/__init__.py +0 -0
  142. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/callbacks.py +0 -0
  143. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/constant.py +0 -0
  144. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/descriptor.py +0 -0
  145. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/dict_impl.py +0 -0
  146. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/error.py +0 -0
  147. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/generic.py +0 -0
  148. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/impl.py +0 -0
  149. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/introspection.py +0 -0
  150. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/random.py +0 -0
  151. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/simulation_context.py +0 -0
  152. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/internal/transient.py +0 -0
  153. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/iterator.py +0 -0
  154. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/level.py +0 -0
  155. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/metadata.py +0 -0
  156. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/options.py +0 -0
  157. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/particle.py +0 -0
  158. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/printing.py +0 -0
  159. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/project.py +0 -0
  160. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/sprite.py +0 -0
  161. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/text.py +0 -0
  162. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/timing.py +0 -0
  163. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/ui.py +0 -0
  164. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/sonolus/script/values.py +0 -0
  165. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/__init__.py +0 -0
  166. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/__init__.py +0 -0
  167. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/conftest.py +0 -0
  168. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_assert.py +0 -0
  169. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_dict.py +0 -0
  170. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_flow.py +0 -0
  171. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_functions.py +0 -0
  172. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_helpers.py +0 -0
  173. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_match.py +0 -0
  174. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_num.py +0 -0
  175. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_quad.py +0 -0
  176. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_random.py +0 -0
  177. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_record.py +0 -0
  178. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_transform.py +0 -0
  179. {sonolus_py-0.3.2 → sonolus_py-0.3.4}/tests/script/test_values.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonolus.py
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Sonolus engine development in Python
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -154,3 +154,36 @@ def log(x: float, base: float = ...) -> float:
154
154
  The logarithm of x to the specified base.
155
155
  """
156
156
  ...
157
+
158
+ def sqrt(x: float) -> float:
159
+ """Compute the square root of x.
160
+
161
+ Args:
162
+ x: A non-negative numeric value.
163
+
164
+ Returns:
165
+ The square root of x.
166
+ """
167
+ ...
168
+
169
+ def degrees(x: float) -> float:
170
+ """Convert radians to degrees.
171
+
172
+ Args:
173
+ x: An angle in radians.
174
+
175
+ Returns:
176
+ The angle in degrees.
177
+ """
178
+ ...
179
+
180
+ def radians(x: float) -> float:
181
+ """Convert degrees to radians.
182
+
183
+ Args:
184
+ x: An angle in degrees.
185
+
186
+ Returns:
187
+ The angle in radians.
188
+ """
189
+ ...
@@ -2,7 +2,7 @@
2
2
  Sonolus.py comes with support for a number of built-in functions.
3
3
 
4
4
  - `abs(x)`
5
- - `bool(object)` (for a num argument)
5
+ - `bool(object)`
6
6
  - `callable(object)`
7
7
  - `enumerate(iterable, start=0)`
8
8
  - `filter(function, iterable)`
@@ -38,6 +38,13 @@ Sonolus.py also comes with support for some standard library modules.
38
38
  - `ceil(x)`
39
39
  - `trunc(x)`
40
40
  - `log(x[, base])`
41
+ - `sqrt(x)`
42
+ - `degrees(x)`
43
+ - `radians(x)`
44
+ - `pi`
45
+ - `e`
46
+ - `tau`
47
+ - `inf`
41
48
 
42
49
  ### random
43
50
  - `randrange(stop)`, `random.randrange(start, stop[, step])`
@@ -25,4 +25,4 @@ sonolus-py schema
25
25
  ## Programmatic usage
26
26
  The same functionality can be accessed programmatically as methods of a project.
27
27
 
28
- See [Project][sonolus.script.project.Project] for more information.
28
+ See [Project](../reference/sonolus.script.project.md) for more information.
@@ -1,6 +1,8 @@
1
1
  # Constructs
2
2
 
3
- Most standard Python constructs are supported in Sonolus.py.
3
+ Sonolus.py functions as a compiler from Python to Sonolus nodes. While most standard Python constructs are supported,
4
+ there are some limitations compared to standard Python. The following sections outline what Sonolus.py supports and
5
+ how it differs from standard Python.
4
6
 
5
7
  ## Key Differences
6
8
 
@@ -11,10 +11,14 @@ the constants `None`, `Ellipsis`, and `NotImplemented`.
11
11
  `Num` is the numeric and boolean type in Sonolus.py. It is interchangeable with `int`, `float`, and `bool`.
12
12
  Sonolus.py will treat any of these types as `Num`, but it's recommended to use what's appropriate for clarity.
13
13
 
14
+ Typically, `Num` isn't directly used in code since `int`, `float`, and `bool` are more specific and easier to read.
15
+ The main exception is for instance checking (`isinstance` or `match` patterns), where `Num` is the only supported
16
+ way to check for numeric or boolean values.
17
+
14
18
  The Sonolus app uses 32-bit floating-point numbers for all numeric values, so precision may be lower compared to Python
15
- when running on Sonolus.
19
+ when running within Sonolus.
16
20
 
17
- Infinity, NaN, and values outside the range of 32-bit floating-point numbers are not supported.
21
+ NaN and values outside the range of 32-bit floating-point numbers are not supported.
18
22
 
19
23
  You can import `Num` from `sonolus.script.num`:
20
24
 
@@ -42,8 +46,10 @@ Nums support most of the standard Python operations:
42
46
  Floating point precision may be lower when running on Sonolus compared to Python.
43
47
  Care should be taken when performing precision-sensitive operations.
44
48
 
45
- Nums are the only supported type for boolean operations and control flow conditions.
46
- As a condition, any nonzero value is considered true, and `0` is considered false.
49
+ As in regular Python, `0` is considered `False`, while any non-zero value is considered `True`.
50
+
51
+ Objects with an explicit `__bool__` method may also be used in `if`, `while`, `case ... if` expressions as well as with
52
+ the `not` operator. However, the operands of the `and` and `or` operators must be of type `Num`.
47
53
 
48
54
  - Logical operators: `and`, `or`, `not`
49
55
  - Ternary expressions: `... if <condition> else ...`
@@ -0,0 +1,27 @@
1
+ # Sonolus.py
2
+ Sonolus.py is a Python library for creating Sonolus engines.
3
+
4
+ ## Installation
5
+ Sonolus.py is available on PyPI and can be installed using a package manager like pip.
6
+
7
+ === "pip"
8
+ ```bash
9
+ pip install sonolus.py
10
+ ```
11
+ === "uv"
12
+ ```bash
13
+ uv add sonolus.py
14
+ ```
15
+
16
+ ## Getting Started
17
+ Example Projects:
18
+
19
+ - [pydori](https://github.com/qwewqa/pydori): A Bandori-like engine designed to be an example project for Sonolus.py.
20
+
21
+ New Project Template: [sonolus.py-template-project](https://github.com/qwewqa/sonolus.py-template-project)
22
+
23
+ ## Documentation
24
+
25
+ - [Overview](overview.md): High-level overview of Sonolus.py.
26
+ - [Concepts](concepts/index.md): Detailed information on concepts and usage.
27
+ - [Reference](reference/index.md): Detailed information on included classes and functions.
@@ -0,0 +1,174 @@
1
+ # Overview
2
+
3
+ Sonolus.py is a Python library for creating Sonolus engines. This page provides an overview of the key functionality
4
+ available in the library. For more detailed information, see the [Concepts](concepts/index.md) and
5
+ [Reference](reference/index.md) sections.
6
+
7
+ ## Features
8
+ Sonolus.py functions by compiling Python code into Sonolus nodes. As such, it supports a subset of Python including
9
+ most syntax and a portion of the standard library. Additionally, Sonolus.py provides its own library of types and
10
+ functions that are specifically designed for use in Sonolus engines.
11
+
12
+ ### Language
13
+
14
+ #### Syntax
15
+ Most Python syntax is supported, but there are a few limitations:
16
+
17
+ - Destructuring assignment with the `*` operator is unsupported.
18
+ - Sequence (list and array) `match` patterns with the `*` operator are unsupported.
19
+ - Mapping (dict) `match` patterns are unsupported.
20
+ - Within functions, `import` statements are unsupported.
21
+ - The `global` and `nonlocal` keywords are unsupported.
22
+ - Exception related statements (`try`, `except`, `finally`, `raise`) are unsupported.
23
+
24
+ #### Compile Time Evaluation
25
+ Sonolus.py will evaluate some expressions at compile time such as basic arithmetic operations on constants,
26
+ boolean logical operations (`and`, `or`, `not`) on constants, and type checks (`isinstance`, `issubclass`).
27
+
28
+ In control flow constructs like `if` and `match`, Sonolus.py may determine some branches to be unreachable at compile
29
+ and eliminate them without evaluating them. This allows code like the following to compile successfully:
30
+
31
+ ```python
32
+ a = 1
33
+ if isinstance(a, Vec2):
34
+ # This branch is eliminated at compile time.
35
+ # If it were not, compilation would fail because `a` has no attribute `x`.
36
+ debug_log(a.x)
37
+ else:
38
+ debug_log(a)
39
+ ```
40
+
41
+ #### Variables
42
+ Numeric (`int`, `float`, `bool`) variables are fully supported and can be freely assigned and modified.
43
+
44
+ All other variables have the restriction that if the compiler finds multiple possible values for a variable, it may
45
+ not be accessed. For example, the following code will not compile:
46
+
47
+ ```python
48
+ if random() < 0.5:
49
+ a = Vec2(1, 2)
50
+ else:
51
+ a = Vec2(3, 4)
52
+ # This will not compile because `a` could have been defined in either branch.
53
+ debug_log(a.x)
54
+ ```
55
+
56
+ #### Function Returns
57
+ Similar to variables, functions returning `int`, `float`, or `bool` can have any number of return statements. Functions
58
+ returning `None` may also have any number of `return` or `return None` statements.
59
+
60
+ Functions returning any other type must have exactly one `return` statement, and it must be the only exit point of the
61
+ function. It is ok, however, for a function to have other `return` statements that are eliminated at compile time.
62
+ For example, the following code will compile successfully:
63
+
64
+ ```python
65
+ def fn(a: int | Vec2):
66
+ if isinstance(a, Vec2):
67
+ return a.x + a.y
68
+ else:
69
+ return a * 2
70
+
71
+ fn(123)
72
+ ```
73
+
74
+ ### Types
75
+
76
+ #### Numbers
77
+ Sonolus.py supports `int`, `float`, and `bool` types and most of the standard operations such as mathematical operations
78
+ (`+`, `-`, `*`, `/`, `//`, `%`), comparisons (`<`, `<=`, `>`, `>=`, `==`, `!=`), and boolean operations
79
+ (`and`, `or`, `not`).
80
+
81
+ #### Record
82
+ [`Record`](reference/sonolus.script.record.md) is the main way to define custom types in Sonolus.py. It functions similarly to a data class and
83
+ provides a way to define a type with named fields:
84
+
85
+ ```python
86
+ class MyRecord(Record):
87
+ a: int
88
+ b: float
89
+
90
+ my_record = MyRecord(1, b=2.5)
91
+ my_zero_record = +MyRecord # Short for MyRecord(0, 0.0)
92
+ my_record_copy = +my_record # Create a copy of my_record with the same values
93
+ my_record.a = 123 # Modify a field of the record
94
+ ```
95
+
96
+ Records may also be generic:
97
+
98
+ ```python
99
+ class MyGenericRecord[T](Record):
100
+ value: T
101
+
102
+ my_generic_record = MyGenericRecord[int](42)
103
+ my_generic_record_2 = MyGenericRecord(MyRecord(1, 3.5)) # Type arguments are inferred
104
+ ```
105
+
106
+ #### Array
107
+ [`Array`](reference/sonolus.script.array.md) is a type that represents a fixed-size array of elements of a specific type.
108
+
109
+ ```python
110
+ my_array = Array[int, 3](1, 2, 3)
111
+ my_array_2 = Array(4, 5, 6) # Type arguments are inferred
112
+ my_zero_array = +Array[int, 3] # Short for Array[int, 3](0, 0, 0)
113
+ my_array_copy = +my_array # Create a copy of my_array with the same values
114
+ my_array[2] = 10 # Modify an element of the array
115
+ ```
116
+
117
+ #### Other Types
118
+ Sonolus.py has limited support for other types of values such as strings, tuples, and functions. These have restrictions
119
+ such as not being valid as Record field types or Array element types.
120
+
121
+ ### Modules
122
+ Sonolus.py provides a number of built-in modules that can be used in Sonolus engines. These include:
123
+
124
+ - Project
125
+ - [Project](reference/sonolus.script.project.md): Configuration for a Sonolus.py project.
126
+ - [Engine](reference/sonolus.script.engine.md): Configuration for a Sonolus.py engine.
127
+ - [Level](reference/sonolus.script.level.md): Configuration for a Sonolus.py level.
128
+ - [Archetype](reference/sonolus.script.archetype.md): Engine archetypes and their configuration.
129
+ - Core Types
130
+ - [Array](reference/sonolus.script.array.md): Fixed-size arrays.
131
+ - [Num](reference/sonolus.script.num.md): Numeric values (int, float, bool).
132
+ - [Record](reference/sonolus.script.record.md): User-defined types with named fields.
133
+ - Engine Resources
134
+ - [Bucket](reference/sonolus.script.bucket.md): Judgment buckets.
135
+ - [Effect](reference/sonolus.script.effect.md): Sound effects.
136
+ - [Instruction](reference/sonolus.script.instruction.md): Tutorial instructions.
137
+ - [Options](reference/sonolus.script.options.md): Engine options.
138
+ - [Particle](reference/sonolus.script.particle.md): Particle effects.
139
+ - [Sprite](reference/sonolus.script.sprite.md): Sprites and skins.
140
+ - [UI](reference/sonolus.script.ui.md): Engine ui configuration.
141
+ - Sonolus Runtime
142
+ - [Globals](reference/sonolus.script.globals.md): Level data and level memory definition.
143
+ - [Runtime](reference/sonolus.script.runtime.md): Runtime functions like time and ui configuration.
144
+ - [Stream](reference/sonolus.script.stream.md): Data streams recorded in play mode and used in watch mode.
145
+ - [Text](reference/sonolus.script.text.md): Standard Sonolus text constants.
146
+ - [Timing](reference/sonolus.script.timing.md): Beat and timescale related functions.
147
+ - Python Builtins
148
+ - [builtins](reference/builtins.md): Supported Python builtins.
149
+ - [math](reference/math.md): Supported math functions.
150
+ - [random](reference/random.md): Supported random functions.
151
+ - Utilities
152
+ - [ArrayLike](reference/sonolus.script.array_like.md): Mixin for array functionality.
153
+ - [Containers](reference/sonolus.script.containers.md): Additional container types like `VarArray` and `ArrayMap`.
154
+ - [Debug](reference/sonolus.script.debug.md): Debugging utilities.
155
+ - [Easing](reference/sonolus.script.easing.md): Easing functions for animations.
156
+ - [Interval](reference/sonolus.script.interval.md): Mathematical intervals.
157
+ - [Iterator](reference/sonolus.script.iterator.md): Iterators over collections.
158
+ - [Printing](reference/sonolus.script.printing.md): Preview mode number printing.
159
+ - [Quad](reference/sonolus.script.quad.md): Quadrilaterals.
160
+ - [Transform](reference/sonolus.script.transform.md): Transformations like translation, rotation, and scaling.
161
+ - [Values](reference/sonolus.script.values.md): Generic utilities for working with values.
162
+ - [Vec](reference/sonolus.script.vec.md): The Vec2 type and related functions.
163
+
164
+ For more details, see the [Reference](reference/index.md) section.
165
+
166
+ ## Getting Started
167
+
168
+ Before making a new Sonolus.py engine, it may be helpful to look at some existing projects to see how they use
169
+ the library:
170
+
171
+ - [pydori](https://github.com/qwewqa/pydori): A Bandori-like engine designed to be an example project for Sonolus.py.
172
+
173
+ If you're starting a new project, you'll probably want to use the
174
+ [new project template](https://github.com/qwewqa/sonolus.py-template-project).
@@ -70,6 +70,7 @@ markdown_extensions:
70
70
  alternate_style: true
71
71
  nav:
72
72
  - index.md
73
+ - Overview: overview.md
73
74
  - Concepts:
74
75
  - concepts/index.md
75
76
  - concepts/types.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sonolus.py"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "Sonolus engine development in Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,3 +1,5 @@
1
+ from math import isfinite, isinf, isnan
2
+
1
3
  from sonolus.backend.ir import IRConst, IRGet, IRInstr, IRPureInstr, IRSet
2
4
  from sonolus.backend.node import ConstantNode, EngineNode, FunctionNode
3
5
  from sonolus.backend.ops import Op
@@ -54,10 +56,20 @@ def cfg_to_engine_node(entry: BasicBlock):
54
56
 
55
57
  def ir_to_engine_node(stmt) -> EngineNode:
56
58
  match stmt:
57
- case int() | float():
58
- return ConstantNode(value=float(stmt))
59
- case IRConst(value=int(value) | float(value)):
60
- return ConstantNode(value=value)
59
+ case int(value) | float(value) | IRConst(value=int(value) | float(value)):
60
+ value = float(value)
61
+ if value.is_integer():
62
+ return ConstantNode(value=int(value))
63
+ elif isfinite(value):
64
+ return ConstantNode(value=value)
65
+ elif isinf(value):
66
+ # Read values from ROM
67
+ return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=1 if value > 0 else 2)])
68
+ elif isnan(value):
69
+ # Read value from ROM
70
+ return FunctionNode(Op.Get, args=[ConstantNode(value=3000), ConstantNode(value=0)])
71
+ else:
72
+ raise ValueError(f"Invalid constant value: {value}")
61
73
  case IRPureInstr(op=op, args=args) | IRInstr(op=op, args=args):
62
74
  return FunctionNode(func=op, args=[ir_to_engine_node(arg) for arg in args])
63
75
  case IRGet(place=place):
@@ -359,7 +359,7 @@ class Visitor(ast.NodeVisitor):
359
359
  self.loop_head_ctxs.append(header_ctx)
360
360
  self.break_ctxs.append([])
361
361
  set_ctx(header_ctx)
362
- has_next = self.ensure_boolean_num(self.handle_call(node, iterator.has_next))
362
+ has_next = self.convert_to_boolean_num(node, self.handle_call(node, iterator.has_next))
363
363
  if has_next._is_py_() and not has_next._as_py_():
364
364
  # The loop will never run, continue after evaluating the condition
365
365
  self.loop_head_ctxs.pop()
@@ -400,7 +400,7 @@ class Visitor(ast.NodeVisitor):
400
400
  self.loop_head_ctxs.append(header_ctx)
401
401
  self.break_ctxs.append([])
402
402
  set_ctx(header_ctx)
403
- test = self.ensure_boolean_num(self.visit(node.test))
403
+ test = self.convert_to_boolean_num(node.test, self.visit(node.test))
404
404
  if test._is_py_():
405
405
  if test._as_py_():
406
406
  # The loop will run until a break / return
@@ -454,7 +454,7 @@ class Visitor(ast.NodeVisitor):
454
454
  set_ctx(after_ctx)
455
455
 
456
456
  def visit_If(self, node):
457
- test = self.ensure_boolean_num(self.visit(node.test))
457
+ test = self.convert_to_boolean_num(node.test, self.visit(node.test))
458
458
 
459
459
  if test._is_py_():
460
460
  if test._as_py_():
@@ -507,7 +507,9 @@ class Visitor(ast.NodeVisitor):
507
507
  set_ctx(false_ctx)
508
508
  continue
509
509
  set_ctx(true_ctx)
510
- guard = self.ensure_boolean_num(self.visit(case.guard)) if case.guard else validate_value(True)
510
+ guard = (
511
+ self.convert_to_boolean_num(case.guard, self.visit(case.guard)) if case.guard else validate_value(True)
512
+ )
511
513
  if guard._is_py_():
512
514
  if guard._as_py_():
513
515
  for stmt in case.body:
@@ -544,7 +546,7 @@ class Visitor(ast.NodeVisitor):
544
546
  match pattern:
545
547
  case ast.MatchValue(value=value):
546
548
  value = self.visit(value)
547
- test = self.ensure_boolean_num(validate_value(subject == value))
549
+ test = self.convert_to_boolean_num(pattern, validate_value(subject == value))
548
550
  if test._is_py_():
549
551
  if test._as_py_():
550
552
  return ctx(), ctx().into_dead()
@@ -574,7 +576,7 @@ class Visitor(ast.NodeVisitor):
574
576
  target_len = len(patterns)
575
577
  if not (isinstance(subject, Sequence | TupleImpl)):
576
578
  return ctx().into_dead(), ctx()
577
- length_test = self.ensure_boolean_num(validate_value(_len(subject) == target_len))
579
+ length_test = self.convert_to_boolean_num(pattern, validate_value(_len(subject) == target_len))
578
580
  ctx_init = ctx()
579
581
  if not length_test._is_py_():
580
582
  ctx_init.test = length_test.ir()
@@ -739,7 +741,7 @@ class Visitor(ast.NodeVisitor):
739
741
  def visit_UnaryOp(self, node):
740
742
  operand = self.visit(node.operand)
741
743
  if isinstance(node.op, ast.Not):
742
- return self.ensure_boolean_num(operand).not_()
744
+ return self.convert_to_boolean_num(node, operand).not_()
743
745
  op = unary_ops[type(node.op)]
744
746
  if operand._is_py_():
745
747
  operand_py = operand._as_py_()
@@ -768,7 +770,7 @@ class Visitor(ast.NodeVisitor):
768
770
  return validate_value(fn)
769
771
 
770
772
  def visit_IfExp(self, node):
771
- test = self.ensure_boolean_num(self.visit(node.test))
773
+ test = self.convert_to_boolean_num(node.test, self.visit(node.test))
772
774
 
773
775
  if test._is_py_():
774
776
  if test._as_py_():
@@ -1076,7 +1078,7 @@ class Visitor(ast.NodeVisitor):
1076
1078
 
1077
1079
  def handle_call[**P, R](
1078
1080
  self, node: ast.stmt | ast.expr, fn: Callable[P, R], /, *args: P.args, **kwargs: P.kwargs
1079
- ) -> R:
1081
+ ) -> R | Value:
1080
1082
  """Handles a call to the given callable."""
1081
1083
  self.active_ctx = ctx()
1082
1084
  if (
@@ -1127,6 +1129,20 @@ class Visitor(ast.NodeVisitor):
1127
1129
  raise TypeError(f"Invalid type where a bool (Num) was expected: {type(value).__name__}")
1128
1130
  return value
1129
1131
 
1132
+ def convert_to_boolean_num(self, node, value: Value) -> Num:
1133
+ if _is_num(value):
1134
+ return value
1135
+ if hasattr(type(value), "__bool__"):
1136
+ return self.ensure_boolean_num(self.handle_call(node, type(value).__bool__, validate_value(value)))
1137
+ if hasattr(type(value), "__len__"):
1138
+ length = self.handle_call(node, type(value).__len__, validate_value(value))
1139
+ if not _is_num(length):
1140
+ raise TypeError(f"Invalid type for __len__: {type(length).__name__}")
1141
+ if length._is_py_():
1142
+ return Num._accept_(length._as_py_() > 0)
1143
+ return length > Num._accept_(0)
1144
+ raise TypeError(f"Converting {type(value).__name__} to bool is not supported")
1145
+
1130
1146
  def arguments_to_signature(self, arguments: ast.arguments) -> inspect.Signature:
1131
1147
  parameters: list[inspect.Parameter] = []
1132
1148
  pos_only_count = len(arguments.posonlyargs)
@@ -312,7 +312,7 @@ class StandardImport:
312
312
  def callback[T: Callable](*, order: int = 0) -> Callable[[T], T]:
313
313
  """Annotate a callback with its order.
314
314
 
315
- Callbacks are execute from lowest to highest order. By default, callbacks have an order of 0.
315
+ Callbacks are executed from lowest to highest order. By default, callbacks have an order of 0.
316
316
 
317
317
  Usage:
318
318
  ```python
@@ -338,9 +338,9 @@ class _ArchetypeSelfData:
338
338
 
339
339
 
340
340
  class _ArchetypeReferenceData:
341
- index: Num
341
+ index: int
342
342
 
343
- def __init__(self, index: Num):
343
+ def __init__(self, index: int):
344
344
  self.index = index
345
345
 
346
346
 
@@ -416,21 +416,21 @@ class _BaseArchetype:
416
416
 
417
417
  @classmethod
418
418
  @meta_fn
419
- def at(cls, index: Num) -> Self:
419
+ def at(cls, index: int) -> Self:
420
420
  result = cls._new()
421
421
  result._data_ = _ArchetypeReferenceData(index=Num._accept_(index))
422
422
  return result
423
423
 
424
424
  @classmethod
425
425
  @meta_fn
426
- def is_at(cls, index: Num) -> bool:
426
+ def is_at(cls, index: int) -> bool:
427
427
  if not ctx():
428
428
  raise RuntimeError("is_at is only available during compilation")
429
429
  return entity_info_at(index).archetype_id == cls.id()
430
430
 
431
431
  @classmethod
432
432
  @meta_fn
433
- def id(cls):
433
+ def id(cls) -> int:
434
434
  if not ctx():
435
435
  raise RuntimeError("Archetype id is only available during compilation")
436
436
  result = ctx().global_state.archetypes.get(cls)
@@ -549,11 +549,19 @@ class _BaseArchetype:
549
549
  metadata = _annotation_defaults.get(metadata, metadata)
550
550
  if isinstance(metadata, _ArchetypeFieldInfo):
551
551
  if field_info is not None:
552
- raise TypeError(
553
- f"Unexpected multiple field annotations for '{name}', "
554
- f"expected exactly one of imported, exported, entity_memory, or shared_memory"
555
- )
556
- field_info = metadata
552
+ if field_info.storage == metadata.storage and field_info.name is None:
553
+ field_info = metadata
554
+ elif field_info.storage == metadata.storage and (
555
+ metadata.name is None or field_info.name == metadata.name
556
+ ):
557
+ pass
558
+ else:
559
+ raise TypeError(
560
+ f"Unexpected multiple field annotations for '{name}', "
561
+ f"expected exactly one of imported, exported, entity_memory, or shared_memory"
562
+ )
563
+ else:
564
+ field_info = metadata
557
565
  if field_info is None:
558
566
  raise TypeError(
559
567
  f"Missing field annotation for '{name}', "
@@ -880,7 +888,7 @@ class WatchArchetype(_BaseArchetype):
880
888
  case _ArchetypeSelfData():
881
889
  return _deref(ctx().blocks.EntityInfo, 0, WatchEntityInfo)
882
890
  case _ArchetypeReferenceData(index=index):
883
- return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), PlayEntityInfo)
891
+ return _deref(ctx().blocks.EntityInfoArray, index * WatchEntityInfo._size_(), WatchEntityInfo)
884
892
  case _:
885
893
  raise RuntimeError("Info is only accessible from the entity itself")
886
894
 
@@ -984,7 +992,7 @@ class PreviewArchetype(_BaseArchetype):
984
992
 
985
993
 
986
994
  @meta_fn
987
- def entity_info_at(index: Num) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
995
+ def entity_info_at(index: int) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo:
988
996
  """Retrieve entity info of the entity at the given index.
989
997
 
990
998
  Available in play, watch, and preview mode.
@@ -1039,24 +1047,24 @@ class PreviewEntityInfo(Record):
1039
1047
  class ArchetypeLife(Record):
1040
1048
  """How an entity contributes to life."""
1041
1049
 
1042
- perfect_increment: Num
1050
+ perfect_increment: int
1043
1051
  """Life increment for a perfect judgment."""
1044
1052
 
1045
- great_increment: Num
1053
+ great_increment: int
1046
1054
  """Life increment for a great judgment."""
1047
1055
 
1048
- good_increment: Num
1056
+ good_increment: int
1049
1057
  """Life increment for a good judgment."""
1050
1058
 
1051
- miss_increment: Num
1059
+ miss_increment: int
1052
1060
  """Life increment for a miss judgment."""
1053
1061
 
1054
1062
  def update(
1055
1063
  self,
1056
- perfect_increment: Num | None = None,
1057
- great_increment: Num | None = None,
1058
- good_increment: Num | None = None,
1059
- miss_increment: Num | None = None,
1064
+ perfect_increment: int | None = None,
1065
+ great_increment: int | None = None,
1066
+ good_increment: int | None = None,
1067
+ miss_increment: int | None = None,
1060
1068
  ):
1061
1069
  """Update the life increments."""
1062
1070
  if perfect_increment is not None:
@@ -1106,10 +1114,20 @@ class EntityRef[A: _BaseArchetype](Record):
1106
1114
  """Return a new reference with the given archetype type."""
1107
1115
  return EntityRef[archetype](index=self.index)
1108
1116
 
1117
+ @meta_fn
1109
1118
  def get(self) -> A:
1110
1119
  """Get the entity."""
1120
+ if ref := getattr(self, "_ref_", None):
1121
+ return ref
1111
1122
  return self.archetype().at(self.index)
1112
1123
 
1124
+ @meta_fn
1125
+ def get_as(self, archetype: type[_BaseArchetype]) -> _BaseArchetype:
1126
+ """Get the entity as the given archetype type."""
1127
+ if getattr(archetype, "_ref_", None):
1128
+ raise TypeError("Using get_as in level data is not supported.")
1129
+ return self.with_archetype(archetype).get()
1130
+
1113
1131
  def archetype_matches(self) -> bool:
1114
1132
  """Check if entity at the index is precisely of the archetype."""
1115
1133
  return self.index >= 0 and self.archetype().is_at(self.index)
@@ -1117,7 +1135,7 @@ class EntityRef[A: _BaseArchetype](Record):
1117
1135
  def _to_list_(self, level_refs: dict[Any, str] | None = None) -> list[DataValue | str]:
1118
1136
  ref = getattr(self, "_ref_", None)
1119
1137
  if ref is None:
1120
- return [self.index]
1138
+ return Num._accept_(self.index)._to_list_()
1121
1139
  else:
1122
1140
  if ref not in level_refs:
1123
1141
  raise KeyError("Reference to entity not in level data")