aether-engine 1.3.1__tar.gz → 1.7.0__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.
Files changed (129) hide show
  1. {aether_engine-1.3.1 → aether_engine-1.7.0}/MANIFEST.in +1 -0
  2. {aether_engine-1.3.1/aether_engine.egg-info → aether_engine-1.7.0}/PKG-INFO +19 -14
  3. {aether_engine-1.3.1 → aether_engine-1.7.0}/README.md +13 -11
  4. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/__init__.py +12 -3
  5. aether_engine-1.7.0/aether/__main__.py +6 -0
  6. aether_engine-1.7.0/aether/accelerators/__init__.py +30 -0
  7. aether_engine-1.7.0/aether/accelerators/gameplay.py +54 -0
  8. aether_engine-1.7.0/aether/accelerators/voxel.py +227 -0
  9. aether_engine-1.7.0/aether/assets/__pycache__/asset_pipeline.cpython-314.pyc +0 -0
  10. aether_engine-1.7.0/aether/assets/fonts/AetherSans.ttf +0 -0
  11. aether_engine-1.7.0/aether/cli.py +167 -0
  12. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/_identity.py +6 -7
  13. aether_engine-1.7.0/aether/core/boot_splash.py +208 -0
  14. aether_engine-1.7.0/aether/core/branding_guard.py +32 -0
  15. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/engine.py +16 -36
  16. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/input.py +5 -0
  17. aether_engine-1.7.0/aether/editor/__init__.py +3 -0
  18. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/mesh.py +21 -0
  19. aether_engine-1.7.0/aether/graphics/voxel_mesh.py +4 -0
  20. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics2d/__init__.py +7 -0
  21. aether_engine-1.7.0/aether/resources/asset_loader.py +143 -0
  22. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/loaders/model_loader.py +14 -0
  23. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/component.py +8 -0
  24. aether_engine-1.7.0/aether/tools/__init__.py +1 -0
  25. aether_engine-1.7.0/aether/tools/packager.py +68 -0
  26. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ui/text.py +8 -2
  27. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/fonts.py +8 -0
  28. aether_engine-1.7.0/aether/web/__init__.py +4 -0
  29. aether_engine-1.7.0/aether/web/bot_detect.py +53 -0
  30. aether_engine-1.7.0/aether/web/config.py +61 -0
  31. aether_engine-1.7.0/aether/web/content.py +427 -0
  32. aether_engine-1.7.0/aether/web/notifications.py +43 -0
  33. aether_engine-1.7.0/aether/web/plain.py +57 -0
  34. aether_engine-1.7.0/aether/web/plain_content.py +166 -0
  35. aether_engine-1.7.0/aether/web/registry.py +42 -0
  36. aether_engine-1.7.0/aether/web/secrets.py +29 -0
  37. aether_engine-1.7.0/aether/web/server.py +165 -0
  38. aether_engine-1.7.0/aether/web/templates.py +198 -0
  39. aether_engine-1.7.0/aether/web/urls.py +20 -0
  40. {aether_engine-1.3.1 → aether_engine-1.7.0/aether_engine.egg-info}/PKG-INFO +19 -14
  41. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether_engine.egg-info/SOURCES.txt +23 -4
  42. aether_engine-1.7.0/aether_engine.egg-info/entry_points.txt +2 -0
  43. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether_engine.egg-info/requires.txt +6 -2
  44. {aether_engine-1.3.1 → aether_engine-1.7.0}/examples/03_first_person.py +1 -1
  45. {aether_engine-1.3.1 → aether_engine-1.7.0}/setup.py +4 -2
  46. aether_engine-1.3.1/aether/core/boot_splash.py +0 -141
  47. aether_engine-1.3.1/aether/core/branding_guard.py +0 -103
  48. aether_engine-1.3.1/aether/editor/__init__.py +0 -5
  49. aether_engine-1.3.1/aether/editor/__main__.py +0 -6
  50. aether_engine-1.3.1/aether/editor/gizmo.py +0 -346
  51. aether_engine-1.3.1/aether/editor/imgui_integration.py +0 -468
  52. aether_engine-1.3.1/aether/editor/model_editor.py +0 -253
  53. aether_engine-1.3.1/aether_engine.egg-info/entry_points.txt +0 -2
  54. {aether_engine-1.3.1 → aether_engine-1.7.0}/LICENSE +0 -0
  55. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ai/behavior_tree.py +0 -0
  56. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ai/navmesh.py +0 -0
  57. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/animation/skeleton.py +0 -0
  58. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/assets/asset_pipeline.py +0 -0
  59. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/audio/__init__.py +0 -0
  60. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/audio/audio_manager.py +0 -0
  61. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/audio/music.py +0 -0
  62. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/audio/sfx.py +0 -0
  63. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/audio/sound.py +0 -0
  64. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/__init__.py +0 -0
  65. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/achievement_system.py +0 -0
  66. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/application.py +0 -0
  67. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/config.py +0 -0
  68. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/console.py +0 -0
  69. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/event_bus.py +0 -0
  70. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/plugin_system.py +0 -0
  71. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/save_system.py +0 -0
  72. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/time_manager.py +0 -0
  73. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/core/window.py +0 -0
  74. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/environment/environment_system.py +0 -0
  75. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/__init__.py +0 -0
  76. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/camera.py +0 -0
  77. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/first_person.py +0 -0
  78. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/framebuffer.py +0 -0
  79. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/light.py +0 -0
  80. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/lod_system.py +0 -0
  81. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/material.py +0 -0
  82. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/mesh_factory.py +0 -0
  83. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/particle_system.py +0 -0
  84. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/post_process.py +0 -0
  85. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/renderer.py +0 -0
  86. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/shader.py +0 -0
  87. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/shader_library.py +0 -0
  88. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/sky_atmosphere.py +0 -0
  89. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/terrain.py +0 -0
  90. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/texture.py +0 -0
  91. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/graphics/water.py +0 -0
  92. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/network/network_manager.py +0 -0
  93. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/__init__.py +0 -0
  94. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/bullet_backend.py +0 -0
  95. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/collider.py +0 -0
  96. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/physics2d.py +0 -0
  97. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/physics3d.py +0 -0
  98. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/physics_world.py +0 -0
  99. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/raycast.py +0 -0
  100. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/physics/rigidbody.py +0 -0
  101. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/platform/steam_api.py +0 -0
  102. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/__init__.py +0 -0
  103. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/asset_database.py +0 -0
  104. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/loaders/obj_loader.py +0 -0
  105. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/loaders/texture_loader.py +0 -0
  106. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/resource_manager.py +0 -0
  107. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/save_load.py +0 -0
  108. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/resources/serialization.py +0 -0
  109. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/__init__.py +0 -0
  110. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/entity.py +0 -0
  111. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/scene.py +0 -0
  112. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/scene_graph.py +0 -0
  113. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/scene/scene_manager.py +0 -0
  114. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ui/__init__.py +0 -0
  115. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ui/button.py +0 -0
  116. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ui/canvas.py +0 -0
  117. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/ui/hud.py +0 -0
  118. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/__init__.py +0 -0
  119. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/logger.py +0 -0
  120. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/math_utils.py +0 -0
  121. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/object_pool.py +0 -0
  122. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether/utils/profiler.py +0 -0
  123. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether_engine.egg-info/dependency_links.txt +0 -0
  124. {aether_engine-1.3.1 → aether_engine-1.7.0}/aether_engine.egg-info/top_level.txt +0 -0
  125. {aether_engine-1.3.1 → aether_engine-1.7.0}/examples/01_hello_cube.py +0 -0
  126. {aether_engine-1.3.1 → aether_engine-1.7.0}/examples/02_sphere_scene.py +0 -0
  127. {aether_engine-1.3.1 → aether_engine-1.7.0}/examples/04_physics2d.py +0 -0
  128. {aether_engine-1.3.1 → aether_engine-1.7.0}/pyproject.toml +0 -0
  129. {aether_engine-1.3.1 → aether_engine-1.7.0}/setup.cfg +0 -0
@@ -7,6 +7,7 @@ recursive-include aether *.glsl
7
7
  recursive-include aether *.vert
8
8
  recursive-include aether *.frag
9
9
  recursive-include aether *.json
10
+ recursive-include aether/assets *
10
11
 
11
12
  recursive-include examples *.py
12
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aether-engine
3
- Version: 1.3.1
3
+ Version: 1.7.0
4
4
  Summary: 3D/2D game engine by entiti937 — OpenGL, physics, FPS, mandatory branding splash
5
5
  Home-page: https://pypi.org/project/aether-engine/
6
6
  Author: entiti937
@@ -28,13 +28,16 @@ Provides-Extra: physics
28
28
  Requires-Dist: pymunk>=6.0; extra == "physics"
29
29
  Provides-Extra: models
30
30
  Requires-Dist: trimesh>=3.21; extra == "models"
31
+ Provides-Extra: build
32
+ Requires-Dist: pyinstaller>=6.0; extra == "build"
31
33
  Provides-Extra: full
32
- Requires-Dist: pygame-ce>=2.5; extra == "full"
34
+ Requires-Dist: freetype-py>=2.4; extra == "full"
33
35
  Requires-Dist: imageio>=2.31; extra == "full"
36
+ Requires-Dist: pygame-ce>=2.5; extra == "full"
34
37
  Requires-Dist: pyyaml>=6.0; extra == "full"
35
38
  Requires-Dist: trimesh>=3.21; extra == "full"
36
39
  Requires-Dist: pymunk>=6.0; extra == "full"
37
- Requires-Dist: freetype-py>=2.4; extra == "full"
40
+ Requires-Dist: pyinstaller>=6.0; extra == "full"
38
41
  Dynamic: author
39
42
  Dynamic: classifier
40
43
  Dynamic: description
@@ -49,7 +52,7 @@ Dynamic: summary
49
52
  # Aether Engine
50
53
 
51
54
  **3D / 2D игровой движок на Python + OpenGL**
52
- **Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
55
+ **Разработчик:** entiti937 · **Версия:** 1.7.0 · **Лицензия:** MIT
53
56
  **PyPI:** https://pypi.org/project/aether-engine/
54
57
 
55
58
  > Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
@@ -62,18 +65,19 @@ Dynamic: summary
62
65
  pip install -U "aether-engine[full]"
63
66
  ```
64
67
 
65
- ## Что нового в 1.3.0
68
+ ## Что нового в 1.7.0
66
69
 
67
- - **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
68
- - **Водяной знак** в каждом кадре + защита от удаления брендинга
69
- - **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
70
- - Компиляция защищённых модулей при сборке пакета
70
+ - **AssetLoader** — загрузка любых моделей, спрайтов и текстур (OBJ/GLB/FBX/PNG/WebP/…)
71
+ - `Sprite.from_image(path)` — быстрый импорт 2D-графики
72
+ - Встроенный редактор **убран** (может вернуться в будущих версиях)
71
73
 
72
74
  ## Showcase-игра
73
75
 
74
76
  Minecraft-like sandbox **на этом же движке**:
75
77
 
76
78
  ```bash
79
+ aether help
80
+ aether build games/aether_craft.py --name AetherCraft
77
81
  python games/aether_craft.py
78
82
  ```
79
83
 
@@ -82,10 +86,10 @@ python games/aether_craft.py
82
86
  ## Другие запуски
83
87
 
84
88
  ```bash
85
- python -m aether.editor
86
89
  python games/orb_rush.py
87
90
  python examples/03_first_person.py
88
91
  python examples/04_physics2d.py
92
+ aether website
89
93
  ```
90
94
 
91
95
  ## Минимальный код
@@ -110,15 +114,16 @@ engine.enable_first_person(camera)
110
114
  engine.run()
111
115
  ```
112
116
 
113
- ## Возможности 1.2
117
+ ## Возможности
114
118
 
115
119
  - PBR 3D + sprites 2D + HUD overlay
116
120
  - FPS (`enable_first_person`) и free-look камера
117
121
  - Физика 3D/2D + raycast
118
- - `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
119
- - Встроенный voxel model editor
122
+ - `AssetLoader` / `ModelLoader` — OBJ/GLB/glTF/STL/FBX/STEP/… + PNG/WebP спрайты
120
123
  - Procedural SFX, JSON save/load
121
- - Примеры и две игры в репозитории
124
+ - Примеры и игры в репозитории
125
+
126
+ > Встроенный редактор моделей/пиксель-арт **временно убран** — планируется вернуть позже. Сейчас используйте Blender, Aseprite и т.д., файлы подгружайте через `AssetLoader`.
122
127
 
123
128
  ## Документация
124
129
 
@@ -1,7 +1,7 @@
1
1
  # Aether Engine
2
2
 
3
3
  **3D / 2D игровой движок на Python + OpenGL**
4
- **Разработчик:** entiti937 · **Версия:** 1.3.0 · **Лицензия:** MIT
4
+ **Разработчик:** entiti937 · **Версия:** 1.7.0 · **Лицензия:** MIT
5
5
  **PyPI:** https://pypi.org/project/aether-engine/
6
6
 
7
7
  > Полное руководство: [`docs/GUIDE.md`](docs/GUIDE.md)
@@ -14,18 +14,19 @@
14
14
  pip install -U "aether-engine[full]"
15
15
  ```
16
16
 
17
- ## Что нового в 1.3.0
17
+ ## Что нового в 1.7.0
18
18
 
19
- - **Обязательный splash** при старте любой игры (чёрный экран, белый текст, entiti937)
20
- - **Водяной знак** в каждом кадре + защита от удаления брендинга
21
- - **Кириллица** — системные шрифты, UTF-8 консоль, полный Unicode в UI
22
- - Компиляция защищённых модулей при сборке пакета
19
+ - **AssetLoader** — загрузка любых моделей, спрайтов и текстур (OBJ/GLB/FBX/PNG/WebP/…)
20
+ - `Sprite.from_image(path)` — быстрый импорт 2D-графики
21
+ - Встроенный редактор **убран** (может вернуться в будущих версиях)
23
22
 
24
23
  ## Showcase-игра
25
24
 
26
25
  Minecraft-like sandbox **на этом же движке**:
27
26
 
28
27
  ```bash
28
+ aether help
29
+ aether build games/aether_craft.py --name AetherCraft
29
30
  python games/aether_craft.py
30
31
  ```
31
32
 
@@ -34,10 +35,10 @@ python games/aether_craft.py
34
35
  ## Другие запуски
35
36
 
36
37
  ```bash
37
- python -m aether.editor
38
38
  python games/orb_rush.py
39
39
  python examples/03_first_person.py
40
40
  python examples/04_physics2d.py
41
+ aether website
41
42
  ```
42
43
 
43
44
  ## Минимальный код
@@ -62,15 +63,16 @@ engine.enable_first_person(camera)
62
63
  engine.run()
63
64
  ```
64
65
 
65
- ## Возможности 1.2
66
+ ## Возможности
66
67
 
67
68
  - PBR 3D + sprites 2D + HUD overlay
68
69
  - FPS (`enable_first_person`) и free-look камера
69
70
  - Физика 3D/2D + raycast
70
- - `ModelLoader` (OBJ/GLB/glTF/STL/FBX/…)
71
- - Встроенный voxel model editor
71
+ - `AssetLoader` / `ModelLoader` — OBJ/GLB/glTF/STL/FBX/STEP/… + PNG/WebP спрайты
72
72
  - Procedural SFX, JSON save/load
73
- - Примеры и две игры в репозитории
73
+ - Примеры и игры в репозитории
74
+
75
+ > Встроенный редактор моделей/пиксель-арт **временно убран** — планируется вернуть позже. Сейчас используйте Blender, Aseprite и т.д., файлы подгружайте через `AssetLoader`.
74
76
 
75
77
  ## Документация
76
78
 
@@ -23,15 +23,19 @@ from aether.graphics.material import Material
23
23
  from aether.graphics.camera import Camera
24
24
  from aether.graphics.first_person import FirstPersonController
25
25
  from aether.resources.loaders.model_loader import ModelLoader
26
+ from aether.resources.asset_loader import AssetLoader
26
27
  from aether.physics import PhysicsWorld2D, PhysicsWorld3D, BoxCollider, SphereCollider
27
28
  from aether.graphics2d import Renderer2D, Sprite, Camera2D
28
- from aether.editor import ModelEditor
29
29
  from aether.ui.hud import Hud, HudBar
30
30
  from aether.audio.sfx import SfxBank
31
31
  from aether.resources.save_load import save_json, load_json
32
32
  from aether.physics.raycast import Raycaster
33
33
 
34
- __version__ = "1.3.1"
34
+ __version__ = "1.7.0"
35
+
36
+ from aether.graphics.voxel_mesh import ChunkedVoxelMesher
37
+ from aether.accelerators import is_native_available, backend_name
38
+ from aether.accelerators.gameplay import npc_move_toward, roll_loot
35
39
 
36
40
  __all__ = [
37
41
  "Engine",
@@ -48,6 +52,7 @@ __all__ = [
48
52
  "Camera",
49
53
  "FirstPersonController",
50
54
  "ModelLoader",
55
+ "AssetLoader",
51
56
  "PhysicsWorld2D",
52
57
  "PhysicsWorld3D",
53
58
  "BoxCollider",
@@ -55,12 +60,16 @@ __all__ = [
55
60
  "Renderer2D",
56
61
  "Sprite",
57
62
  "Camera2D",
58
- "ModelEditor",
59
63
  "Hud",
60
64
  "HudBar",
61
65
  "SfxBank",
62
66
  "save_json",
63
67
  "load_json",
64
68
  "Raycaster",
69
+ "ChunkedVoxelMesher",
70
+ "is_native_available",
71
+ "backend_name",
72
+ "npc_move_toward",
73
+ "roll_loot",
65
74
  "__version__",
66
75
  ]
@@ -0,0 +1,6 @@
1
+ """python -m aether"""
2
+
3
+ from aether.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,30 @@
1
+ """Rust/C++ accelerators — prebuilt binaries, no user toolchain required."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional
5
+
6
+ _NATIVE = None
7
+ _NATIVE_TRIED = False
8
+
9
+
10
+ def is_native_available() -> bool:
11
+ """True when the precompiled Rust extension is loaded."""
12
+ global _NATIVE, _NATIVE_TRIED
13
+ if _NATIVE_TRIED:
14
+ return _NATIVE is not None
15
+ _NATIVE_TRIED = True
16
+ try:
17
+ import aether_native as mod # type: ignore
18
+
19
+ _NATIVE = mod
20
+ except ImportError:
21
+ _NATIVE = None
22
+ return _NATIVE is not None
23
+
24
+
25
+ def native_module():
26
+ return _NATIVE
27
+
28
+
29
+ def backend_name() -> str:
30
+ return "rust" if is_native_available() else "python"
@@ -0,0 +1,54 @@
1
+ """Game systems accelerated by Rust when the prebuilt extension is present."""
2
+ from __future__ import annotations
3
+
4
+ from typing import List, Tuple
5
+
6
+ from aether.accelerators import is_native_available, native_module
7
+
8
+
9
+ def npc_move_toward(
10
+ pos: Tuple[float, float],
11
+ target: Tuple[float, float],
12
+ speed: float,
13
+ attack_range: float = 1.5,
14
+ ) -> Tuple[float, float, int]:
15
+ """
16
+ Move NPC toward target. Returns (x, z, action).
17
+ action: 0=idle, 1=chase, 2=attack
18
+ """
19
+ px, pz = pos
20
+ tx, tz = target
21
+ if is_native_available():
22
+ nx, nz, act = native_module().npc_tick(px, pz, tx, tz, speed, attack_range)
23
+ return nx, nz, int(act)
24
+
25
+ dx, dz = tx - px, tz - pz
26
+ dist = (dx * dx + dz * dz) ** 0.5
27
+ if dist < 0.05:
28
+ return px, pz, 0
29
+ if dist <= attack_range:
30
+ return px, pz, 2
31
+ step = min(speed, dist)
32
+ return px + dx / dist * step, pz + dz / dist * step, 1
33
+
34
+
35
+ def roll_loot(weights: List[float], seed: int = 0) -> int:
36
+ """Weighted random loot index, or -1."""
37
+ if not weights:
38
+ return -1
39
+ if is_native_available():
40
+ return int(native_module().loot_roll(weights, seed & 0xFFFFFFFFFFFFFFFF))
41
+
42
+ import random
43
+
44
+ rng = random.Random(seed)
45
+ total = sum(weights)
46
+ if total <= 0:
47
+ return -1
48
+ pick = rng.uniform(0, total)
49
+ acc = 0.0
50
+ for i, w in enumerate(weights):
51
+ acc += w
52
+ if pick <= acc:
53
+ return i
54
+ return len(weights) - 1
@@ -0,0 +1,227 @@
1
+ """Voxel mesh acceleration — Rust when available, pure Python fallback."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Dict, List, Tuple
5
+
6
+ import numpy as np
7
+
8
+ from aether.accelerators import is_native_available, native_module
9
+ from aether.graphics.mesh import Mesh
10
+
11
+ Vec3i = Tuple[int, int, int]
12
+
13
+ DEFAULT_FACES = [
14
+ ((0, 0, 1), [(0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]),
15
+ ((0, 0, -1), [(1, 0, 0), (0, 0, 0), (0, 1, 0), (1, 1, 0)]),
16
+ ((-1, 0, 0), [(0, 0, 0), (0, 0, 1), (0, 1, 1), (0, 1, 0)]),
17
+ ((1, 0, 0), [(1, 0, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1)]),
18
+ ((0, 1, 0), [(0, 1, 1), (1, 1, 1), (1, 1, 0), (0, 1, 0)]),
19
+ ((0, -1, 0), [(0, 0, 0), (1, 0, 0), (1, 0, 1), (0, 0, 1)]),
20
+ ]
21
+
22
+
23
+ def _python_build_chunk(
24
+ get_block,
25
+ x0: int,
26
+ y0: int,
27
+ z0: int,
28
+ x1: int,
29
+ y1: int,
30
+ z1: int,
31
+ block_types: set | None,
32
+ faces,
33
+ ) -> Dict[int, Mesh]:
34
+ buckets: Dict[int, list] = {}
35
+ for x in range(x0, x1):
36
+ for y in range(y0, y1):
37
+ for z in range(z0, z1):
38
+ bid = get_block(x, y, z)
39
+ if bid == 0:
40
+ continue
41
+ if block_types and bid not in block_types:
42
+ continue
43
+ if bid not in buckets:
44
+ buckets[bid] = [[], []]
45
+ verts, inds = buckets[bid]
46
+ index = len(verts) // 8
47
+ for normal, corners in faces:
48
+ nx, ny, nz = normal
49
+ if get_block(x + nx, y + ny, z + nz) != 0:
50
+ continue
51
+ face = []
52
+ for px, py, pz in corners:
53
+ verts.extend([x + px, y + py, z + pz, float(nx), float(ny), float(nz), 0.0, 0.0])
54
+ face.append(index)
55
+ index += 1
56
+ inds.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
57
+
58
+ meshes = {}
59
+ for bid, (verts, inds) in buckets.items():
60
+ if inds:
61
+ meshes[bid] = Mesh(
62
+ np.array(verts, dtype=np.float32),
63
+ np.array(inds, dtype=np.uint32),
64
+ )
65
+ return meshes
66
+
67
+
68
+ def _rust_build_chunk(
69
+ get_block,
70
+ x0: int,
71
+ y0: int,
72
+ z0: int,
73
+ x1: int,
74
+ y1: int,
75
+ z1: int,
76
+ block_types: set | None,
77
+ ) -> Dict[int, Mesh]:
78
+ mod = native_module()
79
+ blocks: List[Tuple[int, int, int, int]] = []
80
+ for x in range(x0, x1):
81
+ for y in range(y0, y1):
82
+ for z in range(z0, z1):
83
+ bid = get_block(x, y, z)
84
+ if bid == 0:
85
+ continue
86
+ if block_types and bid not in block_types:
87
+ continue
88
+ blocks.append((x, y, z, int(bid)))
89
+
90
+ raw = mod.build_chunk_mesh(blocks, x0, y0, z0, x1, y1, z1)
91
+ meshes = {}
92
+ for bid, (verts, inds) in raw.items():
93
+ if inds:
94
+ meshes[int(bid)] = Mesh(
95
+ np.array(verts, dtype=np.float32),
96
+ np.array(inds, dtype=np.uint32),
97
+ )
98
+ return meshes
99
+
100
+
101
+ class VoxelChunk:
102
+ __slots__ = ("cx", "cy", "cz", "meshes", "dirty")
103
+
104
+ def __init__(self, cx, cy, cz):
105
+ self.cx, self.cy, self.cz = cx, cy, cz
106
+ self.meshes: Dict[int, Mesh] = {}
107
+ self.dirty = True
108
+
109
+
110
+ class ChunkedVoxelMesher:
111
+ """
112
+ Incrementally rebuilds only dirty 16³ chunks instead of the whole world.
113
+ Uses Rust mesh builder when the prebuilt extension is installed.
114
+ """
115
+
116
+ CHUNK_SIZE = 16
117
+
118
+ def __init__(
119
+ self,
120
+ get_block,
121
+ block_types=None,
122
+ chunk_size: int = 16,
123
+ ):
124
+ self.get_block = get_block
125
+ self.block_types = block_types or set()
126
+ self.chunk_size = chunk_size
127
+ self.chunks: Dict[Vec3i, VoxelChunk] = {}
128
+ self._dirty: set[Vec3i] = set()
129
+ self.faces = DEFAULT_FACES
130
+ self._use_rust = is_native_available()
131
+
132
+ def _chunk_of(self, x: int, y: int, z: int) -> Vec3i:
133
+ s = self.chunk_size
134
+ return (x // s, y // s, z // s)
135
+
136
+ def mark_dirty_at(self, x: int, y: int, z: int):
137
+ cx, cy, cz = self._chunk_of(x, y, z)
138
+ for dx in (-1, 0, 1):
139
+ for dy in (-1, 0, 1):
140
+ for dz in (-1, 0, 1):
141
+ key = (cx + dx, cy + dy, cz + dz)
142
+ self._dirty.add(key)
143
+ if key in self.chunks:
144
+ self.chunks[key].dirty = True
145
+
146
+ def rebuild_all(self):
147
+ self._dirty.clear()
148
+ for key in list(self.chunks.keys()):
149
+ self._dirty.add(key)
150
+
151
+ def mark_all_dirty(self):
152
+ for ch in self.chunks.values():
153
+ ch.dirty = True
154
+ self._dirty = set(self.chunks.keys())
155
+
156
+ def notify_block_change(self, x: int, y: int, z: int):
157
+ self.mark_dirty_at(x, y, z)
158
+
159
+ def _build_chunk_mesh(self, cx: int, cy: int, cz: int) -> Dict[int, Mesh]:
160
+ s = self.chunk_size
161
+ x0, y0, z0 = cx * s, cy * s, cz * s
162
+ x1, y1, z1 = x0 + s, y0 + s, z0 + s
163
+
164
+ if self._use_rust:
165
+ fresh = _rust_build_chunk(
166
+ self.get_block, x0, y0, z0, x1, y1, z1, self.block_types or None
167
+ )
168
+ else:
169
+ fresh = _python_build_chunk(
170
+ self.get_block, x0, y0, z0, x1, y1, z1, self.block_types or None, self.faces
171
+ )
172
+
173
+ chunk = self.chunks.get((cx, cy, cz))
174
+ meshes = {}
175
+ for bid, mesh in fresh.items():
176
+ old = chunk.meshes.get(bid) if chunk else None
177
+ if old is not None:
178
+ old.update_data(mesh.vertices, mesh.indices)
179
+ meshes[bid] = old
180
+ else:
181
+ meshes[bid] = mesh
182
+ return meshes
183
+
184
+ def flush_dirty(self):
185
+ out = {}
186
+ removed = []
187
+ for key in list(self._dirty):
188
+ cx, cy, cz = key
189
+ if key not in self.chunks:
190
+ self.chunks[key] = VoxelChunk(cx, cy, cz)
191
+ ch = self.chunks[key]
192
+ old_bids = set(ch.meshes.keys())
193
+ new_meshes = self._build_chunk_mesh(cx, cy, cz)
194
+ new_bids = set(new_meshes.keys())
195
+ for bid in old_bids - new_bids:
196
+ old = ch.meshes.get(bid)
197
+ if old:
198
+ old.destroy()
199
+ removed.append((cx, cy, cz, bid))
200
+ for old in ch.meshes.values():
201
+ if old not in new_meshes.values():
202
+ old.destroy()
203
+ ch.meshes = new_meshes
204
+ ch.dirty = False
205
+ for bid, mesh in new_meshes.items():
206
+ out[(cx, cy, cz, bid)] = mesh
207
+ self._dirty.discard(key)
208
+ return out, removed
209
+
210
+ def get_all_meshes(self) -> Dict[Tuple[int, int, int, int], Mesh]:
211
+ result = {}
212
+ for key, ch in self.chunks.items():
213
+ for bid, mesh in ch.meshes.items():
214
+ cx, cy, cz = key
215
+ result[(cx, cy, cz, bid)] = mesh
216
+ return result
217
+
218
+ def ensure_chunk(self, x: int, y: int, z: int):
219
+ key = self._chunk_of(x, y, z)
220
+ if key not in self.chunks:
221
+ self.chunks[key] = VoxelChunk(*key)
222
+ self._dirty.add(key)
223
+
224
+ def prime_chunks(self, blocks: Dict[Vec3i, int]):
225
+ for (x, y, z) in blocks.keys():
226
+ self.ensure_chunk(x, y, z)
227
+ self.mark_dirty_at(x, y, z)