Repository navigation
Generated Project and Runtime
Applies to: GM2Godot 1.0.0 · GameMaker LTS 2026 · Godot 4.7.2
Last reviewed: 2026-10-04
Home · Installation · Quick start · Compatibility · Diagnostics · Contributing
This page is the advanced-user map of a converted project: what GM2Godot owns, how the generated runtime coordinates GameMaker semantics, and which reports are authoritative. The implementation-facing documents in the main repository remain canonical for details that can change between releases.
A full conversion can produce a tree like this. The exact files depend on the enabled converter keys and the source project.
GodotProject/
├── project.godot
├── default_bus_layout.tres
├── icon.ico / icon.png
├── addons/gm2godot_extensions/
├── gm2godot/
│ ├── gml_runtime.gd
│ ├── gml_room_node.gd
│ ├── managers/
│ ├── timelines/
│ ├── conversion_attempt.json
│ ├── conversion_manifest.json
│ ├── conversion_diagnostics.json / .md
│ ├── architecture_policy.json
│ └── other registries and compatibility reports
├── fonts/
├── included_files/
├── notes/
├── objects/
├── particles/
├── particlesystems/
├── paths/
├── rooms/
├── scripts/
├── sequences/
├── shaders/
├── sounds/
├── sprites/
└── tilesets/
GM2Godot treats these directories as managed output roots:
addons/gm2godot_extensions/fonts/gm2godot/included_files/notes/objects/particles/particlesystems/paths/rooms/scripts/sequences/shaders/sounds/sprites/tilesets/
The top-level default_bus_layout.tres, icon.ico, and icon.png are managed files. project.godot is jointly managed: GM2Godot can update the generated GM* autoload entries and, when a room is generated, set run/main_scene from the first GameMaker RoomOrderNodes entry. Existing unrelated project settings and unrelated autoloads are preserved.
GameMaker Included Files from datafiles/ are emitted under res://included_files/. Each relative path uses GameMaker's packaged lookup form: ASCII A–Z is lowercased and spaces become underscores, including nested directory components; other characters are preserved.
For relative file and buffer reads, the generated runtime checks the exact user://gm2godot/<path> first and then the normalized res://included_files/<path>. Writes target user storage. A saved user file therefore overrides its packaged default, and deleting the saved copy reveals the packaged file again. file_exists(), text-file reads, and buffer_load() share this precedence.
When source paths collapse to the same packaged name, or when one normalized file path would block another file's directory, GM2Godot reserves natural names and deterministically assigns _2, _3, and later suffixes before the extension. The emitted files, asset registry, and conversion manifest use the same assignments. GM2GD-INCLUDED-FILE-PATH-COLLISION reports the mapping; rename these conflicts in GameMaker because normalized lookup cannot distinguish every original alias. Publication also rejects redirected or non-regular paths in the managed included_files/ output tree instead of writing through them.
Unchanged-generation receipt validation and changed-generation staging keep at most twice the configured worker count unfinished. The source plan and completed receipt map still scale with the Included File count, but queued futures and submission bookkeeping do not. Once cancellation or the first terminal worker failure is observed, GM2Godot admits no further source work, cancels pending tasks where possible, drains only the bounded running remainder, and leaves the previous public generation unchanged.
When a changed generation retains the same planned paths, immutable source-content receipts flow into copying and staged-tree capture instead of rereading bytes already proven for that attempt. Reuse remains bound to the exact source path and handle, assigned path, staged and public output identities, transaction, and generation. GM2Godot still hashes both the source and published tree at the final pre-commit boundary; any same-size mutation, replacement, redirection, hard-link, mount, or directory swap fails closed and restores the previous complete root/registry pair. The deterministic single-file payload bounds are 5x reads for an initial generation, 4x for an unchanged generation, and at most 8x for a changed generation.
res://included_files/ and res://gm2godot/gml_included_file_registry.gd form one recoverable converter-owned generation. Before replacing either public path, GM2Godot durably publishes .gm2godot-included-files-transaction.json, which records the exact previous and staged identities, bytes, hashes, and transaction paths. Format v2 stores tree entries as strict compact rows with fixed-width integer fields; recovery continues to accept canonical format-v1 journals and commit markers. After both new paths have been published and verified, GM2Godot durably publishes .gm2godot-included-files-commit.json; the marker embeds the complete cleanup manifest so recovery remains possible if journal retirement was interrupted. At the start of the next Included Files conversion, a prepared transaction with no matching commit marker is rolled back to the complete previous pair; a transaction with the matching marker is verified as the complete new pair and its cleanup is finalized. The public res:// paths do not change, so existing projects need no path migration.
The two public-path moves are not one indivisible filesystem syscall. A format-v2 generated registry therefore records the expected byte count and SHA-256 for every emitted file. The first generated GMRuntime autoload establishes integrity before script-registry initialization and before the main scene: it parses every receipt strictly, then hashes emitted payloads sequentially in deterministic registry order using fixed 1 MiB chunks. Trust is all-or-nothing. A missing or receiptless registry, malformed receipt, absent payload, size mismatch, SHA-256 mismatch, or transaction journal leaves the complete packaged generation unavailable to relative GML file APIs instead of exposing a verified subset or falling through to a loose res://included_files/ path. Rerun conversion to replace a legacy receiptless runtime registry; this does not remove format-v1 journal and commit-marker recovery support.
Startup prevalidation performs one full read of every emitted Included File, so startup time is proportional to their total byte count while hashing memory remains bounded to one 1 MiB chunk. After trust is established, exact and canonical path resolution, file_exists(), text reads, and buffer loads use receipt-cache and byte-count checks and do not checksum the payload again on first access. gml_included_file_integrity_status() reports the registry, entry, verified-entry, hash-attempt, hashed-byte, and hashing-time counters for diagnostics and performance tests. Verification is deliberately completed on the ordered startup path rather than racing file calls against background workers. User-file overrides retain their normal precedence, and explicit res:// paths remain explicit engine paths.
The runtime contract follows the official GameMaker LTS Included Files and file-area precedence documentation. Its implementation uses the exact Godot 4.7 FileAccess, HashingContext, and Autoload contracts; Godot's thread-safe API guidance is why generation trust is established on the ordered startup path instead of sharing mutable dictionaries with worker tasks.
Recovery and publication run under a non-blocking operating-system lock stored at .gm2godot-included-files.lock in the destination project. A second cooperating GM2Godot converter fails without modifying the generation and may be retried after the first converter exits. The persistent lock file itself is not evidence that a converter is still running; ownership is the operating-system lock, not file existence. A fully synced journal temporary is promoted before orphan cleanup, while partial or ambiguously named records are preserved. Canonical recovery records are limited to 16 MiB and are rejected from their verified file size before payload parsing if they exceed that bound. For a changed generation, GM2Godot renders byte-exact format-v2 journal and commit stand-ins from the planned paths, source byte counts, fixed-width metadata, and registry shape before creating the payload stage; both actual records must retain those exact sizes before publication. Malformed, oversized, ambiguous, or unknown replacements at reserved transaction paths are preserved and fail closed instead of being guessed at or deleted.
Do not convert while a live game, editor session that is actively loading Included Files, or non-cooperating writer is accessing the generated project. Those processes do not acquire the converter lock, and a live runtime can retain already-open files or startup-established verification from before a publication. Close them, run conversion or automatic recovery to completion, and then reopen the project.
Managed generation trees and the registry must remain on the Godot project's filesystem. Capture and recovery reject cross-device entries and nested mount points; on Linux, open-descriptor mount IDs also detect same-filesystem bind mounts that ordinary mount-point checks can miss. Descriptor-capable hosts retain the open directory chain and verify each direct parent/child binding before and after traversal instead of repeatedly rechecking every ancestor. The native Windows path fallback verifies each current directory through its complete path. Both paths therefore keep binding work linear in tree depth while retaining deterministic ordering and fail-closed replacement and concurrent-mutation checks. Cleanup rechecks filesystem boundaries before traversing recorded directories and preserves the tree if a mount or unknown replacement appears. Recorded file receipts are streamed in 1 MiB chunks before and after cleanup quarantine, so receipt verification never buffers an entire Included File in Python memory.
On Windows, the path-based transaction fallback rejects real NTFS junctions at the managed root, inside the tree, at the registry directory, and at staging or backup boundaries without traversing their targets. Native calls use extended-length paths, and transaction moves request write-through completion. Recovery cleanup walks only entries recorded in the durable snapshot and moves each one through a fixed transaction-derived tombstone before deletion; it never treats an owned directory identity as authority to delete unknown descendants. A read-only file is made writable only at that deterministic tombstone, its attribute is restored if deletion fails, and a file with multiple hard links is retained rather than changing the shared attribute visible through an external alias. Native windows-2025 tests cover those junctions and read-only cleanup after success, commit failure, cancellation, rollback, process exit, and restart recovery. Windows does not expose an unprivileged, documented directory-deletion flush equivalent: a machine power loss may replay a hidden cleanup tombstone after its deletion, but cannot turn that tombstone back into either public generation path. Preserve and report unexpected hidden transaction debris instead of deleting it by hand. These checks do not make non-cooperating concurrent namespace mutation safe.
Subprocess tests terminate conversion without running Python cleanup at every forward publication boundary, plus the quarantine/removal boundaries for owned backup, staging, stable-record, and temporary-record cleanup. Every forward boundary is recovered with both the existing format-v1 records and the generated format-v2 records. Recovery must expose the exact previous generation before the commit marker or the exact new generation after it, never a mixed pair. The registry receipt and runtime-read contract is also covered by an end-to-end smoke test using the exact supported Godot 4.7.2 build.
Version 0.7.38 added a reusable ManagedOutputWorkspace for the complete managed-output transaction planned in later releases. It creates a private transaction directory under .gm2godot-managed-output/ only after binding the destination, workspace parent, and stage and proving that all three share the destination device and mount. A persistent .gm2godot-managed-output.lock file carries one non-blocking operating-system lock for the whole session; file existence alone does not mean a session is active.
Snapshot and streaming-copy operations accept an exact normalized relative-path allowlist. They reject symbolic links, Windows junctions or other reparse points, hard-linked regular files, nested mounts, cross-device entries, and changed directory bindings without scanning an entire managed root for implied ownership. Bounded canonical markers bind the workspace parent and stage to the destination identity and 32-character transaction identifier. Cleanup first validates the complete private tree, preserves unknown or changed lookalikes, and removes only identity-verified transaction state. Cancellation and ordinary staging errors therefore leave public managed and user-owned destination bytes and modes unchanged.
This is intentionally foundation-only in 0.7.38: Converter, CLI orchestration, finalizers, canonical manifests, and individual converters still use their existing destinations. The workspace does not yet define a complete generation inventory, publish or recover public output, alter the conversion-manifest schema, or implement stale logical-resource deletion.
The implementation follows Python 3.12's documented dir_fd, follow_symlinks, and file-descriptor capability contracts, fcntl.flock on POSIX, and msvcrt.locking on Windows. Native Windows bindings use Microsoft's CreateFileW no-follow/reparse and sharing semantics, GetFileInformationByHandleEx identities, non-replacing write-through MoveFileExW moves, and the documented reparse-point model. No generated GDScript or GameMaker source interpretation changes: the target remains the official GameMaker LTS 2026 Included Files and file-area behavior, with exact Godot 4.7.2 FileAccess, DirAccess, and command-line validation.
The preceding generation-inventory foundation added one immutable model for staging carry-forward, staged/public validation, canonical-manifest rendering, and manifest publication guards. Entries are sorted by normalized NFC destination-relative paths using / separators and record the output kind, a producing converter_step or explicit shared_owner, byte count, sha256: digest, and exact mode. project.godot uses the project_configuration shared-owner class because converter steps update only GM2Godot-owned settings while unrelated settings remain user-controlled. Shared generated runtime and evidence files use named shared-owner classes instead of being assigned arbitrarily to one converter.
The format-v2 conversion manifest retains its stable filename and top-level format version. It adds generation_inventory: {"format_version": 1, "entries": [...]}. generated_files remains for existing consumers, but is now a complete path/kind/digest projection of that same inventory rather than a start-of-invocation fingerprint diff; its canonical-manifest compatibility row remains sha256: "self". The inventory itself excludes the canonical manifest to avoid a self-digest cycle, and also excludes the latest-attempt ledger, .godot/, destination/artifact locks, transaction and recovery records, private stages/backups, and files outside the documented managed paths. Canonical bytes use Python 3.12's documented json.dumps(..., sort_keys=True) behavior with fixed UTF-8, indentation, ASCII escaping, and a trailing newline.
The first inventory-aware run migrates an existing format-v2 manifest through a 32 MiB canonical-record limit and at most 100,000 entries, then completes its allowlist from the documented managed roots. Absolute, escaping, structurally ambiguous, case-colliding, unsafe native-Windows, malformed, redirected, mounted, cross-device, non-regular, or multiply-linked entries are rejected before staging or artifact publication. A selective run carries every disabled converter's prior inventory entries byte-, mode-, and digest-exactly; shared entries provide the baseline for later joint updates. Canonical bytes are rendered from one frozen inventory, and publication rehashes that same topology and content before and after committing the manifest so a same-size mutation with restored timestamps is still detected.
Version 0.7.40 added an internal publisher that consumes one verified ManagedOutputWorkspace plus frozen previous and desired inventories; it never derives ownership from mutable public paths. Before the first public move, a strict bounded journal binds the destination, workspace, exact inventory records, required directory identities, every desired stage and prior backup, the current public identities, and the canonical manifest/attempt receipts. New directories and managed file creates, replacements, and removals use verified no-follow bindings and same-filesystem namespace moves. The attempt is installed after managed files and the canonical manifest is last. Only after the complete desired inventory, evidence bytes, modes, digests, and identities verify does one durable generation pointer select the new generation.
Before that pointer, ordinary failure rolls mutations back in reverse order and recovery restores and verifies the prior managed inventory plus its exact canonical evidence. After it, recovery accepts only the complete selected new inventory/evidence set and finishes identity-bound cleanup. Unknown, redirected, mounted, multiply-linked, replaced, or concurrently changed entries are preserved and fail closed. If rollback cannot finish safely, the journal, stage, backups, and displaced entries remain available and a separate bounded .gm2godot-managed-output-recovery.json record under the private workspace parent reports the transaction, affected paths, selected generation, error, and safe retry call; it is not the canonical manifest or latest-attempt ledger.
The implementation follows Python 3.12's os descriptor-relative and replacement contracts, POSIX Issue 8 atomic rename/renameat and file/directory fsync semantics, and Microsoft's same-volume MoveFileExW MOVEFILE_WRITE_THROUGH, FlushFileBuffers, and reparse-point contracts. Every stage, backup, journal, recovery artifact, generation record, and namespace move remains on the destination filesystem; MOVEFILE_COPY_ALLOWED and every other cross-volume copy/delete fallback are intentionally disabled.
The compatibility baseline was rechecked against the official GameMaker LTS 2026 Included Files, file-area precedence, and 2026.0 release notes, whose current release is IDE 16 with GMS2 Runtime 23. SNAP (IDE 15 metadata) and Adding (IDE 16 metadata) remain pinned conversion fixtures. Generated output is imported, resource-loaded, and booted with the official Godot 4.7.2 maintenance release built from ed1daf0bf, using its documented command-line, FileAccess, and DirAccess contracts.
Version 0.7.41 makes this the production Converter.convert() path used by GUI, direct-library, and CLI conversion. Recovery and the destination-wide lock run before mutating preflight. That release copied the previous complete inventory into the private stage so enabled converters could replace output without silently adopting the then-separate stale-resource deletion policy; disabled and shared output remained exact. The staged Godot path is passed to project initialization/settings, every selected converter and registry, architecture and diagnostic reports, CLI static reports when --report-dir is the project itself, inventory capture/validation, and canonical-manifest construction.
After converter and finalizer work reaches a trustworthy success or partial candidate, GM2Godot freezes and rehashes the stage, reads the exact staged manifest/attempt bytes through verified bindings, performs one final cooperative cancellation check, and enters recoverable publication. Runtime exceptions, finalizer failures, validation failures, or cancellation before that point discard only verified private state. The prior public inventory, canonical diagnostics, and architecture policy remain byte- and mode-exact; only conversion_attempt.json is advanced, with canonical_manifest.current_output: "verified" after transactionally checking the preserved generation. Once publication starts, cancellation is no longer accepted as a rollback claim: the publisher completes its durable old-or-new decision.
Version 0.7.42 adds deterministic phase declarations for every durable publication boundary. Real project-setting, script, object, registry, diagnostic, manifest, and attempt mutations are terminated without Python cleanup after each observed forward move, reverse rollback, recovery retry, and private cleanup operation. Every restart verifies the exact prior generation before the durable pointer or the exact desired generation after it, including canonical manifest and attempt digests; another recovery must make no changes. Native tests use byte, mode, identity, device, and namespace assertions rather than timing thresholds.
Version 0.7.44 represents every generated receiver-aware function with explicit metadata. A generated method callable declares hidden self and other arguments; a constructor declares the new struct plus its constructor other; ordinary Godot object methods declare no hidden GML receiver arguments. The runtime therefore never uses Callable.is_standard() or is_custom() to guess generated arity. It retains the Godot callable owner separately from the semantic GameMaker binding so an unbound script reference remains alive without pretending that its generated RefCounted wrapper is method_get_self().
Each transpiled dynamic call supplies the current GML self. A bound callee receives that value as other, so nested method calls advance context one scope at a time and array/struct callback helpers preserve the scope that invoked the callback. Script registry entries keep separate ordinary and receiver-aware call paths: direct asset calls retain caller self/other, while method(target, script_reference) copies the receiver contract and uses target as the rebound self without consuming a user argument.
new has a separate checked path so receiver injection cannot be omitted or applied twice. The newly allocated struct is always constructor self. For an unbound script constructor, other is the scope that called new; for a constructor rebound with method, GameMaker's bound-constructor exception makes the bound scope other. Parent constructor calls reuse the same new struct and propagate constructor context. Missing receiver metadata and unmarked custom Godot callables fail closed with a runtime error.
These rules follow GameMaker LTS Method Variables, Instance Keywords, and method. Godot behavior is pinned to the exact 4.7.2 Callable contract—where lambdas, bound arguments, global functions, and Variant methods can all be custom callables—and its GDScript lambda rules.
Version 0.7.43 defines logical ownership for the five resource families in #715. An object owns the collision-safe resource directory containing its required .tscn and .gd plus any .gd.gmlmap.json; a room owns its collision-safe directory containing the required .tscn and optional generated .gd; a sprite owns its collision-safe scene/frame directory; a shader owns its exact .gdshader; and a timeline owns each collision-safe gm2godot/timelines/<stem>_<moment>.gd action script named by its metadata. These paths remain physically owned by the corresponding generation-inventory converter step.
When one of the selected object, room, sprite, shader, or asset-registry/timeline converters runs, its prior converter-owned inventory entries are not copied into the private candidate stage. Current source resources then regenerate their complete outputs. A resource that is missing, rejected, blocked by transpilation, skipped, failed, or absent from the authoritative YYP leaves no logical output in the desired inventory; the existing destination-wide publisher commits those removals with all creates/replacements and canonical evidence under the same recoverable old-or-new decision. This gives multi-file objects, rooms, and sprites generation-level atomicity without deleting public files directly from a converter.
Before the runtime asset registry is rendered, object/room/sprite/shader rows are checked against the confined candidate inventory. Rows whose required outputs are absent are omitted, and missing timeline action-script references are stripped while retaining the timeline's supported metadata. Canonical manifest resources are reconciled against the same frozen inventory used to render generated_files. If room conversion leaves a previous run/main_scene pointing to a missing res://rooms/ output, that one GM2Godot-managed setting is removed; unrelated project settings and non-room startup scenes remain unchanged.
Disabled converters retain the prior byte-, mode-, and digest-exact inventory by design, so a selective run must include asset_registry when its runtime registry also needs refreshing. A failed or cancelled run before the durable decision still exposes the complete prior generation, not the candidate's planned deletions. Files outside the documented managed roots are never deletion candidates. Unknown files added inside a managed root make the frozen topology disagree with its manifest and fail closed while preserving the file; GM2Godot does not infer ownership from location alone after a canonical inventory exists.
Version 0.7.47 adds each generated sequence descriptor .tres as the required private output of its logical sequence resource under the asset_registry owner. A removed, rejected, or partial sequence therefore cannot leave an earlier descriptor masquerading as current output; supported partial descriptors are regenerated with their source-linked unsupported-type diagnostics.
The source-of-truth policy follows GameMaker LTS 2026's official Project Format: the root YYP describes project resources, while each YY describes one resource and its associated files. Shader ownership follows the LTS Shader Editor two-stage asset model, and timeline action ownership follows the LTS Timeline Editor moment/code model. Validation uses Godot 4.7's documented --import scan and import process, under which project assets are rescanned from files while imported cache state lives under .godot/ and can be regenerated.
Version 0.7.48 parses both GameMaker GLSL ES stages into declarations and function spans before generating one shader_type canvas_item resource. GameMaker's standard 2D vertex inputs map to Godot processing inputs: in_Position becomes vec3(VERTEX, 0.0), in_Colour/in_Colour0 becomes vertex COLOR, and in_TextureCoord becomes UV. The converter removes the GameMaker world/view/projection prefix from supported gl_Position assignments and writes the resulting local XY value to VERTEX; Godot then applies MODEL_MATRIX, CANVAS_MATRIX, and SCREEN_MATRIX exactly once. Other fixed gm_Matrices accesses map to those matrices or their documented products. Fragment gm_BaseTexture, texture2D, and gl_FragColor map to TEXTURE, texture, and COLOR.
Matching varyings and shared uniforms are emitted once. Fixed one-dimensional arrays, constants, precision qualifiers, and multi-line/comma-separated global declarations are normalized into unambiguous declarations. A conflict, Godot built-in name collision, unsupported scalar matrix constructor, unsupported attribute or matrix, arbitrary clip-space/3D transform, preprocessor directive, mutable global, or unlinked fragment varying produces a source-line GM2GD-SHADER-* diagnostic, fails the shader resource, and publishes no .gdshader; a committed partial rerun therefore cannot retain the old managed shader or registry row. The supported corpus is compiled and loaded under exact Godot 4.7.2.stable.official.ed1daf0bf. Custom vertex formats, macro expansion, multi-pass effects, renderer state, and visual parity still require manual Godot work.
The mapping boundary follows the official GameMaker LTS shader guide, built-in shader constants, and vertex-format mapping, together with Godot 4.7's CanvasItem built-ins, GLSL conversion guidance, and shading-language declaration/array/varying rules.
Converter.convert() is the direct-library contract used by the GUI worker and CLI. A cooperative conversion_running flag cleared during converter/finalizer/validation work, or during recovery before new work begins, yields cancelled only before publication and leaves the verified prior generation public. Once publication starts, cancellation is deferred until the old-or-new decision and cannot relabel a committed generation. Exceptions expose last_outcome, but public trust comes from the canonical evidence plus any recovery artifact—not from return-versus-exception alone.
If .gm2godot-managed-output-recovery.json is present under the private workspace parent:
- Close the Godot editor/game and every process that can write the destination.
- Preserve the recovery artifact, journal, named stage, generation records, and affected public paths unchanged.
- Read
transaction_id,affected_paths,selected_generation,error, andnext_step.previousmeans rollback is selected,desiredmeans the commit decision already selected the new generation, andunknownmeans neither generation may be assumed. - Retry conversion or call
recover_managed_output_generation(destination_path)once the underlying permission/device problem is fixed. Repeat recovery is idempotent after completion. - If recovery still rejects identity, redirection, mount, hard-link, or namespace state, back up the destination and report the preserved artifacts; do not edit the journal or pointer to force acceptance.
The recovery artifact is canonical JSON bounded to 1 MiB, its error to 4,096 characters, and its displayed affected-path list to 100 entries; the full count and truncation flag remain explicit. Crash-interrupted cleanup resumes only for the detached stage whose transaction and filesystem identity match the journal. This does not make a live Godot process or non-cooperating writer safe.
Treat every generated file under a managed root as reproducible output. A later conversion can replace it even if the converter does not delete the entire directory. For repeatable migrations:
- Keep the GameMaker project and
gm2godot_extension_functions.jsonas source-controlled inputs. - Put hand-authored Godot code outside the managed roots—for example,
res://game/orres://addons/my_bridge/. - Point extension mappings or custom autoloads at that hand-authored code.
- Review
conversion_attempt.json, diagnostics, and the manifest diff after every regeneration.
Editing generated GDScript is useful for investigation, but it is not a durable fix. Make durable changes in the GameMaker source, the extension mapping/bridge, or GM2Godot itself. The managed-path safety rules are implemented in project_godot.py.
The generated room policy translates GameMaker’s flat resource model into a Godot scene tree:
- Each room is a
Node2Droot usingres://gm2godot/gml_room_node.gd. - The first ordered GameMaker room becomes the Godot main scene when it was converted successfully.
- GameMaker layers become
Node2Dchildren. The depth mapping isz_index = -GameMaker depth, because lower GameMaker depth and higher Godotz_indexboth draw later. - Every room reserves a
GMGUICanvasLayerat layer1000for Draw GUI phases. - Decodable tile data uses Godot 4
TileMapLayeroutput. - Native Godot callbacks do not define GameMaker event order; the runtime managers coordinate the generated phases.
gm2godot/architecture_policy.json records the feature-detected backend choices for the particular conversion:
| Domain | Possible policy modes | What selects the mode |
|---|---|---|
| Rendering |
godot_node_scene, central_canvas_draw_manager, surface_viewport
|
Detected draw_, shader_, gpu_, font_, or sprite_ API usage, effect layers, and surface/application-surface usage |
| Collision |
generated_bounds_idle, generated_bounds_direct_queries, godot_physics_world_bridge
|
Collision-query code and GameMaker physics-room settings |
| Audio | pooled AudioStreamPlayer / AudioStreamPlayer2D, or an idle runtime manager |
Sound assets and audio API usage |
| File, buffer, HTTP, network |
FileAccess, DirAccess, PackedByteArray, HTTP and socket wrappers |
Detected file/buffer/network APIs |
The policy is a conversion-time description, not proof of gameplay parity. Inspect its project_features, renderer, collision, audio, file_buffer_network, runtime_managers, and signal_queue_policy fields together with diagnostics. The canonical, implementation-facing policy is godot_architecture_policy.md.
Generated scripts continue to preload res://gm2godot/gml_runtime.gd and call GMRuntime.gml_*. That static compatibility facade is the stable call-site boundary. The generated autoloads under res://gm2godot/managers/ divide lifecycle ownership into explicit domains and register in this order:
| Order | Autoload | Primary responsibility |
|---|---|---|
| 0 | GMRuntime |
Root registry, compatibility lifecycle, startup and shutdown |
| 10 | GMAssets |
Assets, texture groups, audio groups and dynamic assets |
| 20 | GMRooms |
Room order, current room, transitions and layers |
| 30 | GMInstances |
Live instances, handles, object indices and creation order |
| 40 | GMEvents |
Input dispatch, Step scheduler, alarms, timelines, sequences and collision window |
| 50 | GMDraw |
Ordered Draw phases, draw state, surfaces, shaders and texture-group state |
| 60 | GMInput |
Godot input capture and GameMaker keyboard, mouse, gamepad and gesture state |
| 70 | GMAudio |
Audio instances, groups, emitters and listeners |
| 80 | GMAsync |
FIFO async delivery, async_load, HTTP, buffer and networking queues |
| 90 | GMPlatform |
Platform hooks, extension callback schemas, OS/debug and GC state |
Godot loads the autoload nodes before the main scene and the generated project.godot lists them deterministically. GMRuntime.manager_order() exposes the observed registration order, while manager_registry_snapshot() exposes domains, dependencies, state keys, and initialization indices.
Each manager also exposes named state_bucket() dictionaries. These are ownership and migration seams; they do not mean that every compatibility variable has already moved out of gml_runtime.gd. Existing generated call sites should keep using the facade unless a runtime change deliberately moves a domain behind a manager.
See runtime_managers.md, runtime_managers.py, and the executable expectations in test_runtime_managers.py.
Generated object _ready() methods register the instance, initialize motion state, and run converted Create behavior when present. The generated room root then performs the room-entry sequence:
- Register layer and view metadata.
- Instantiate authored room particle-layer systems.
- Update the current room and room globals.
- Run instance creation code in declared GameMaker creation order.
- Dispatch Game Start once for the running game.
- Run room creation code.
- Dispatch Room Start.
For room_goto*() and room_restart(), the compatibility runtime dispatches Room End on the old scene, moves persistent instances to a root-level holding node, replaces the room scene, restores those instances, and runs the entry sequence for the new scene.
GMInput._input(event) captures native input as it arrives. On the generated GMEvents frame pump, input event bindings are dispatched first, followed by the central scheduler:
Begin Step
→ time sources
→ alarms
→ authored sequences and timelines
→ Step
→ motion/path update
→ collision dispatch
→ End Step
→ clear one-frame input edges
Collision is therefore evaluated after motion/path updates and before End Step. Do not attach correctness to the incidental _process() order of individual object nodes.
Version 0.7.45 derives Precise collision geometry from the alpha channel of each converted RGBA frame, clips it to the inclusive GameMaker bounding box, and includes pixels whose alpha is strictly greater than collisionTolerance. A static Precise mask unions every subimage before converting contiguous pixels into exact rectangle geometry. Precise Per Frame keeps separate geometry for each subimage; the generated sprite scene switches enabled shapes on AnimatedSprite2D.frame_changed, and generated image_index handling explicitly synchronizes the same frame.
The visual and mask use the GameMaker origin as their common local reference. The object transform then applies position, image_xscale, image_yscale, and image_angle to both. Collision events, place/position and motion checks, and point/rectangle/line/circle single-result and list APIs read the active transformed polygons. An advanced query with precise = false uses the transformed GameMaker mask bounds; precise = true uses precise pixels when the target has them.
If frame bytes are unreadable, dimensions or bounds disagree, tolerance is invalid, or exact decomposition exceeds 16,384 rectangles, conversion emits GM2GD-SPRITE-PRECISE-MASK-FALLBACK and retains the prior bounding-box fallback when that box is valid. Runtime-created/modified sprite masks, mask_index overrides, skeletal meshes, tile-set masks, and GameMaker physics fixtures remain separate compatibility areas.
This follows the GameMaker LTS Sprite Editor collision-mask rules, collision rotation/scaling and precise-query semantics, and sprite_collision_mask. Generated behavior is validated against Godot 4.7.2 AnimatedSprite2D.frame_changed, CollisionShape2D, and Transform2D.
Version 0.7.46 normalizes each current GMParticleSystem asset into one deterministic descriptor and writes it as a loadable resource under its collision-safe res://particlesystems/ path (res://particles/ for the legacy resource family). The descriptor keeps system origin and draw order, one embedded particle type per current editor emitter, emitter order/name/mode/count/region/distribution/relative and timing fields, visual and motion ranges, and secondary-spawn references. The generated asset registry carries the same descriptor so static asset IDs passed to part_system_create and part_system_create_layer resolve without guessing.
At runtime, each authored emitter becomes a managed GPUParticles2D with a ParticleProcessMaterial. Lifetime frames and per-step speed/gravity are converted at the generated room speed; region shapes, initial direction/spread, orientation, size ranges/increment curves, colour/alpha ramps, additive blend, built-in particle textures, custom sprite textures, and stream or one-shot burst mode are applied where Godot has a safe mapping. Exact GameMaker Gaussian/inverse-Gaussian placement, wiggle/increment evolution, sprite flipbooks, delay/interval scheduling, and secondary spawning remain descriptor-preserved approximations rather than claimed native parity.
A GMRParticleSystem room asset keeps its layer visibility, inverse-depth z_index, layer offset, element position/rotation/scale/modulate, source asset ID, and source metadata. Room entry creates its system below that element. Tree exit destroys the system's emitter and owned-type handles and frees every managed particle node; directly created one-shot nodes also free themselves after Godot's finished signal. Non-empty legacy attractor, destroyer, deflector, or changer fields do not silently disappear: conversion records GM2GD-PARTICLE-MODIFIER-UNSUPPORTED against the source .yy field and retains the unsupported category in the descriptor.
These mappings follow the GameMaker LTS Particle System Editor, particle_get_info, and part_system_create contracts. Generated behavior and cleanup are tested with exact Godot 4.7.2 GPUParticles2D and ParticleProcessMaterial APIs.
Version 0.7.47 normalizes each supported GMSequence into a deterministic descriptor and a managed loadable resource under res://sequences/<path>/<name>.tres. Asset and parameter key order, key frame/length/stretch/disabled state, assign or linear interpolation, sequence length, both playback-speed modes, one-shot/loop/ping-pong state, origins, volume, track visibility, and top-to-bottom draw order are retained. Supported asset tracks are sprite, instance, audio, text, and nested sequence. Object-track instances are created with the sequence and visibility-gated at their authored keys, matching GameMaker rather than repeatedly creating them.
The generated runtime evaluates position, rotation (with GameMaker-to-Godot direction conversion), origin, scale, colour multiply, image index/speed, audio position/gain/pitch, text frame/alignment/wrapping/spacing/outline/shadow, and nested sequence state. Each audio track owns a collision-safe temporary Godot bus; gain, reverb, delay, LPF, HPF, high-shelf, and low-shelf effects are installed in authored order when their documented properties map safely. Sequence destruction unregisters eager object instances, stops players, recursively cleans nested state, and removes every owned bus.
Moment keys call converted script functions in sequence-instance scope before same-frame broadcast keys. Broadcast strings preserve authored order and expose GameMaker-compatible event_data keys (event_type, message, and element_id) to each listening object. Legacy timeline moment GML is transpiled to source-linked scripts and all crossed moments run in ascending or descending frame order, including speeds greater than one. Unsupported track/key/effect/action types, animation-curve keys, lifecycle event bindings, clip masks/groups, particle sequence tracks, runtime-authored track/key structures, and object-track overrides are not guessed: they produce source-linked partial-conversion diagnostics.
The mapping follows the GameMaker LTS Sequence Track Struct, Keyframe Struct, events/moments/broadcast, sound/effect, and timeline contracts. Runtime nodes and effects are validated against exact Godot 4.7.2 Label, AudioStreamPlayer2D, AudioServer, and audio-effect APIs.
GMDraw independently dispatches the ordered drawing phases:
Pre Draw → Draw Begin → Draw → Draw End → Post Draw
→ Draw GUI Begin → Draw GUI → Draw GUI End
GMAsync owns FIFO delivery for queued HTTP, networking, audio, platform, and extension callbacks. It scopes async_load to each delivered Async event. Godot signals that affect GameMaker ordering—such as supported collision, timer, animation, HTTP-completion, and audio-finished signals—must be queued through the manager identified by architecture_policy.json; user-authored native callbacks are outside this ordering contract unless they use the same queue.
The current GameMaker-facing phase contract and known deviations live in runtime_managers.md. GameMaker’s reference order is linked from that canonical document.
Runtime state has three practical ownership levels:
| Scope | Examples | Expected lifetime |
|---|---|---|
| Static compatibility facade | Globals, handles, registries and compatibility helper state still owned by gml_runtime.gd
|
Across generated room scene changes |
| Autoload manager buckets | Domain registries, queue metadata and future migrated runtime state | Across generated room scene changes |
| Room/object scene state | Layer nodes, object instances, transient draw/collision data | Normally one room or frame unless explicitly preserved |
Persistent instances are reparented across generated room transitions. Their instance creation code is not rerun after restoration. Full persistent room state is not preserved: the generated scene enters again and lifecycle/room creation behavior runs, with a runtime warning. This is a deliberate, reported deviation rather than silent parity.
One-frame input edges, collision pairs, transient draw state, room-local layers, and non-persistent instances should be reset at their documented frame or room boundary. Add a regression test whenever custom runtime work moves state between ownership levels.
The gm2godot/ directory is also the evidence bundle for a conversion:
| Artifact | When it exists | Use it for |
|---|---|---|
conversion_attempt.json (format 1) |
Every terminal attempt after destination preflight | Latest terminal state, named step ledger, failure/cancellation context, and the canonical-manifest digest relationship |
conversion_manifest.json (format 2) |
Trustworthy success or usable partial conversion | Source metadata, enabled converters, resources, complete format-v1 generation inventory, source maps, generated-file hashes, architecture policy and path-collision diagnostics |
conversion_diagnostics.json / .md
|
Conversion/report pipeline | Structured warnings, errors, unsupported APIs, source locations, workarounds and outcome |
architecture_policy.json (format 1) |
Conversion policy publication | Project feature scan and selected runtime/backend policies |
platform_capability_report.json / .md
|
Static report generation | Target permissions, export presets, optional plugins and platform-service gaps |
extension_compatibility_report.json |
Extension metadata conversion | Native files, discovered functions, mappings, generated stubs and extension diagnostics |
godot_validation_report.json (format 1) |
validate with Godot validation enabled |
Destination-project import, loadable-resource scan and optional main-scene boot results |
*.gd.gmlmap.json |
GML source-map emission | Mapping generated GDScript lines back to GameMaker source/event context |
Important trust rules:
- Unsafe destinations rejected during preflight are not modified and do not receive an attempt ledger.
- A partial canonical manifest is written only when every requested converter step completed; its partiality comes from skipped or failed resources.
-
conversion_attempt.jsonand the optionalconversion_manifest.jsonare published as one recoverable generation through a verifiedgm2godot/directory binding. A durable.gm2godot-conversion-transaction.jsonrecords the complete previous and desired pair before either public file changes;.gm2godot-conversion-generation.jsonis the sole commit pointer. - Recovery under
.gm2godot-conversion.lockrestores the complete previous pair when the transaction has no matching pointer, or verifies and finalizes the complete new pair after the pointer switch. The public filenames, format-v1 attempt schema, and existing format-v2 manifest fields do not change;generation_inventoryis additive. - Consumers should still compare
conversion_attempt.json→canonical_manifest.sha256with the actual canonical manifest as a defense against later replacement or corruption. After migration to the generation pointer, a digest mismatch is rejected recovery state, not a normal interrupted-publication result. -
canonical_manifest.status = preservedis transaction-relative. It does not prove that an older manifest describes the destination after a failed or cancelled attempt. -
generation_inventory.entriesis the complete desired managed generation, including exact unchanged carry-forward for disabled converter steps and jointly managedproject.godot.generated_filesis the backward-compatible projection of that same frozen inventory.
The first 0.7.32 publication accepts a legacy pair only when its existing attempt digest agrees with the canonical bytes, or both files are absent. A mismatched, malformed, redirected, mounted, hard-linked, or otherwise replaced pair/recovery record is preserved and rejected for manual inspection. Recovery records are canonical JSON capped at 32 MiB and each embedded artifact is capped at 64 MiB uncompressed, so damaged or hostile state cannot request unbounded parsing.
The exact schemas and transaction rules are defined by generation_inventory.py, conversion_manifest.py, conversion_artifact_generation.py, conversion_outcome.py, and their inventory and manifest tests.
architecture_policy.json, the conversion_diagnostics.json / .md pair, and the four CLI static compatibility reports are staged, backed up, published, rolled back, recovered and cleaned through one destination-directory binding retained for each complete operation. Diagnostic snapshots and publication receipts bind both the report root and its exact gm2godot/ child. The static set commits gml_manual_scope.md, gml_api_compatibility.md, platform_capability_report.json, then platform_capability_report.md; ordinary failures restore every prior byte and exact mode rather than invalidating the set. An explicit external report root that does not exist is created one component at a time through its verified parent; each new directory entry crosses the parent durability barrier before creation descends further.
On POSIX hosts with the required APIs, every child lookup and namespace mutation is relative to a no-follow directory descriptor and durability barriers call fsync() on that retained descriptor. Replacing the visible gm2godot/ path therefore makes publication fail, but cannot redirect capture, restore, invalidation, cleanup or rollback into the replacement directory.
On Windows, GM2Godot opens both the report root and the exact captured gm2godot/ directory with reparse-point inspection and without delete sharing before it creates a stage. Child paths remain safe to resolve while those handles prevent either directory from being relocated; replacement requests use write-through completion. Publishing absence first moves the exact target behind a private write-through tombstone, then removes that tombstone on a best-effort basis. Windows has no documented general equivalent to POSIX directory fsync, so directory barriers revalidate the retained handle. This is a confinement and strongest-available write-through guarantee, not a claim of identical power-loss durability; a crash can leave hidden tombstone debris, but cannot restore its old public name.
If a non-Windows host lacks descriptor-relative open, stat, mkdir, rename or unlink, O_NOFOLLOW, O_DIRECTORY, or descriptor chmod, the transaction selects a verified_path fallback before staging. That fallback verifies the root and destination identities before and after each full-path operation. It rejects links and changed directories but narrows rather than eliminates the final non-cooperating path-resolution race. The backend is fixed for the transaction and never downgrades after a stage exists.
The retained directory binding does not lock individual artifact names against a non-cooperating writer. Exact identity, allowed-mode, byte, and SHA-256 checks run after staging, before every ordered mutation, and again after the observable before_replace / before_unlink hook and any deliberate Windows writable-mode preparation, immediately before the native namespace mutation. Replace revalidates both its immutable source stage and expected destination; unlink revalidates its exact target. Same-inode equal-size changes remain detectable even when their timestamps are restored. If a hard link appears while Windows writable-mode preparation is in progress, the transaction restores the original mode through the retained descriptor and aborts; later abort cleanup restores only the exact temporary mode it set, so an unrelated external mode change is not overwritten. A process that races the final system call can still win the remaining leaf-entry interval on platforms without a suitable handle-relative primitive. Close editors, games and other writers while conversion or recovery is running; unexpected exact-state changes fail the transaction and preserve verified recovery material instead of authorizing an overwrite.
Version 0.7.58 extends those checks to the backup lifecycle. A staged backup is revalidated after the observable after_backup hook, every backup is revalidated as one complete set before the first public mutation, and the applicable backup is checked again before its ordered commit and after before_commit. An ordinary error or control signal from backup staging durably removes only the exact owned stage; an in-place change, identity replacement, hard link, or other unverified same-name path is preserved and named in the failure instead of being trusted or deleted. Publication, snapshot restoration, and stale conversion-artifact retirement cannot return success when exact cleanup or its required durability barrier is incomplete, even if the public files already committed successfully.
Cancellation remains first-class across this transaction. Publication, restoration, reverse rollback and cleanup preserve the exact first KeyboardInterrupt, SystemExit, or other control-flow exception, including object identity and SystemExit.code; rollback still attempts every recorded mutation, while later failures and retained recovery paths are attached as notes. If a replace or unlink completed before the signal became observable, the transaction records the resulting ownership before rollback, and completed cleanup unlinks reach their applicable directory durability barrier before the exact signal is re-raised. Repeated exact probes resolve interrupted completion where possible; genuinely indeterminate private-stage ownership is preserved rather than guessed. An earlier ordinary operation error remains primary over a later ordinary cleanup error, with the cleanup details attached, while internal recovery-stage cleanup keeps its deliberate retry behavior.
The two diagnostic files retain stable public paths and ordinary-exception rollback, but they are still separate ordered replacements. A hard process or machine crash between their durability barriers can therefore expose one old file and one new file. Treat the pair as belonging to one successful invocation only when their outcome and surrounding attempt evidence agree; a future generation protocol is required for old-or-new crash atomicity across the pair.
GM2Godot does not guess how a native GameMaker extension, SDK, storefront, ad network, or analytics library should behave in Godot. Unmapped extension calls are not emitted as raw GDScript.
Put an explicit mapping file at the GameMaker project root:
{
"functions": {
"ads_show_rewarded": {
"target": "AdBridge.show_rewarded",
"min_args": 1,
"max_args": 1
},
"analytics_event": "AnalyticsBridge.event"
}
}The target becomes the emitted Godot call target. Back it with a reviewed script, addon, autoload, or GDExtension that handles platform permissions and SDK setup. For repeat conversion, keep that implementation outside res://addons/gm2godot_extensions/, because that subtree is generated.
GM2Godot emits disabled-by-default stubs under res://addons/gm2godot_extensions/<extension>/. Their methods call push_error() until a project-specific implementation replaces the behavior. Platform-service hooks and extension async schemas can route results through the generated GMAsync queue, preserving callback payload context and async_load scoping.
Use the full mapping, platform-hook, and callback-schema contract in extension_compatibility.md. Always inspect extension_compatibility_report.json before enabling an extension on an export target.
| Area | Current behavior |
|---|---|
| Runtime-authored collision masks | Imported static and per-frame precise sprite masks are generated exactly within the documented complexity bound; runtime mask mutation, mask_index, skeletal/tile masks, and physics fixtures still need project-specific review. |
| Advanced particle behavior | Authored systems and room elements instantiate managed emitters, but legacy field modifiers and engine-specific distribution, timing, wiggle, flipbook, and secondary-spawn details need diagnostics-guided visual review. |
| Persistent rooms | Persistent instances are carried across room changes, but full persistent room state is not retained. |
| Shaders and GPU state | Supported 2D attributes, varyings, uniforms, base-texture sampling, and matrix transforms compile under exact Godot 4.7.2; custom vertex streams, macros, 3D/clip-space logic, multi-pass effects, GPU state, and visual parity remain manual review areas. |
| Native extensions and platform services | Closed binaries, entitlements, permissions and SDK initialization require explicit reviewed Godot integrations. |
game_end |
Maps to SceneTree.quit(); platform-specific close prompts and window behavior are not emulated. |
| Custom Godot callbacks/signals | Only generated manager queues participate in the GameMaker ordering contract. Direct custom callbacks can observe a different order. |
| Architecture feature detection | Policy selection is based on indexed project metadata and GML feature scanning. It guides review; it is not a runtime-equivalence proof. |
The complete and frequently changing coverage matrix belongs on Compatibility and Limitations and in the repository’s todo-list/. Do not infer support merely because a function transpiles or a resource loads.
Use the exact pinned Godot build for resource and runtime validation:
python main.py validate \
--godot-project /path/to/GodotProject \
--godot-bin /path/to/Godot-4.7.2 \
--godot-boot-frames 120 \
--fail-on-unsupportedValidation imports supported asset types, loads every .gd, .tscn, .tres, and .gdshader resource under the destination project except .godot/, and can boot the configured main scene for the requested frame count. Read the first warning/error in gm2godot/godot_validation_report.json, then correlate it with conversion diagnostics and any .gmlmap.json source map.
Headless import, resource, and boot validation captures bounded combined stdout/stderr. At reader shutdown it makes one final nonblocking read attempt, preserving buffered diagnostics without waiting indefinitely for inherited stdout descriptors.
For report interpretation and failure recovery, continue to Diagnostics and Troubleshooting. For implementation changes, use Contributing and Testing and preserve the runtime/architecture contracts covered by the Godot-backed tests.