quantum-framework 0.18.0__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.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1449 @@
1
+ """
2
+ Godot 4 - GDScript/TSCN Templates and Builders
3
+
4
+ Provides:
5
+ - GdScriptBuilder: Structured GDScript code emitter with proper indentation
6
+ - TscnBuilder: Godot .tscn scene file builder (text-based format)
7
+ - GDScript templates for runtime scripts (bridge, event bus, player controller, etc.)
8
+ - Scale constants for Quantum → Godot unit conversion
9
+ """
10
+
11
+ import json
12
+ import re
13
+ from typing import List, Dict, Any, Optional, Tuple
14
+
15
+
16
+ # ==========================================================================
17
+ # SNES Hardware Constants (Super Nintendo Entertainment System)
18
+ # ==========================================================================
19
+ # Source: SNES dev wiki, SMW disassembly, TASVideos
20
+ # Resolution: 256×224 (Mode 1, NTSC)
21
+ # Tile size: 16×16 px (BG), sprites 8/16/32/64
22
+ # Framerate: 60 fps (NTSC)
23
+ # Subpixel resolution: 1/16 pixel (4-bit fractional)
24
+
25
+ SNES_WIDTH = 256
26
+ SNES_HEIGHT = 224
27
+ SNES_TILE = 16
28
+ SNES_FPS = 60
29
+ SNES_WINDOW_SCALE = 3 # 3x upscale → 768×672 window
30
+
31
+ # ==========================================================================
32
+ # SMW Physics Constants (for Godot: px/s and px/s² at 60fps)
33
+ # ==========================================================================
34
+ # Derived from SMW disassembly + frame analysis:
35
+ # Walk max: ~21 subpx/frame = 1.3 px/frame → 78.75 px/s
36
+ # Run max: ~37 subpx/frame = 2.3 px/frame → 138.75 px/s
37
+ # Sprint: ~49 subpx/frame = 3.1 px/frame → 183.75 px/s
38
+ # Max fall: ~64 subpx/frame = 4.0 px/frame → 240 px/s
39
+ # Jump initial: ~-5 px/frame → -300 px/s
40
+ # Gravity rise (A held): ~0.19 px/frame² → 675 px/s²
41
+ # Gravity fall: ~0.44 px/frame² → 1575 px/s²
42
+ #
43
+ # Tuned values (balanced for playability in Godot):
44
+
45
+ SMW_WALK_SPEED = 90.0 # px/s (1.5 px/frame)
46
+ SMW_JUMP_VELOCITY = -280.0 # px/s (initial upward, negative = up)
47
+ SMW_GRAVITY = 750.0 # px/s² (project default)
48
+ SMW_GRAVITY_RISING = 600.0 # px/s² (holding jump, lighter feel)
49
+ SMW_GRAVITY_FALLING = 1050.0 # px/s² (falling, snappy)
50
+ SMW_MAX_FALL = 240.0 # px/s (4 px/frame terminal velocity)
51
+ SMW_JUMP_HOLD_BOOST = 50.0 # px/s² (extra lift while holding jump)
52
+ SMW_COYOTE_FRAMES = 6 # frames of grace after leaving edge
53
+
54
+ # Validation: jump height = v² / (2 * g_rise) = 280² / (2*600) = 65px ≈ 4 tiles ✓
55
+ # With hold boost over 0.4s rise: ~80px ≈ 5 tiles ✓ (SMW standard)
56
+
57
+ # ==========================================================================
58
+ # Legacy scale helpers (kept for backward compat with tests)
59
+ # ==========================================================================
60
+
61
+ QUANTUM_SCALE = 100 # Legacy — NOT used for positions or SNES physics
62
+
63
+
64
+ def q2g(value: float) -> float:
65
+ """Convert Quantum abstract unit to Godot value (legacy)."""
66
+ return value * QUANTUM_SCALE
67
+
68
+
69
+ def q2g_int(value: float) -> int:
70
+ """Convert Quantum abstract unit to integer (legacy)."""
71
+ return int(value * QUANTUM_SCALE)
72
+
73
+
74
+ def q2g_gravity(value: float) -> float:
75
+ """Convert Quantum gravity to Godot gravity (legacy)."""
76
+ return value * QUANTUM_SCALE
77
+
78
+
79
+ def q2g_speed(value: float) -> float:
80
+ """Convert Quantum speed to Godot speed (legacy)."""
81
+ return value * QUANTUM_SCALE
82
+
83
+
84
+ def q2g_jump(value: float) -> float:
85
+ """Convert Quantum jump force to Godot velocity (legacy)."""
86
+ return -(value * QUANTUM_SCALE * 0.6)
87
+
88
+
89
+ # ==========================================================================
90
+ # GdScriptBuilder - Structured GDScript code emitter
91
+ # ==========================================================================
92
+
93
+ class GdScriptBuilder:
94
+ """Builds syntactically valid GDScript code with proper indentation.
95
+
96
+ Every append method returns self for chaining.
97
+ """
98
+
99
+ def __init__(self, indent_char: str = '\t'):
100
+ self._lines: list[str] = []
101
+ self._indent: int = 0
102
+ self._indent_char: str = indent_char
103
+
104
+ # -- core --
105
+
106
+ @property
107
+ def _pad(self) -> str:
108
+ return self._indent_char * self._indent
109
+
110
+ def line(self, code: str = '') -> 'GdScriptBuilder':
111
+ """Emit a single line at current indent."""
112
+ self._lines.append(f"{self._pad}{code}" if code else '')
113
+ return self
114
+
115
+ def raw(self, code: str) -> 'GdScriptBuilder':
116
+ """Emit raw code preserving its own indentation at current base indent."""
117
+ for ln in code.split('\n'):
118
+ self._lines.append(f"{self._pad}{ln}")
119
+ return self
120
+
121
+ def raw_unindented(self, code: str) -> 'GdScriptBuilder':
122
+ """Emit raw code without adding base indentation."""
123
+ for ln in code.split('\n'):
124
+ self._lines.append(ln)
125
+ return self
126
+
127
+ def blank(self) -> 'GdScriptBuilder':
128
+ self._lines.append('')
129
+ return self
130
+
131
+ def comment(self, text: str) -> 'GdScriptBuilder':
132
+ return self.line(f"# {text}")
133
+
134
+ def section(self, title: str) -> 'GdScriptBuilder':
135
+ return self.blank().line(f"# === {title} ===")
136
+
137
+ def indent(self) -> 'GdScriptBuilder':
138
+ self._indent += 1
139
+ return self
140
+
141
+ def dedent(self) -> 'GdScriptBuilder':
142
+ self._indent = max(0, self._indent - 1)
143
+ return self
144
+
145
+ # -- GDScript specific --
146
+
147
+ def extends(self, base_class: str) -> 'GdScriptBuilder':
148
+ return self.line(f"extends {base_class}")
149
+
150
+ def class_name(self, name: str) -> 'GdScriptBuilder':
151
+ return self.line(f"class_name {name}")
152
+
153
+ def var(self, name: str, value: str = '', type_hint: str = '') -> 'GdScriptBuilder':
154
+ if type_hint and value:
155
+ return self.line(f"var {name}: {type_hint} = {value}")
156
+ elif type_hint:
157
+ return self.line(f"var {name}: {type_hint}")
158
+ elif value:
159
+ return self.line(f"var {name} = {value}")
160
+ else:
161
+ return self.line(f"var {name}")
162
+
163
+ def const(self, name: str, value: str) -> 'GdScriptBuilder':
164
+ return self.line(f"const {name} = {value}")
165
+
166
+ def assign(self, target: str, value: str) -> 'GdScriptBuilder':
167
+ return self.line(f"{target} = {value}")
168
+
169
+ def export_var(self, name: str, type_hint: str, value: str = '') -> 'GdScriptBuilder':
170
+ if value:
171
+ return self.line(f"@export var {name}: {type_hint} = {value}")
172
+ return self.line(f"@export var {name}: {type_hint}")
173
+
174
+ def onready_var(self, name: str, path: str) -> 'GdScriptBuilder':
175
+ return self.line(f'@onready var {name} = ${path}')
176
+
177
+ def signal_decl(self, name: str, params: str = '') -> 'GdScriptBuilder':
178
+ if params:
179
+ return self.line(f"signal {name}({params})")
180
+ return self.line(f"signal {name}")
181
+
182
+ def preload(self, name: str, path: str) -> 'GdScriptBuilder':
183
+ return self.line(f'var {name} = preload("{path}")')
184
+
185
+ # -- blocks --
186
+
187
+ def func(self, name: str, params: str = '', return_type: str = '') -> 'GdScriptBuilder':
188
+ """Open a function block."""
189
+ sig = f"func {name}({params})"
190
+ if return_type:
191
+ sig += f" -> {return_type}"
192
+ sig += ":"
193
+ self.line(sig)
194
+ self._indent += 1
195
+ return self
196
+
197
+ def func_close(self) -> 'GdScriptBuilder':
198
+ """Close a function block (just dedent in GDScript)."""
199
+ self._indent = max(0, self._indent - 1)
200
+ return self
201
+
202
+ def if_block(self, condition: str) -> 'GdScriptBuilder':
203
+ self.line(f"if {condition}:")
204
+ self._indent += 1
205
+ return self
206
+
207
+ def elif_block(self, condition: str) -> 'GdScriptBuilder':
208
+ self._indent = max(0, self._indent - 1)
209
+ self.line(f"elif {condition}:")
210
+ self._indent += 1
211
+ return self
212
+
213
+ def else_block(self) -> 'GdScriptBuilder':
214
+ self._indent = max(0, self._indent - 1)
215
+ self.line("else:")
216
+ self._indent += 1
217
+ return self
218
+
219
+ def for_block(self, var: str, iterable: str) -> 'GdScriptBuilder':
220
+ self.line(f"for {var} in {iterable}:")
221
+ self._indent += 1
222
+ return self
223
+
224
+ def while_block(self, condition: str) -> 'GdScriptBuilder':
225
+ self.line(f"while {condition}:")
226
+ self._indent += 1
227
+ return self
228
+
229
+ def match_block(self, value: str) -> 'GdScriptBuilder':
230
+ self.line(f"match {value}:")
231
+ self._indent += 1
232
+ return self
233
+
234
+ def match_case(self, pattern: str) -> 'GdScriptBuilder':
235
+ self.line(f"{pattern}:")
236
+ self._indent += 1
237
+ return self
238
+
239
+ def block_close(self) -> 'GdScriptBuilder':
240
+ """Generic block close (dedent)."""
241
+ self._indent = max(0, self._indent - 1)
242
+ return self
243
+
244
+ def pass_stmt(self) -> 'GdScriptBuilder':
245
+ return self.line("pass")
246
+
247
+ def return_stmt(self, value: str = '') -> 'GdScriptBuilder':
248
+ if value:
249
+ return self.line(f"return {value}")
250
+ return self.line("return")
251
+
252
+ def build(self) -> str:
253
+ """Return the complete GDScript source."""
254
+ return '\n'.join(self._lines) + '\n'
255
+
256
+
257
+ # ==========================================================================
258
+ # TscnBuilder - Godot .tscn scene file builder
259
+ # ==========================================================================
260
+
261
+ class TscnBuilder:
262
+ """Builds Godot .tscn (text scene) files programmatically.
263
+
264
+ TSCN format:
265
+ [gd_scene load_steps=N format=3 uid="uid://..."]
266
+ [ext_resource type="..." path="..." id="1"]
267
+ [sub_resource type="..." id="sub_1"]
268
+ [node name="Root" type="Node2D"]
269
+ [node name="Child" type="Sprite2D" parent="."]
270
+ """
271
+
272
+ def __init__(self):
273
+ self._ext_resources: List[Dict[str, str]] = []
274
+ self._sub_resources: List[Dict[str, Any]] = []
275
+ self._nodes: List[Dict[str, Any]] = []
276
+ self._ext_id_counter: int = 0
277
+ self._sub_id_counter: int = 0
278
+ self._ext_resource_cache: Dict[str, str] = {} # path → id
279
+
280
+ def add_ext_resource(self, res_type: str, path: str) -> str:
281
+ """Add an external resource. Returns the resource ID string."""
282
+ if path in self._ext_resource_cache:
283
+ return self._ext_resource_cache[path]
284
+ self._ext_id_counter += 1
285
+ rid = str(self._ext_id_counter)
286
+ self._ext_resources.append({
287
+ 'type': res_type,
288
+ 'path': path,
289
+ 'id': rid,
290
+ })
291
+ self._ext_resource_cache[path] = rid
292
+ return rid
293
+
294
+ def add_sub_resource(self, res_type: str, properties: Dict[str, Any] = None) -> str:
295
+ """Add a sub-resource. Returns the sub-resource ID string."""
296
+ self._sub_id_counter += 1
297
+ sid = f"SubResource(\"{self._sub_id_counter}\")"
298
+ self._sub_resources.append({
299
+ 'type': res_type,
300
+ 'id': str(self._sub_id_counter),
301
+ 'properties': properties or {},
302
+ })
303
+ return sid
304
+
305
+ def add_node(self, name: str, node_type: str, parent: Optional[str] = None,
306
+ properties: Dict[str, Any] = None, groups: List[str] = None,
307
+ instance: Optional[str] = None) -> 'TscnBuilder':
308
+ """Add a scene node."""
309
+ self._nodes.append({
310
+ 'name': name,
311
+ 'type': node_type,
312
+ 'parent': parent,
313
+ 'properties': properties or {},
314
+ 'groups': groups or [],
315
+ 'instance': instance,
316
+ })
317
+ return self
318
+
319
+ def add_node_with_script(self, name: str, node_type: str, script_path: str,
320
+ parent: Optional[str] = None,
321
+ properties: Dict[str, Any] = None,
322
+ groups: List[str] = None) -> 'TscnBuilder':
323
+ """Add a scene node with an attached script."""
324
+ script_id = self.add_ext_resource('Script', script_path)
325
+ props = properties or {}
326
+ props['script'] = f'ExtResource("{script_id}")'
327
+ return self.add_node(name, node_type, parent, props, groups)
328
+
329
+ def build(self) -> str:
330
+ """Return the complete .tscn file content."""
331
+ lines = []
332
+ load_steps = 1 + len(self._ext_resources) + len(self._sub_resources)
333
+
334
+ # Header
335
+ lines.append(f'[gd_scene load_steps={load_steps} format=3]')
336
+ lines.append('')
337
+
338
+ # External resources
339
+ for res in self._ext_resources:
340
+ lines.append(
341
+ f'[ext_resource type="{res["type"]}" path="res://{res["path"]}" id="{res["id"]}"]'
342
+ )
343
+ if self._ext_resources:
344
+ lines.append('')
345
+
346
+ # Sub-resources
347
+ for sub in self._sub_resources:
348
+ props_str = self._format_sub_resource_props(sub['properties'])
349
+ lines.append(f'[sub_resource type="{sub["type"]}" id="{sub["id"]}"]')
350
+ if props_str:
351
+ lines.append(props_str)
352
+ lines.append('')
353
+
354
+ # Nodes
355
+ for node in self._nodes:
356
+ lines.append(self._format_node(node))
357
+ props_lines = self._format_node_props(node['properties'])
358
+ if props_lines:
359
+ lines.append(props_lines)
360
+ lines.append('')
361
+
362
+ return '\n'.join(lines)
363
+
364
+ def _format_node(self, node: Dict[str, Any]) -> str:
365
+ """Format a [node ...] header line."""
366
+ parts = [f'name="{node["name"]}"']
367
+
368
+ if node.get('instance'):
369
+ parts.append(f'instance={node["instance"]}')
370
+ elif node['type']:
371
+ parts.append(f'type="{node["type"]}"')
372
+
373
+ if node['parent'] is not None:
374
+ parts.append(f'parent="{node["parent"]}"')
375
+
376
+ if node.get('groups'):
377
+ groups_str = ', '.join(f'"{g}"' for g in node['groups'])
378
+ parts.append(f'groups=[{groups_str}]')
379
+
380
+ return '[node ' + ' '.join(parts) + ']'
381
+
382
+ def _format_node_props(self, props: Dict[str, Any]) -> str:
383
+ """Format node properties as key = value lines."""
384
+ lines = []
385
+ for key, value in props.items():
386
+ lines.append(f'{key} = {self._format_value(value)}')
387
+ return '\n'.join(lines)
388
+
389
+ def _format_sub_resource_props(self, props: Dict[str, Any]) -> str:
390
+ """Format sub-resource properties."""
391
+ lines = []
392
+ for key, value in props.items():
393
+ lines.append(f'{key} = {self._format_value(value)}')
394
+ return '\n'.join(lines)
395
+
396
+ def _format_value(self, value: Any) -> str:
397
+ """Format a value for .tscn file."""
398
+ if isinstance(value, bool):
399
+ return 'true' if value else 'false'
400
+ elif isinstance(value, int):
401
+ return str(value)
402
+ elif isinstance(value, float):
403
+ return f'{value:.6g}'
404
+ elif isinstance(value, str):
405
+ # Check for special Godot references
406
+ if value.startswith('ExtResource(') or value.startswith('SubResource('):
407
+ return value
408
+ if value.startswith('Vector2(') or value.startswith('Vector2i('):
409
+ return value
410
+ if value.startswith('Rect2(') or value.startswith('Color('):
411
+ return value
412
+ if value.startswith('PackedScene('):
413
+ return value
414
+ return f'"{value}"'
415
+ elif isinstance(value, list):
416
+ items = ', '.join(self._format_value(v) for v in value)
417
+ return f'[{items}]'
418
+ elif isinstance(value, dict):
419
+ items = ', '.join(
420
+ f'{self._format_value(k)}: {self._format_value(v)}'
421
+ for k, v in value.items()
422
+ )
423
+ return f'{{{items}}}'
424
+ else:
425
+ return str(value)
426
+
427
+
428
+ # ==========================================================================
429
+ # GDScript helper functions
430
+ # ==========================================================================
431
+
432
+ def gd_string(s: str) -> str:
433
+ """Escape a string for GDScript."""
434
+ return '"' + s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') + '"'
435
+
436
+
437
+ def gd_bool(b: bool) -> str:
438
+ """Convert Python bool to GDScript bool literal."""
439
+ return 'true' if b else 'false'
440
+
441
+
442
+ def gd_vector2(x: float, y: float) -> str:
443
+ """Format a Vector2."""
444
+ return f'Vector2({x:.6g}, {y:.6g})'
445
+
446
+
447
+ def gd_vector2i(x: int, y: int) -> str:
448
+ """Format a Vector2i."""
449
+ return f'Vector2i({x}, {y})'
450
+
451
+
452
+ def gd_color(hex_color: str) -> str:
453
+ """Convert hex color to Godot Color."""
454
+ c = hex_color.lstrip('#')
455
+ if len(c) == 3:
456
+ c = ''.join(ch * 2 for ch in c)
457
+ if len(c) == 6:
458
+ r = int(c[0:2], 16) / 255.0
459
+ g = int(c[2:4], 16) / 255.0
460
+ b = int(c[4:6], 16) / 255.0
461
+ return f'Color({r:.3f}, {g:.3f}, {b:.3f}, 1.0)'
462
+ return f'Color("{hex_color}")'
463
+
464
+
465
+ # ==========================================================================
466
+ # Easing name mapping: Quantum → Godot
467
+ # ==========================================================================
468
+
469
+ EASING_MAP = {
470
+ 'linear': 'Tween.EASE_IN', # Godot TRANS_LINEAR
471
+ 'ease-in': 'Tween.EASE_IN',
472
+ 'ease-out': 'Tween.EASE_OUT',
473
+ 'ease-in-out': 'Tween.EASE_IN_OUT',
474
+ 'bounce': 'Tween.EASE_OUT', # with TRANS_BOUNCE
475
+ }
476
+
477
+ TRANS_MAP = {
478
+ 'linear': 'Tween.TRANS_LINEAR',
479
+ 'ease-in': 'Tween.TRANS_QUAD',
480
+ 'ease-out': 'Tween.TRANS_QUAD',
481
+ 'ease-in-out': 'Tween.TRANS_QUAD',
482
+ 'bounce': 'Tween.TRANS_BOUNCE',
483
+ }
484
+
485
+
486
+ # ==========================================================================
487
+ # project.godot template
488
+ # ==========================================================================
489
+
490
+ def build_project_godot(config: Dict[str, Any]) -> str:
491
+ """Generate project.godot file content."""
492
+ lines = []
493
+ lines.append('; Engine configuration file.')
494
+ lines.append('; Generated by Quantum Framework - Godot Backend')
495
+ lines.append('')
496
+ lines.append('config_version=5')
497
+ lines.append('')
498
+
499
+ # [application]
500
+ lines.append('[application]')
501
+ lines.append('')
502
+ lines.append(f'config/name="{config.get("name", "Quantum Game")}"')
503
+ lines.append(f'run/main_scene="res://{config.get("main_scene", "main.tscn")}"')
504
+ lines.append('config/features=PackedStringArray("4.4")')
505
+ lines.append('')
506
+
507
+ # [autoload]
508
+ autoloads = config.get('autoloads', {})
509
+ if autoloads:
510
+ lines.append('[autoload]')
511
+ lines.append('')
512
+ for name, path in autoloads.items():
513
+ lines.append(f'{name}="*res://{path}"')
514
+ lines.append('')
515
+
516
+ # [display]
517
+ lines.append('[display]')
518
+ lines.append('')
519
+ vp_width = config.get('viewport_width', SNES_WIDTH)
520
+ vp_height = config.get('viewport_height', SNES_HEIGHT)
521
+ lines.append(f'window/size/viewport_width={vp_width}')
522
+ lines.append(f'window/size/viewport_height={vp_height}')
523
+
524
+ # Window override: scale up for modern monitors
525
+ scale = config.get('window_scale', SNES_WINDOW_SCALE)
526
+ win_width = vp_width * scale
527
+ win_height = vp_height * scale
528
+ lines.append(f'window/size/window_width_override={win_width}')
529
+ lines.append(f'window/size/window_height_override={win_height}')
530
+
531
+ # Stretch: "viewport" = pixel-perfect upscale (no subpixel blending)
532
+ stretch_mode = config.get('stretch_mode', 'viewport')
533
+ lines.append(f'window/stretch/mode="{stretch_mode}"')
534
+ stretch_aspect = config.get('stretch_aspect', 'keep')
535
+ lines.append(f'window/stretch/aspect="{stretch_aspect}"')
536
+ lines.append('')
537
+
538
+ # [physics]
539
+ lines.append('[physics]')
540
+ lines.append('')
541
+ gravity = config.get('gravity', 980.0)
542
+ lines.append(f'2d/default_gravity={gravity:.1f}')
543
+ lines.append('')
544
+
545
+ # [input] - input map
546
+ input_map = config.get('input_map', {})
547
+ if input_map:
548
+ lines.append('[input]')
549
+ lines.append('')
550
+ for action_name, keys in input_map.items():
551
+ events = []
552
+ for key in keys:
553
+ events.append(_input_event_for_key(key))
554
+ events_str = ', '.join(events)
555
+ lines.append(f'{action_name}={{"deadzone": 0.5, "events": [{events_str}]}}')
556
+ lines.append('')
557
+
558
+ # [layer_names]
559
+ collision_layer_names = config.get('collision_layer_names', {})
560
+ if collision_layer_names:
561
+ lines.append('[layer_names]')
562
+ lines.append('')
563
+ # Sort by layer id value, map name->id to id->name
564
+ id_to_name = {}
565
+ for name, layer_id in collision_layer_names.items():
566
+ # Convert bitmask to layer number (1-based)
567
+ bit = 1
568
+ while (1 << (bit - 1)) < layer_id:
569
+ bit += 1
570
+ if (1 << (bit - 1)) == layer_id:
571
+ id_to_name[bit] = name
572
+ else:
573
+ # Not a power of 2, use as direct layer number
574
+ id_to_name[layer_id] = name
575
+ for layer_num in sorted(id_to_name.keys()):
576
+ lines.append(f'2d_physics/layer_{layer_num}="{id_to_name[layer_num]}"')
577
+ lines.append('')
578
+ else:
579
+ # Default SMW layer names
580
+ lines.append('[layer_names]')
581
+ lines.append('')
582
+ lines.append('2d_physics/layer_1="world"')
583
+ lines.append('2d_physics/layer_2="player"')
584
+ lines.append('2d_physics/layer_3="enemies"')
585
+ lines.append('2d_physics/layer_4="collectibles"')
586
+ lines.append('2d_physics/layer_5="sensors"')
587
+ lines.append('')
588
+
589
+ # [rendering]
590
+ lines.append('[rendering]')
591
+ lines.append('')
592
+ lines.append('textures/canvas_textures/default_texture_filter=0')
593
+ lines.append('')
594
+
595
+ return '\n'.join(lines)
596
+
597
+
598
+ def _input_event_for_key(key: str) -> str:
599
+ """Convert a key name to a Godot InputEvent resource string.
600
+
601
+ Godot 4 project.godot uses integer physical keycodes, not symbolic names.
602
+ """
603
+ # Godot 4 physical keycode integer values
604
+ key_map = {
605
+ 'ArrowLeft': 4194319, 'ArrowRight': 4194321,
606
+ 'ArrowUp': 4194320, 'ArrowDown': 4194322,
607
+ 'a': 65, 'd': 68, 'w': 87, 's': 83,
608
+ 'Space': 32, ' ': 32,
609
+ 'Enter': 4194309, 'Escape': 4194305,
610
+ 'Shift': 4194325, 'Control': 4194326,
611
+ 'z': 90, 'x': 88, 'c': 67,
612
+ }
613
+ godot_key = key_map.get(key, ord(key.upper()) if len(key) == 1 else 0)
614
+ return f'Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":{godot_key},"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)'
615
+
616
+
617
+ # ==========================================================================
618
+ # export_presets.cfg template
619
+ # ==========================================================================
620
+
621
+ def build_export_presets() -> str:
622
+ """Generate export_presets.cfg for HTML5 web export."""
623
+ return """[preset.0]
624
+
625
+ name="Web"
626
+ platform="Web"
627
+ runnable=true
628
+ dedicated_server=false
629
+ custom_features=""
630
+ export_filter="all_resources"
631
+ include_filter=""
632
+ exclude_filter=""
633
+ export_path="dist/index.html"
634
+ encryption_include_filters=""
635
+ encryption_exclude_filters=""
636
+ encrypt_pck=false
637
+ encrypt_directory=false
638
+
639
+ [preset.0.options]
640
+
641
+ custom_template/debug=""
642
+ custom_template/release=""
643
+ variant/extensions_support=false
644
+ vram_texture_compression/for_desktop=true
645
+ vram_texture_compression/for_mobile=false
646
+ html/export_icon=true
647
+ html/custom_html_shell=""
648
+ html/head_include=""
649
+ html/canvas_resize_policy=2
650
+ html/focus_canvas_on_start=true
651
+ html/experimental_virtual_keyboard=false
652
+ progressive_web_app/enabled=false
653
+ """
654
+
655
+
656
+ # ==========================================================================
657
+ # GDScript templates for runtime scripts
658
+ # ==========================================================================
659
+
660
+ QUANTUM_BRIDGE_GD = '''\
661
+ extends Node
662
+ ## Quantum Bridge - Game API autoload
663
+ ## Provides centralized game object management and utility functions.
664
+
665
+ var sprites: Dictionary = {}
666
+ var event_bus: Node
667
+ var game_state: Dictionary = {}
668
+
669
+ func save_game_state(data: Dictionary):
670
+ \tgame_state = data.duplicate()
671
+
672
+ func get_game_state() -> Dictionary:
673
+ \treturn game_state
674
+
675
+ func clear_game_state():
676
+ \tgame_state = {}
677
+
678
+ func _ready():
679
+ \tevent_bus = get_node("/root/QuantumEventBus")
680
+
681
+ func register_sprite(id: String, node: Node):
682
+ \tsprites[id] = node
683
+
684
+ func destroy_sprite(id: String):
685
+ \tif id in sprites:
686
+ \t\tsprites[id].queue_free()
687
+ \t\tsprites.erase(id)
688
+
689
+ func respawn(id: String, x: float, y: float):
690
+ \tif id in sprites:
691
+ \t\tsprites[id].position = Vector2(x, y)
692
+ \t\tif sprites[id] is CharacterBody2D:
693
+ \t\t\tsprites[id].velocity = Vector2.ZERO
694
+
695
+ func get_sprite(id: String) -> Node:
696
+ \tif id in sprites:
697
+ \t\treturn sprites[id]
698
+ \treturn null
699
+
700
+ func emit_event(event_name: String, data: Dictionary = {}):
701
+ \tif event_bus:
702
+ \t\tevent_bus.emit_event(event_name, data)
703
+
704
+ func camera_shake(intensity: float = 5.0, duration: float = 0.3):
705
+ \tvar cam = get_viewport().get_camera_2d()
706
+ \tif cam and cam.has_method("shake"):
707
+ \t\tcam.shake(intensity, duration)
708
+
709
+ func set_patrol_ai(id: String, speed: float):
710
+ \tif id in sprites and sprites[id].has_method("set_patrol"):
711
+ \t\tsprites[id].set_patrol(speed)
712
+
713
+ func kill_player(id: String):
714
+ \tif id in sprites and sprites[id].has_method("die"):
715
+ \t\tsprites[id].die()
716
+
717
+ func play_sound(id: String, opts: Dictionary = {}):
718
+ \tvar player = get_node_or_null("/root/Main/Sounds/" + id)
719
+ \tif player and player is AudioStreamPlayer:
720
+ \t\tif opts.has("volume"):
721
+ \t\t\tplayer.volume_db = linear_to_db(opts["volume"])
722
+ \t\tplayer.play()
723
+
724
+ func pause():
725
+ \tget_tree().paused = true
726
+
727
+ func resume():
728
+ \tget_tree().paused = false
729
+
730
+ func load_scene(scene_name: String):
731
+ \tvar path = "res://scenes/" + scene_name + ".tscn"
732
+ \tget_tree().change_scene_to_file(path)
733
+
734
+ func iris_in(duration: float = 0.5):
735
+ \temit_event("iris_in", {"duration": duration})
736
+
737
+ func iris_out(duration: float = 0.5):
738
+ \temit_event("iris_out", {"duration": duration})
739
+
740
+ func save_state(key: String, value) -> void:
741
+ \tvar save_data: Dictionary = {}
742
+ \tvar path = "user://quantum_save.json"
743
+ \tif FileAccess.file_exists(path):
744
+ \t\tvar f = FileAccess.open(path, FileAccess.READ)
745
+ \t\tsave_data = JSON.parse_string(f.get_as_text()) if f else {}
746
+ \t\tif f: f.close()
747
+ \tif save_data == null:
748
+ \t\tsave_data = {}
749
+ \tsave_data[key] = value
750
+ \tvar f = FileAccess.open(path, FileAccess.WRITE)
751
+ \tif f:
752
+ \t\tf.store_string(JSON.stringify(save_data))
753
+ \t\tf.close()
754
+
755
+ func load_state(key: String, default_value = null):
756
+ \tvar path = "user://quantum_save.json"
757
+ \tif not FileAccess.file_exists(path):
758
+ \t\treturn default_value
759
+ \tvar f = FileAccess.open(path, FileAccess.READ)
760
+ \tif not f:
761
+ \t\treturn default_value
762
+ \tvar data = JSON.parse_string(f.get_as_text())
763
+ \tf.close()
764
+ \tif data is Dictionary and data.has(key):
765
+ \t\treturn data[key]
766
+ \treturn default_value
767
+ '''
768
+
769
+
770
+ QUANTUM_EVENT_BUS_GD = '''\
771
+ extends Node
772
+ ## Quantum Event Bus - Global event system autoload
773
+ ## Allows decoupled communication between game objects.
774
+
775
+ var _listeners: Dictionary = {}
776
+
777
+ func emit_event(event_name: String, data: Dictionary = {}):
778
+ \tif event_name in _listeners:
779
+ \t\tfor callback in _listeners[event_name]:
780
+ \t\t\tif callback.is_valid():
781
+ \t\t\t\tcallback.call(data)
782
+
783
+ func listen(event_name: String, callback: Callable):
784
+ \tif event_name not in _listeners:
785
+ \t\t_listeners[event_name] = []
786
+ \t_listeners[event_name].append(callback)
787
+
788
+ func remove_listener(event_name: String, callback: Callable):
789
+ \tif event_name in _listeners:
790
+ \t\t_listeners[event_name].erase(callback)
791
+
792
+ func clear(event_name: String = ""):
793
+ \tif event_name:
794
+ \t\t_listeners.erase(event_name)
795
+ \telse:
796
+ \t\t_listeners.clear()
797
+ '''
798
+
799
+
800
+ PLAYER_CONTROLLER_GD = '''\
801
+ extends CharacterBody2D
802
+ ## Player Controller - SMW-style platformer physics
803
+ ## Generated by Quantum Framework
804
+
805
+ @export var move_speed: float = {speed}
806
+ @export var jump_velocity: float = {jump_velocity}
807
+ @export var gravity_up: float = {gravity_up}
808
+ @export var gravity_down: float = {gravity_down}
809
+ @export var jump_hold_boost: float = {jump_hold_boost}
810
+ @export var coyote_frames: int = {coyote_frames}
811
+ @export var max_fall_speed: float = {max_fall_speed}
812
+
813
+ var _coyote_counter: int = 0
814
+ var _jump_held: bool = false
815
+ var _facing_right: bool = true
816
+ var _stomp_bouncing: bool = false
817
+
818
+ # Death state
819
+ var is_dead: bool = false
820
+ var _death_texture: Texture2D = null
821
+ var _normal_texture: Texture2D = null
822
+
823
+ # Animation
824
+ var _anim_timer: float = 0.0
825
+ var _walk_frames: Array = {walk_frames}
826
+ var _walk_frame_idx: int = 0
827
+ var _walk_speed: float = {walk_anim_speed}
828
+ var _idle_frame: int = {idle_frame}
829
+ var _jump_frame: int = {jump_frame}
830
+
831
+ func _ready():
832
+ \tcollision_layer = {collision_layer}
833
+ \tcollision_mask = {collision_mask}
834
+ \tfloor_max_angle = deg_to_rad({floor_max_angle})
835
+ \tfloor_snap_length = 4.0
836
+ \tfloor_block_on_wall = true
837
+ \tmax_slides = 6
838
+
839
+ func _physics_process(delta: float):
840
+ \t# Death: only apply gravity and slide
841
+ \tif is_dead:
842
+ \t\tvelocity.y += gravity_down * delta
843
+ \t\tmove_and_slide()
844
+ \t\treturn
845
+
846
+ \t# Enemy hit cooldown
847
+ \tif _enemy_hit_cooldown > 0:
848
+ \t\t_enemy_hit_cooldown -= delta
849
+
850
+ \t# Gravity
851
+ \tif not is_on_floor():
852
+ \t\tvar grav = gravity_down if velocity.y > 0 else gravity_up
853
+ \t\tvelocity.y += grav * delta
854
+ \t\tvelocity.y = min(velocity.y, max_fall_speed)
855
+ \t\t_coyote_counter -= 1
856
+ \telse:
857
+ \t\tif _stomp_bouncing:
858
+ \t\t\t_stomp_bouncing = false
859
+ \t\telse:
860
+ \t\t\tvelocity.y = 0
861
+ \t\t\t_coyote_counter = coyote_frames
862
+
863
+ \t# Jump
864
+ \tif Input.is_action_just_pressed("jump") and _coyote_counter > 0:
865
+ \t\tvelocity.y = jump_velocity
866
+ \t\t_jump_held = true
867
+ \t\t_coyote_counter = 0
868
+ \t\tprint("[JUMP] vel_y=", velocity.y, " pos_y=", global_position.y)
869
+
870
+ \t# Variable jump height
871
+ \tif Input.is_action_just_released("jump") and velocity.y < 0:
872
+ \t\tvelocity.y *= 0.5
873
+ \t\t_jump_held = false
874
+
875
+ \t# Jump hold boost
876
+ \tif _jump_held and Input.is_action_pressed("jump") and velocity.y < 0:
877
+ \t\tvelocity.y -= jump_hold_boost * delta
878
+
879
+ \t# Horizontal movement
880
+ \tvar direction = Input.get_axis("move_left", "move_right")
881
+ \tif direction:
882
+ \t\tvelocity.x = direction * move_speed
883
+ \t\t_facing_right = direction > 0
884
+ \telse:
885
+ \t\tvelocity.x = move_toward(velocity.x, 0, move_speed * 0.2)
886
+
887
+ \tmove_and_slide()
888
+ \t_check_enemy_collisions()
889
+ \t_update_animation(delta)
890
+
891
+ var _enemy_hit_cooldown: float = 0.0
892
+
893
+ func _check_enemy_collisions():
894
+ \tif _enemy_hit_cooldown > 0:
895
+ \t\treturn
896
+ \tfor i in range(get_slide_collision_count()):
897
+ \t\tvar col = get_slide_collision(i)
898
+ \t\tvar collider = col.get_collider()
899
+ \t\tif not collider or not collider.has_meta("tag"):
900
+ \t\t\tcontinue
901
+ \t\tvar tag = collider.get_meta("tag")
902
+ \t\tvar normal = col.get_normal()
903
+ \t\tif tag == "enemy":
904
+ \t\t\t_enemy_hit_cooldown = 0.3
905
+ \t\t\tQuantumEventBus.emit_event("enemy-collision", {{
906
+ \t\t\t\t"other": collider,
907
+ \t\t\t\t"normal_y": normal.y
908
+ \t\t\t}})
909
+ \t\t\tbreak
910
+ \t\telif tag == "qblock" and normal.y > 0.5:
911
+ \t\t\tif collider.has_method("hit"):
912
+ \t\t\t\tcollider.hit()
913
+ \t\t\tQuantumEventBus.emit_event("qblock-hit", {{
914
+ \t\t\t\t"other": collider,
915
+ \t\t\t\t"normal_y": normal.y
916
+ \t\t\t}})
917
+ \t\telif tag == "rotating_block" and normal.y > 0.5:
918
+ \t\t\tif collider.has_method("hit"):
919
+ \t\t\t\tcollider.hit()
920
+ \t\t\tQuantumEventBus.emit_event("rotating-block-hit", {{
921
+ \t\t\t\t"other": collider,
922
+ \t\t\t\t"normal_y": normal.y
923
+ \t\t\t}})
924
+
925
+ func die():
926
+ \tif is_dead:
927
+ \t\treturn
928
+ \tis_dead = true
929
+ \tprocess_mode = Node.PROCESS_MODE_ALWAYS
930
+ \tvar sprite = get_node_or_null("Sprite2D")
931
+ \tif sprite and _death_texture:
932
+ \t\t_normal_texture = sprite.texture
933
+ \t\tsprite.texture = _death_texture
934
+ \t\tsprite.hframes = 1
935
+ \t\tsprite.vframes = 1
936
+ \t\tsprite.frame = 0
937
+ \t\tsprite.flip_h = false
938
+ \tvelocity = Vector2(0, -200)
939
+ \tset_deferred("collision_layer", 0)
940
+ \tset_deferred("collision_mask", 0)
941
+
942
+ func set_death_texture(tex: Texture2D):
943
+ \t_death_texture = tex
944
+
945
+ func stomp_bounce(vel: float):
946
+ \tprint("[STOMP_BOUNCE] before_vel_y=", velocity.y, " bounce_vel=", vel, " pos_y=", global_position.y)
947
+ \tvelocity.y = vel
948
+ \t_jump_held = false
949
+ \t_coyote_counter = 0
950
+ \t_stomp_bouncing = true
951
+
952
+ func _update_animation(delta: float):
953
+ \tvar sprite = get_node_or_null("Sprite2D")
954
+ \tif not sprite:
955
+ \t\treturn
956
+
957
+ \t# Flip sprite based on facing direction
958
+ \t# SMW spritesheets face LEFT by default, flip when facing right
959
+ \tsprite.flip_h = _facing_right
960
+
961
+ \t# Frame selection
962
+ \tif not is_on_floor():
963
+ \t\t# Airborne: jump frame
964
+ \t\tsprite.frame = _jump_frame
965
+ \t\t_anim_timer = 0.0
966
+ \t\t_walk_frame_idx = 0
967
+ \telif abs(velocity.x) > 5:
968
+ \t\t# Walking: cycle through walk frames
969
+ \t\t_anim_timer += delta
970
+ \t\tif _anim_timer >= _walk_speed:
971
+ \t\t\t_anim_timer = 0.0
972
+ \t\t\t_walk_frame_idx = (_walk_frame_idx + 1) % _walk_frames.size()
973
+ \t\tsprite.frame = _walk_frames[_walk_frame_idx]
974
+ \telse:
975
+ \t\t# Idle
976
+ \t\tsprite.frame = _idle_frame
977
+ \t\t_anim_timer = 0.0
978
+ \t\t_walk_frame_idx = 0
979
+ '''
980
+
981
+
982
+ # Spin jump section — appended to player controller when spin_jump=true
983
+ SPIN_JUMP_SECTION = '''
984
+
985
+ # --- Spin Jump (SMW-style) ---
986
+ var _is_spin_jumping: bool = false
987
+ var _spin_jump_velocity_mult: float = 0.85
988
+
989
+ func _unhandled_input(event):
990
+ \tif event.is_action_pressed("spin_jump") and is_on_floor() and not is_dead:
991
+ \t\tvelocity.y = jump_velocity * _spin_jump_velocity_mult
992
+ \t\t_is_spin_jumping = true
993
+ \t\t_jump_held = true
994
+ \t\t_coyote_counter = 0
995
+ \t\tQuantumBridge.play_sound("sfx-spin")
996
+
997
+ func _check_spin_landing():
998
+ \tif _is_spin_jumping and is_on_floor():
999
+ \t\t_is_spin_jumping = false
1000
+ \t\tvar sprite = get_node_or_null("Sprite2D")
1001
+ \t\tif sprite:
1002
+ \t\t\tsprite.rotation = 0.0
1003
+
1004
+ func _spin_visual(delta: float):
1005
+ \tif _is_spin_jumping:
1006
+ \t\tvar sprite = get_node_or_null("Sprite2D")
1007
+ \t\tif sprite:
1008
+ \t\t\tsprite.rotation += delta * 20.0
1009
+ '''
1010
+
1011
+
1012
+ CAMERA_FOLLOW_GD = '''\
1013
+ extends Camera2D
1014
+ ## Camera Follow - Smooth follow with bounds
1015
+ ## Generated by Quantum Framework
1016
+
1017
+ @export var follow_target: NodePath
1018
+ @export var smoothing_speed: float = {lerp_speed}
1019
+ @export var use_bounds: bool = {use_bounds}
1020
+ @export var bound_left: float = 0.0
1021
+ @export var bound_top: float = 0.0
1022
+ @export var bound_right: float = {bound_right}
1023
+ @export var bound_bottom: float = {bound_bottom}
1024
+
1025
+ var _shake_intensity: float = 0.0
1026
+ var _shake_timer: float = 0.0
1027
+ var _base_offset: Vector2
1028
+
1029
+ func _ready():
1030
+ \t_base_offset = offset
1031
+ \tposition_smoothing_enabled = true
1032
+ \tposition_smoothing_speed = smoothing_speed
1033
+
1034
+ \tif use_bounds:
1035
+ \t\tlimit_left = int(bound_left)
1036
+ \t\tlimit_top = int(bound_top)
1037
+ \t\tlimit_right = int(bound_right)
1038
+ \t\tlimit_bottom = int(bound_bottom)
1039
+
1040
+ func _process(delta: float):
1041
+ \tif _shake_timer > 0:
1042
+ \t\t_shake_timer -= delta
1043
+ \t\toffset = _base_offset + Vector2(
1044
+ \t\t\trandf_range(-_shake_intensity, _shake_intensity),
1045
+ \t\t\trandf_range(-_shake_intensity, _shake_intensity)
1046
+ \t\t)
1047
+ \telse:
1048
+ \t\toffset = _base_offset
1049
+
1050
+ func shake(intensity: float = 5.0, duration: float = 0.3):
1051
+ \t_shake_intensity = intensity
1052
+ \t_shake_timer = duration
1053
+ '''
1054
+
1055
+
1056
+ PATROL_AI_GD = '''\
1057
+ extends CharacterBody2D
1058
+ ## Patrol AI - Simple back-and-forth movement
1059
+ ## Generated by Quantum Framework
1060
+
1061
+ @export var patrol_speed: float = {speed}
1062
+ @export var turn_on_edge: bool = {turn_on_edge}
1063
+ @export var gravity: float = {gravity}
1064
+
1065
+ var _direction: float = {initial_direction}
1066
+
1067
+ func _physics_process(delta: float):
1068
+ \tif not is_on_floor():
1069
+ \t\tvelocity.y += gravity * delta
1070
+ \telse:
1071
+ \t\tvelocity.y = 0
1072
+
1073
+ \tvelocity.x = _direction * patrol_speed
1074
+
1075
+ \tif is_on_wall():
1076
+ \t\t_direction *= -1
1077
+
1078
+ \tif turn_on_edge and is_on_floor():
1079
+ \t\tvar ray_pos = Vector2(_direction * 16, 2)
1080
+ \t\tvar space = get_world_2d().direct_space_state
1081
+ \t\tvar query = PhysicsRayQueryParameters2D.create(
1082
+ \t\t\tglobal_position + ray_pos,
1083
+ \t\t\tglobal_position + ray_pos + Vector2(0, 32)
1084
+ \t\t)
1085
+ \t\tvar result = space.intersect_ray(query)
1086
+ \t\tif not result:
1087
+ \t\t\t_direction *= -1
1088
+
1089
+ \tmove_and_slide()
1090
+
1091
+ func set_patrol(speed: float):
1092
+ \tpatrol_speed = speed
1093
+
1094
+ func die():
1095
+ \tqueue_free()
1096
+ '''
1097
+
1098
+
1099
+ HUD_MANAGER_GD = '''\
1100
+ extends CanvasLayer
1101
+ ## HUD Manager - UI overlay
1102
+ ## Generated by Quantum Framework
1103
+
1104
+ {label_vars}
1105
+
1106
+ func _ready():
1107
+ {label_ready}
1108
+
1109
+ func update_label(label_name: String, value):
1110
+ \tvar label = get_node_or_null(label_name)
1111
+ \tif label and label is Label:
1112
+ \t\tlabel.text = str(value)
1113
+ '''
1114
+
1115
+
1116
+ SPRITE_ANIMATOR_GD = '''\
1117
+ extends {base_class}
1118
+ ## Sprite Animator - Auto-cycling sprite frames
1119
+ ## Generated by Quantum Framework
1120
+
1121
+ var _anim_timer: float = 0.0
1122
+ var _anim_frames: Array = {frames}
1123
+ var _anim_idx: int = 0
1124
+ var _anim_speed: float = {speed}
1125
+ var _sprite: Sprite2D
1126
+
1127
+ func _ready():
1128
+ \t_sprite = find_child("Sprite2D", true, false)
1129
+
1130
+ func _process(delta: float):
1131
+ \tif not _sprite or _anim_frames.size() <= 1:
1132
+ \t\treturn
1133
+ \t_anim_timer += delta
1134
+ \tif _anim_timer >= _anim_speed:
1135
+ \t\t_anim_timer = 0.0
1136
+ \t\t_anim_idx = (_anim_idx + 1) % _anim_frames.size()
1137
+ \t\t_sprite.frame = _anim_frames[_anim_idx]
1138
+ '''
1139
+
1140
+
1141
+ # ==========================================================================
1142
+ # SceneManager autoload — handles multi-scene transitions & persistent state
1143
+ # ==========================================================================
1144
+
1145
+ SCENE_MANAGER_GD = '''extends Node
1146
+ ## SceneManager — handles scene transitions and persistent state.
1147
+ ## Generated by Quantum Framework - Godot Backend
1148
+
1149
+ var persistent_state: Dictionary = {}
1150
+ var current_scene_name: String = ""
1151
+
1152
+ var _transition_layer: CanvasLayer
1153
+ var _color_rect: ColorRect
1154
+ var _transitioning: bool = false
1155
+
1156
+ func _ready():
1157
+ \t_setup_transition_layer()
1158
+
1159
+ func _setup_transition_layer():
1160
+ \t_transition_layer = CanvasLayer.new()
1161
+ \t_transition_layer.layer = 100
1162
+ \tadd_child(_transition_layer)
1163
+ \t_color_rect = ColorRect.new()
1164
+ \t_color_rect.color = Color.BLACK
1165
+ \t_color_rect.anchors_preset = Control.PRESET_FULL_RECT
1166
+ \t_color_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
1167
+ \t_color_rect.modulate.a = 0.0
1168
+ \t_transition_layer.add_child(_color_rect)
1169
+
1170
+ func transition_to(scene_name: String, effect: String = "fade", duration: float = 0.5, data: Dictionary = {}):
1171
+ \tif _transitioning:
1172
+ \t\treturn
1173
+ \t_transitioning = true
1174
+ \tfor key in data:
1175
+ \t\tpersistent_state[key] = data[key]
1176
+ \tvar scene_path = "res://scenes/" + scene_name + ".tscn"
1177
+ \tmatch effect:
1178
+ \t\t"fade":
1179
+ \t\t\tawait _fade_out(duration / 2.0)
1180
+ \t\t\tget_tree().change_scene_to_file(scene_path)
1181
+ \t\t\tawait get_tree().process_frame
1182
+ \t\t\tcurrent_scene_name = scene_name
1183
+ \t\t\tawait _fade_in(duration / 2.0)
1184
+ \t\t"iris-out":
1185
+ \t\t\tawait _fade_out(duration)
1186
+ \t\t\tget_tree().change_scene_to_file(scene_path)
1187
+ \t\t\tawait get_tree().process_frame
1188
+ \t\t\tcurrent_scene_name = scene_name
1189
+ \t\t\t_color_rect.modulate.a = 0.0
1190
+ \t\t"iris-in":
1191
+ \t\t\t_color_rect.modulate.a = 1.0
1192
+ \t\t\tget_tree().change_scene_to_file(scene_path)
1193
+ \t\t\tawait get_tree().process_frame
1194
+ \t\t\tcurrent_scene_name = scene_name
1195
+ \t\t\tawait _fade_in(duration)
1196
+ \t\t"cut":
1197
+ \t\t\tget_tree().change_scene_to_file(scene_path)
1198
+ \t\t\tawait get_tree().process_frame
1199
+ \t\t\tcurrent_scene_name = scene_name
1200
+ \t_transitioning = false
1201
+
1202
+ func _fade_out(duration: float):
1203
+ \tvar tween = create_tween()
1204
+ \ttween.tween_property(_color_rect, "modulate:a", 1.0, duration)
1205
+ \tawait tween.finished
1206
+
1207
+ func _fade_in(duration: float):
1208
+ \tvar tween = create_tween()
1209
+ \ttween.tween_property(_color_rect, "modulate:a", 0.0, duration)
1210
+ \tawait tween.finished
1211
+
1212
+ func set_state(key: String, value):
1213
+ \tpersistent_state[key] = value
1214
+
1215
+ func get_state(key: String, default = null):
1216
+ \treturn persistent_state.get(key, default)
1217
+ '''
1218
+
1219
+
1220
+ MAP_CONTROLLER_GD = '''extends Node2D
1221
+ ## MapController — handles world map navigation between nodes.
1222
+ ## Generated by Quantum Framework - Godot Backend
1223
+
1224
+ @export var map_data: Dictionary = {}
1225
+ @export var path_data: Array = []
1226
+
1227
+ var current_node: String = ""
1228
+ var _mario_sprite: Sprite2D
1229
+ var _moving: bool = false
1230
+
1231
+ func _ready():
1232
+ \t_mario_sprite = $Mario if has_node("Mario") else null
1233
+ \tif map_data.has("start_node"):
1234
+ \t\tcurrent_node = map_data["start_node"]
1235
+ \tif _mario_sprite and map_data.has("nodes") and map_data["nodes"].has(current_node):
1236
+ \t\tvar node_info = map_data["nodes"][current_node]
1237
+ \t\t_mario_sprite.position = Vector2(node_info["x"], node_info["y"])
1238
+
1239
+ func _input(event: InputEvent):
1240
+ \tif _moving:
1241
+ \t\treturn
1242
+ \tif event.is_action_pressed("ui_accept"):
1243
+ \t\t_enter_node()
1244
+ \telif event.is_action_pressed("ui_right"):
1245
+ \t\t_try_move("right")
1246
+ \telif event.is_action_pressed("ui_left"):
1247
+ \t\t_try_move("left")
1248
+ \telif event.is_action_pressed("ui_up"):
1249
+ \t\t_try_move("up")
1250
+ \telif event.is_action_pressed("ui_down"):
1251
+ \t\t_try_move("down")
1252
+
1253
+ func _enter_node():
1254
+ \tif not map_data.has("nodes"):
1255
+ \t\treturn
1256
+ \tvar node_info = map_data["nodes"].get(current_node, {})
1257
+ \tvar scene_name = node_info.get("scene", "")
1258
+ \tif scene_name != "" and not node_info.get("locked", false):
1259
+ \t\tSceneManager.transition_to(scene_name, "iris-in", 0.5)
1260
+
1261
+ func _try_move(direction: String):
1262
+ \tvar neighbors = _get_neighbors(current_node)
1263
+ \tvar best_node = ""
1264
+ \tvar best_dist = INF
1265
+ \tvar current_pos = _get_node_pos(current_node)
1266
+ \tfor neighbor in neighbors:
1267
+ \t\tvar neighbor_pos = _get_node_pos(neighbor)
1268
+ \t\tvar delta = neighbor_pos - current_pos
1269
+ \t\tvar valid = false
1270
+ \t\tmatch direction:
1271
+ \t\t\t"right": valid = delta.x > 0 and abs(delta.x) >= abs(delta.y)
1272
+ \t\t\t"left": valid = delta.x < 0 and abs(delta.x) >= abs(delta.y)
1273
+ \t\t\t"up": valid = delta.y < 0 and abs(delta.y) >= abs(delta.x)
1274
+ \t\t\t"down": valid = delta.y > 0 and abs(delta.y) >= abs(delta.x)
1275
+ \t\tif valid:
1276
+ \t\t\tvar dist = delta.length()
1277
+ \t\t\tif dist < best_dist:
1278
+ \t\t\t\tbest_dist = dist
1279
+ \t\t\t\tbest_node = neighbor
1280
+ \tif best_node != "":
1281
+ \t\t_move_to(best_node)
1282
+
1283
+ func _move_to(target_node: String):
1284
+ \t_moving = true
1285
+ \tvar target_pos = _get_node_pos(target_node)
1286
+ \tif _mario_sprite:
1287
+ \t\tvar tween = create_tween()
1288
+ \t\ttween.tween_property(_mario_sprite, "position", target_pos, 0.2)
1289
+ \t\tawait tween.finished
1290
+ \tcurrent_node = target_node
1291
+ \t_moving = false
1292
+
1293
+ func _get_neighbors(node_id: String) -> Array:
1294
+ \tvar neighbors = []
1295
+ \tfor path in path_data:
1296
+ \t\tvar unlocked = true
1297
+ \t\tif path.has("unlock") and path["unlock"] != "":
1298
+ \t\t\tunlocked = SceneManager.get_state(path["unlock"], false)
1299
+ \t\tif not unlocked:
1300
+ \t\t\tcontinue
1301
+ \t\tif path["from"] == node_id:
1302
+ \t\t\tneighbors.append(path["to"])
1303
+ \t\telif path["to"] == node_id:
1304
+ \t\t\tneighbors.append(path["from"])
1305
+ \treturn neighbors
1306
+
1307
+ func _get_node_pos(node_id: String) -> Vector2:
1308
+ \tif map_data.has("nodes") and map_data["nodes"].has(node_id):
1309
+ \t\tvar info = map_data["nodes"][node_id]
1310
+ \t\treturn Vector2(info["x"], info["y"])
1311
+ \treturn Vector2.ZERO
1312
+ '''
1313
+
1314
+
1315
+ # ==========================================================================
1316
+ # Tilemap Loader — loads tile data from external JSON at runtime
1317
+ # ==========================================================================
1318
+
1319
+ TILEMAP_LOADER_GD = '''\
1320
+ extends TileMapLayer
1321
+ ## Loads tile data from {json_path} and populates the TileMapLayer at runtime.
1322
+
1323
+ func _ready():
1324
+ \tvar file = FileAccess.open("{json_path}", FileAccess.READ)
1325
+ \tif not file:
1326
+ \t\tpush_error("tilemap_loader: Could not open {json_path}")
1327
+ \t\treturn
1328
+ \tvar json_text = file.get_as_text()
1329
+ \tfile.close()
1330
+
1331
+ \tvar json = JSON.parse_string(json_text)
1332
+ \tif json == null:
1333
+ \t\tpush_error("tilemap_loader: Failed to parse {json_path}")
1334
+ \t\treturn
1335
+
1336
+ \tvar grid = json["grid"]
1337
+ \tvar count = 0
1338
+ \tfor row_idx in grid.size():
1339
+ \t\tvar row = grid[row_idx]
1340
+ \t\tfor col_idx in row.size():
1341
+ \t\t\tvar cell = row[col_idx]
1342
+ \t\t\tif cell != null:
1343
+ \t\t\t\tvar atlas_x = int(cell[0])
1344
+ \t\t\t\tvar atlas_y = int(cell[1])
1345
+ \t\t\t\tset_cell(Vector2i(col_idx, row_idx), {source_id}, Vector2i(atlas_x, atlas_y))
1346
+ \t\t\t\tcount += 1
1347
+
1348
+ \tprint("TileMapLayer: loaded ", count, " tiles")
1349
+ '''
1350
+
1351
+
1352
+ # ==========================================================================
1353
+ # Game Over Screen
1354
+ # ==========================================================================
1355
+
1356
+ GAME_OVER_SCREEN_GD = '''\
1357
+ extends CanvasLayer
1358
+ ## Game Over Screen - SMW-style slide-in animation
1359
+ ## "GAME" slides from left, "OVER" from right, meet at center
1360
+ ## Generated by Quantum Framework
1361
+
1362
+ var _alphabet_tex: Texture2D
1363
+ var _canvas: Control
1364
+ var _game_x: float = -64.0
1365
+ var _over_x: float = 320.0
1366
+ var _target_game_x: float
1367
+ var _target_over_x: float
1368
+ var _black_alpha: float = 0.0
1369
+ var _phase: int = 0 # 0=fade-in, 1=slide, 2=hold
1370
+ var _timer: float = 0.0
1371
+ var _sfx: AudioStreamPlayer
1372
+
1373
+ const CHAR_W: int = 8
1374
+ const CHAR_H: int = 8
1375
+ const SCALE: float = 2.0
1376
+
1377
+ signal game_over_finished
1378
+
1379
+ func _ready():
1380
+ \tlayer = 100
1381
+ \tprocess_mode = Node.PROCESS_MODE_ALWAYS
1382
+
1383
+ \t_alphabet_tex = load("res://{alphabet_path}")
1384
+
1385
+ \t_canvas = Control.new()
1386
+ \t_canvas.set_anchors_preset(Control.PRESET_FULL_RECT)
1387
+ \t_canvas.connect("draw", _on_draw)
1388
+ \tadd_child(_canvas)
1389
+
1390
+ \t# "GAME OVER" layout
1391
+ \tvar vp_w: float = {viewport_width}.0
1392
+ \tvar game_width = 4 * CHAR_W * SCALE
1393
+ \tvar over_width = 4 * CHAR_W * SCALE
1394
+ \tvar space_w = CHAR_W * SCALE
1395
+ \tvar total_width = game_width + space_w + over_width
1396
+ \tvar center_x = vp_w / 2.0
1397
+
1398
+ \t_target_game_x = center_x - total_width / 2.0
1399
+ \t_target_over_x = _target_game_x + game_width + space_w
1400
+
1401
+ \t_game_x = -game_width
1402
+ \t_over_x = vp_w + over_width
1403
+
1404
+ func _process(delta: float):
1405
+ \tmatch _phase:
1406
+ \t\t0: # Fade to black
1407
+ \t\t\t_black_alpha = min(1.0, _black_alpha + delta * 2.0)
1408
+ \t\t\tif _black_alpha >= 1.0:
1409
+ \t\t\t\t_phase = 1
1410
+ \t\t\t\t_timer = 0.0
1411
+ \t\t\t\t_sfx = AudioStreamPlayer.new()
1412
+ \t\t\t\t_sfx.stream = load("res://{jingle_path}")
1413
+ \t\t\t\t_sfx.process_mode = Node.PROCESS_MODE_ALWAYS
1414
+ \t\t\t\tadd_child(_sfx)
1415
+ \t\t\t\t_sfx.play()
1416
+ \t\t1: # Slide in
1417
+ \t\t\t_timer += delta
1418
+ \t\t\tvar t = clamp(_timer / 0.8, 0.0, 1.0)
1419
+ \t\t\tvar ease_t = 1.0 - pow(1.0 - t, 3.0)
1420
+ \t\t\t_game_x = lerp(-64.0, _target_game_x, ease_t)
1421
+ \t\t\t_over_x = lerp({viewport_width}.0 + 64.0, _target_over_x, ease_t)
1422
+ \t\t\tif t >= 1.0:
1423
+ \t\t\t\t_phase = 2
1424
+ \t\t\t\t_timer = 0.0
1425
+ \t\t2: # Hold until jingle ends
1426
+ \t\t\t_timer += delta
1427
+ \t\t\tif _timer >= 6.0:
1428
+ \t\t\t\temit_signal("game_over_finished")
1429
+ \t\t\t\tset_process(false)
1430
+ \t_canvas.queue_redraw()
1431
+
1432
+ func _on_draw():
1433
+ \tvar size = _canvas.size
1434
+ \t_canvas.draw_rect(Rect2(0, 0, size.x, size.y), Color(0, 0, 0, _black_alpha))
1435
+ \tif _phase >= 1:
1436
+ \t\tvar center_y = size.y / 2.0 - CHAR_H * SCALE / 2.0
1437
+ \t\t_draw_word("GAME", _game_x, center_y)
1438
+ \t\t_draw_word("OVER", _over_x, center_y)
1439
+
1440
+ func _draw_word(word: String, x: float, y: float):
1441
+ \tfor i in range(word.length()):
1442
+ \t\tvar ch = word[i]
1443
+ \t\tvar idx = ch.unicode_at(0) - "A".unicode_at(0)
1444
+ \t\tif idx < 0 or idx > 25:
1445
+ \t\t\tcontinue
1446
+ \t\tvar src = Rect2(idx * CHAR_W, 0, CHAR_W, CHAR_H)
1447
+ \t\tvar dst = Rect2(x + i * CHAR_W * SCALE, y, CHAR_W * SCALE, CHAR_H * SCALE)
1448
+ \t\t_canvas.draw_texture_rect_region(_alphabet_tex, dst, src)
1449
+ '''