Migrating to Hammerite 2.0

Migrate a 1.x project before you open it in 2.0. Every .tres, .res and scene that 1.x saved names Hammerite's objects by the scripts they were - res://addons/hammerite-core/scripts/resources/map.gd, brush.gd, map_3d.gd and the rest - and 2.0 has none of those scripts: no stub at their paths, and no loader that reads such a file as the native classes. Until it is migrated a file naming one does not load at all. Godot says Failed loading resource: res://addons/hammerite-core/scripts/resources/map.gd for each script it names, and then the same of the file, so a map, a bake, a prefab set or a scene placing a HammeriteMap3D comes back null and the game does not start.

  1. Commit the project, or copy it.
  2. Install 2.0 in place of 1.x: every addon folder, with its library (What 2.0 needs).
  3. Migrate it: tools/migrate_to_2.sh path/to/project, from the release's docs zip, or Project → Tools → Migrate Hammerite 1.x Resources... in the Godot editor with the full package (Migrating a project). Opening the project first writes nothing by itself, but when the Godot editor offers to open a scene with missing dependencies anyway, or to fix them, decline: a scene saved that way has lost what it held of Hammerite's.
  4. Change the code 2.0's API breaks: the rest of this page.

What needs nothing: a .hmap saved by any earlier Hammerite opens, and so does every other data file - a .hcache worldrep or navigation cache, a variant's bake, the editor's .hworkspace and its autosaves. They name classes, not scripts, and read as they did.

Caches are migrated with the rest, and one that is not is only a cache. level.worldrep.res and level.nav.res, which 1.x wrote beside a map saved as a .res, fail to load as anything else 1.x saved does - with the same errors, on every load - and are then treated as missing: the world is compiled from the map and its navigation built again, and both are written in 2.0's form the next time the editor saves the map. Migrate them, or delete them, rather than leave them to print errors. Hammerite writes nothing else of its own under user://: the editor's workspace and autosaves are data files, and the lightmapper's diagnostic reports are never read back. What a game keeps there itself - its players' maps saved as .res, say - is migrated by pointing the editor's dialog at that folder, or by running the command on it. A game can call HammeriteMigration.migrate("user://maps") itself, but the class is experimental: it may change in any 2.x release, where the command and the menu items will not.

The rest of this page is what changed. 2.0 moves the addons' classes from GDScript into the native library (#263). A class keeps its name, its base class and what it does - checked against its script, call for call, before the script was deleted - but a native class cannot express everything a script could, and where it cannot, the API changes. Every such change is here, and every change of behaviour.

What 2.0 needs

  • The native library, always. 1.x ran on GDScript and used its native kernel where one was built; 2.0's classes are the library. It is addons/hammerite-core/bin/libhammerite.<platform>, loaded by hammerite_core.gdextension for all four addons, and there is no GDScript to fall back on: a project without it does not load core, and says "Hammerite's native library is not loaded". No native_* switch chooses any more (the kernel's classes and switches).
  • One of two packages, installed whole (#273). hammerite-runtime-<version>.zip is core, audio and nav, for a game whose players never open the map editor; hammerite-full-<version>.zip is those and the editor. 1.x's four addons were installed one by one; 2.0's come as a package's folders with the library built for exactly those, so replace all four 1.x folders with one package's. A hammerite-editor folder beside the runtime package's library has none of its classes, and a full library without one is missing a folder: either is an error as the project starts. An addon a game does not use stays installed with its plugin disabled. See Installing.
  • macOS, Linux x86_64 and Windows x86_64. A release carries the library for macOS as one universal binary (Apple silicon and Intel), for Linux x86_64 and for Windows x86_64, in every package. The .gdextension also names a Linux arm64 library, which a checkout can build (tools/build_native.sh) but no release carries.
  • No web export. No library is built for the web, and without one core does not load, so a game exporting to the web cannot use 2.0. None will be until GDExtension on the web is dependable (#263). 1.x did not support the web either, but nothing stopped its GDScript loading there.
  • Godot 4.7, as 1.x. The library is built against godot-cpp's 4.5 API and its .gdextension loads in 4.5 or later, but the suites run on 4.7.1 and nothing earlier is tested.
  • No hot reload. The library is not reloadable: restart the Godot editor after replacing it.

Migrating a project

Migrate a project once, after installing 2.0, from the command line or from either editor:

tools/migrate_to_2.sh path/to/project --dry-run   # what would change; writes nothing
tools/migrate_to_2.sh path/to/project             # migrate, keeping each original as <file>.bak
tools/migrate_to_2.sh path/to/project --no-backup # without the .bak files

The tool is not in either package, which hold only what goes into addons/: it is in tools/ of hammerite-docs-<version>.zip, released beside them, and of this repository. Unzip the docs anywhere and run it from there, naming the project - path/to/hammerite-docs/tools/migrate_to_2.sh path/to/project - keeping migrate_to_2.sh and migrate_to_2.gd together. It is a bash script, so on Windows run it from Git Bash, or use the editor's command below. It runs Godot headless in the project - /Applications/Godot.app unless GODOT= names another binary, as it must on Linux and Windows - importing it first if the project has not been opened since 2.0 was installed - and calls HammeriteMigration.migrate("res://"). It lists each file it migrated, with the classes it was migrated for, and each it could not, with why, and exits 1 when there is any of those. Commit the project, or copy it, before running it.

The editor does the same, with the dry run shown before anything is written: Project → Tools → Migrate Hammerite 1.x Resources... in the Godot editor, which rescans the project afterwards and is the one to use while the game cannot start, and File → Migrate 1.x Resources… in Hammerite's editor, in the game. Each lists what it would migrate and what it cannot, with why, migrates on confirming - keeping the .bak files unless told not to - and lists what it did. It looks in res:// and takes another res:// or user:// folder, such as where a game keeps its players' maps. Both are the editor's, so the full package has them and the runtime package does not: with the runtime package, use the command line. It migrates what core, audio and nav saved, and reports a file naming one of the editor's scripts - a scene placing an editor node, or the editor's settings saved as a .tres - as skipped, with its classes, for the full package to migrate.

What 2.x promises is the command and the two menu items - tools/migrate_to_2.sh with its options, File → Migrate 1.x Resources… and Project → Tools → Migrate Hammerite 1.x Resources... - and what they migrate (API stability). HammeriteMigration, the class all three call, and the editors' dialog are experimental: a script can call them, but they may change in any release.

What it does to a file that names a 1.x script of Hammerite's, and nothing else:

  • each object the file holds as a Resource carrying a 1.x script - a map, a brush, a bake, a probe set, a light curve, a prefab, an audio graph, a navigation cache - is written as its native class, without the script;
  • in a text scene (.tscn), each node carrying one of Hammerite's 1.x node scripts - map_3d.gd, eye_adaptation_3d.gd, a light, a decal, a mover, a trigger, a logic entity, an entity base, audio's emitter, receiver and sound entities, nav's link entity - is that node class, without the script or the _custom_type_script the editor marked it with, keeping its place and its properties;
  • the scripts go from the file's [ext_resource] lines or external table, and load_steps and the script class of the file's main resource with them.

Every other byte of the file is as it was: text stays text, binary stays binary, and a compressed .res is compressed again as it was, with its uid. The file is rewritten where it lies rather than loaded and saved, so nothing in the project is loaded - a game's own script that does not compile in a headless run would otherwise cost the objects carrying it their values - and the rewritten file is read back before it is kept. A file naming no 1.x script is not touched, nor is anything in a hidden folder, one holding a .gdignore, or Hammerite's own folders under addons/. A second run finds nothing to do.

A file it cannot migrate it leaves as it was, and says why:

  • it names a script of Hammerite's that has no class in 2.0. hammerite-audio's acoustic classes (acoustic_node_graph.gd and the rest) were renamed to its audio ones before 1.0 (#249), so a map still holding an acoustic graph loads neither in 1.0 nor in 2.0: re-save it from the Hammerite that wrote it with its audio rebaked, or rebuild it;
  • a node carries a 1.x script on a node the scene does not make itself (an instance's, or an inherited scene's), or one whose class the native one does not extend: set the node's type in the Godot editor;
  • a binary scene (.scn) places a node by its 1.x script: save it as a .tscn from the Godot editor, and run the migration again.

A script of the game's that extends one of Hammerite's by path (extends "res://addons/hammerite-core/...gd") is the game's code, not a resource, and is not touched: extend the class by name.

2.0 ships without the 1.x compatibility layer its development builds had - a stub script at each 1.x path and a loader that read 1.x files as the native classes (#272) - and the migration needs neither: it knows each 1.x script by the path and the uid 1.x gave it. It ships with every 2.x release (API stability).

The entity interface

Core's scene nodes are native (#268): HammeriteMap3D, the entity bases, and every entity class of core's own sets. A game's entity class still extends a base (HammeritePointEntityNode3D, HammeritePointEntityAnimatableBody3D, HammeritePointEntityRigidBody3D, HammeriteBrushEntityNode3D, HammeriteBrushEntityStaticBody3D, HammeriteBrushEntityAnimatableBody3D, HammeriteBrushEntityArea3D) or one of core's entity classes, but it overrides the interface under the engine's convention: a method a script overrides is a native virtual, and a native virtual is a name with an underscore. The public names stay, and are what everybody calls - the map, the editor, a game: each dispatches to the virtual.

Rename every override to its underscore name. The signatures are unchanged.

# 1.x
extends HammeritePointEntityNode3D
func set_editor_active(active: bool) -> void: ...
func apply_entity_property(prop_name: StringName, value: Variant) -> void: ...
func trigger(impulse: Variant = null) -> void: ...

# 2.0
extends HammeritePointEntityNode3D
func _set_editor_active(active: bool) -> void: ...
func _apply_entity_property(prop_name: StringName, value: Variant) -> void: ...
func _trigger(impulse: Variant = null) -> void: ...
1.x override 2.0 virtual
set_entity(new_entity) _set_entity(new_entity) after the base has bound the entity
update_properties() _update_properties() after the base has applied every property
apply_entity_property(prop_name, value) _apply_entity_property(prop_name, value) required of a script extending a base; after the class's own
set_editor_active(active) _set_editor_active(active) required of a script extending a base, except HammeriteBrushEntityArea3D
draw_editor_gizmos(gizmos) _draw_editor_gizmos(gizmos)
draws_editor_gizmos_only_when_picked() _draws_editor_gizmos_only_when_picked()
get_editor_handles() _get_editor_handles()
move_editor_handle(handle_id, to) _move_editor_handle(handle_id, to)
get_aabb() _get_aabb()
can_simulate(), start_simulation(), stop_simulation(), reset_simulation() _can_simulate(), ...
get_runtime_properties(), get_runtime_property(key, default_value), set_runtime_property(key, value), reset_runtime_properties() _get_runtime_properties(), ...
can_trigger(), trigger(impulse) _can_trigger(), _trigger(impulse)
get_actions() _get_actions() merged over the class's own (Trigger)
run_action(action_id) _run_action(action_id) after the class's own
turn_to(facing) _turn_to(facing) point bases but the rigid body
set_light_modulation(modulation) _set_light_modulation(modulation) point bases
should_create_meshes(), should_create_colliders() _should_create_meshes(), _should_create_colliders() brush bases

An override left under its old name does not compile: it would hide a native method the engine calls, which GDScript refuses (Required virtuals).

super() goes. GDScript cannot call super() into a native virtual, nor super._init(), super._ready() or super._process() on a native class. Where an override called super() first - set_entity, update_properties, apply_entity_property on one of core's classes, get_actions, run_action - the class's own now runs first by itself, and the script's override after it, so drop the call:

# 1.x
func set_entity(new_entity: HammeriteEntity) -> void:
    super(new_entity)
    _offer_a_hand()

# 2.0
func _set_entity(new_entity: HammeriteEntity) -> void:
    _offer_a_hand()

Work an override did before super() has to find another place: it runs after the base now. A flag raised around super() to quiet _apply_entity_property while the entity is being bound starts raised instead, and _set_entity lowers it. get_actions()'s override answers only the actions it adds. Any other override replaces what the class does, as before - with no super() to reach what it replaced. A script on one of core's own classes that called super._process(delta) drops it: the class does its per-frame work from its notification, after the script's _process.

Overriding one of a script's virtuals is unchanged: a class extending a game's entity script still calls super._apply_entity_property(...).

A native class of core's has hooks of its own under the same convention: a script on a dynamic light overrides _get_lit_modulation() and may follow set_seen() with _set_seen(); one on a logic entity or a mover _get_icon(), _tick(), _reset(), _apply() and _wired_targets(); one on HammeritePropModel may follow set_room_seen() with _set_room_seen(); one on HammeriteThumbnailGenerator takes over _generate_thumbnail(), _generate_entity_thumbnail() or _generate_mesh_thumbnail().

A classname may name a native class. HammeriteMap3D builds an entity's node from a script class of that name or, where there is none, from a class the library registers; core's own (HammeritePointLight3D...) are native.

Required virtuals

1.x marked what a subclass had to write @abstract, and a script that left one out did not compile. A native class has no @abstract: what a script must write is a required virtual, and every one of them has an underscore.

1.x, @abstract 2.0, required virtual
set_editor_active(active) and apply_entity_property(prop_name, value) on HammeritePointEntityNode3D, HammeritePointEntityAnimatableBody3D, HammeritePointEntityRigidBody3D, HammeriteBrushEntityNode3D and HammeriteBrushEntityStaticBody3D _set_editor_active(active) and _apply_entity_property(prop_name, value), on those and on HammeriteBrushEntityAnimatableBody3D
apply_entity_property(prop_name, value) on HammeriteBrushEntityArea3D _apply_entity_property(prop_name, value); _set_editor_active is optional there, as set_editor_active was
HammeriteEditorTool's _tool_input(event), _tool_process(delta), _tool_exit() and get_tool_name() _tool_input(event), _tool_process(delta), _tool_exit() and _get_tool_name()
HammeritePropertyControl's get_value(), set_value_no_signal(value), set_disabled(disabled) and set_indeterminate(indeterminate) _get_value(), _set_value_no_signal(value), _set_disabled(disabled) and _set_indeterminate(indeterminate)
HammeriteDynamicLight3D._icon() none: the class is registered abstract, and only its own point, spot and directional lights extend it. A script that extended it extends HammeriteDynamicPointLight3D or one of the others, whose icon is theirs
- HammeriteEntitySet._register_types(), new since 1.0

A script overrides one by declaring it under that name with the signature in the class reference, as it would any engine virtual (func _set_editor_active(active: bool) -> void:). Where the public method of the same name is still what everybody calls, the class dispatches to the override.

One left out is an error when it is called, not when the script compiles. A script extending a base without _set_editor_active parses, and new() makes one where the @abstract script refused; the first call then reports

ERROR: Required virtual method HammeritePointEntityNode3D::_set_editor_active must be overridden before calling.

and does nothing - an engine error, which fails a suite. HammeriteMap3D still refuses a classname whose class does not extend a base, so a map never builds a base itself. The entity suites of a game should build every type it registers and call these once, which is what Hammerite's own do.

One written under its old public name does not compile:

Parse Error: The method "set_editor_active()" overrides a method from native class "HammeritePointEntityNode3D". This won't be called by the engine and may not work as expected. (Warning treated as error.)

The editor

The level editor is native (#271): every class of hammerite-editor, and the editor tools and extensions of hammerite-audio and hammerite-nav, in the library's editor, audio_editor and nav_editor modules, which only the full package has. They keep their names and what they do - each was held to its script, call for call and node for node, before the scripts were deleted - and the shapes that change are the same as core's. What a game that extends or drives the editor changes:

A tool overrides the underscore names. The editor calls tool_input(event), tool_process(delta) and tool_exit(), and get_tool_name(), get_status_text(), holds_cursor() and get_manual_page() as before; each dispatches to a virtual, which is what a script overrides, as an entity does. A tool is made with create(editor), and a script's own constructor calls setup(editor) where it called super(editor). The options panel is a class of its own.

# 1.x
extends HammeriteEditorTool

func _init(editor: HammeriteMapEditor) -> void:
    super(editor)

func get_tool_name() -> String:
    return "Patrol Route"

func get_status_text() -> String:
    return "Click to add a point"

func _tool_input(event: InputEvent) -> bool: ...

var popup := HammeriteEditorTool.SimpleToolPopup.new()
editor.set_active_tool(PatrolRouteTool.new(editor))
tool._tool_process(delta)

# 2.0
extends HammeriteEditorTool

func _init(editor: HammeriteMapEditor) -> void:
    setup(editor)

func _get_tool_name() -> String:
    return "Patrol Route"

func _get_status_text() -> String:
    return "Click to add a point"

func _tool_input(event: InputEvent) -> bool: ...

var popup := HammeriteEditorToolSimpleToolPopup.new()
editor.set_active_tool(PatrolRouteTool.new(editor))
tool.tool_process(delta)
1.x 2.0, what a script overrides what is called
_tool_input(event) _tool_input(event) tool_input(event)
_tool_process(delta) _tool_process(delta) tool_process(delta)
_tool_exit() _tool_exit() tool_exit()
get_tool_name() _get_tool_name() get_tool_name()
get_status_text() _get_status_text() get_status_text()
holds_cursor() _holds_cursor() holds_cursor()
get_manual_page() _get_manual_page() get_manual_page()

The editor's own tools are made with create(...) where new(...) took arguments: HammeriteTransformTool.create(editor, mode), HammeriteFaceEditTool.create(editor, popup), HammeriteFaceMoveTool.create(editor, origin), HammeriteExtrudeTool.create(editor, origin) and create(editor) for the rest, hammerite-audio's HammeriteAudioPortalTool and hammerite-nav's HammeriteNavigationTool among them.

A property control overrides the underscore names too. The panel calls get_value(), set_value_no_signal(value), set_disabled(disabled) and set_indeterminate(indeterminate), which dispatch to _get_value(), _set_value_no_signal(value), _set_disabled(disabled) and _set_indeterminate(indeterminate). set_value(value) is the class's own: it shows the value through _set_value_no_signal and emits value_changed with what _get_value then answers, so an override of it goes.

# 1.x
extends HammeritePropertyControl

func get_value() -> Variant:
    return _value

func set_value(new_value: Variant) -> void:
    set_value_no_signal(new_value)
    value_changed.emit(_value)

func set_value_no_signal(new_value: Variant) -> void:
    _value = new_value
    _refresh()

func set_disabled(disabled: bool) -> void: ...
func set_indeterminate(indeterminate: bool) -> void: ...

# 2.0
extends HammeritePropertyControl

func _get_value() -> Variant:
    return _value

func _set_value_no_signal(new_value: Variant) -> void:
    _value = new_value
    _refresh()

func _set_disabled(disabled: bool) -> void: ...
func _set_indeterminate(indeterminate: bool) -> void: ...

A script extending HammeriteChoiceProperty calls setup(initial, choices, allow_empty) where it called super(...), and one is made with HammeriteChoiceProperty.create(initial, choices, allow_empty = true). Everything else a game hangs on the editor - register_property_editor, register_property_choices, register_cell_overlay, add_toolbar_button, register_workspace and the rest - takes a Callable, as before.

A coroutine answers a Variant. A native method cannot await, so the editor's coroutines run on a stack of their own on the main thread and give the thread back where the script awaited, as the lightmapper's do. Awaited, each answers what it did; not awaited, it answers its result when it never had to wait, and otherwise a HammeriteEditorCoroutine's finished signal that carries it. HammeriteMapEditor.save_map(), save_map_to(), prompt_save_map_as(), autosave(), new_map(), open_map_at(), prompt_load_map() and prompt_unsaved_changes(), and HammeriteTextureBrowser.refresh():

if await editor.save_map() == OK: ...    # as in 1.x

Names that would hide the engine's take a suffix: a Window's min_size, so MIN_SIZE of the texture browser, the command launcher, the entity search and the manual browser is min_size_constant(); and Node.get_viewport(), so HammeriteMapEditor's and the editor cameras' viewport property has the accessors get_viewport_value() and set_viewport_value() (the property keeps its name; a tool's viewport is unchanged).

Static variables: HammeritePropertyEditor.label_width is get_label_width() / set_label_width(), and HammeriteObjectSelection.individual_key get_individual_key() / set_individual_key(). HammeriteNavigationTool.CRAMPED_CLEARANCE, a static var while the tool was a script, is cramped_clearance().

Inner classes:

1.x 2.0
HammeriteEditorTool.SimpleToolPopup HammeriteEditorToolSimpleToolPopup
HammeriteManual.SearchHit HammeriteManualSearchHit
HammeriteManualPage.Heading HammeriteManualPageHeading (HammeriteManualPage.headings holds them)
HammeriteCommandMatcher.Entry HammeriteCommandMatcherEntry
HammeriteEditorHandles.Offered HammeriteEditorHandlesOffered
HammeriteDarkMissionImporter.DarkObject HammeriteDarkMissionImporterDarkObject
HammeriteDarkMissionImporter.DarkPatrol HammeriteDarkMissionImporterDarkPatrol
HammeriteArchGeometry.ArchSpec HammeriteArchGeometryArchSpec
HammeriteEntityBrowser.EntityBrowserItem HammeriteEntityBrowserEntityBrowserItem
HammeriteMessageLog.LogEntry HammeriteMessageLogLogEntry

The importer's objects and patrols are typed: object.patrol is a HammeriteDarkMissionImporterDarkPatrol, and importer.objects, importer.patrols and importer.player_start hold the two classes. After 2.0.0 the importer is in no release, only the dev package a checkout builds (tools/build_native.sh dev, #279). The other inner classes are internal, which nothing outside the library names: HammeritePrefabBrowser.PrefabBrowserItem, HammeritePropBrowser.PropGrid, HammeriteManualTabs.Tab, HammeriteCellPreview2D.WallDrawing, HammeriteVertexTool.VertexIndex, HammeriteFaceEditTool.FaceAttributes, the dock's Strip, Chevron and DropZone, the importer's DarkSurface, DarkCell, WorldrepLayout, BrushRecord and Piece, and the map editor's private _GizmoPresenter and _SaveShield and the save status's _Spinner. HammeritePropertyEditor._DocumentedLabel is HammeritePropertyEditorDocumentedLabel.

A few signatures take one of these classes as a RefCounted, since an inner class could not be named where they are declared; the object passed is the class above, as before: HammeriteArchGeometry's functions and HammeriteArchBrushTool.build_spec() a HammeriteArchGeometryArchSpec, HammeriteCommandMatcher, HammeriteCommandLauncher (and its entry_chosen signal) and HammeriteMapEditor.launcher_entries() and remember_launched() a HammeriteCommandMatcherEntry, HammeriteEditorHandles a HammeriteEditorHandlesOffered, and HammeriteManualTabs.tabs its tabs.

Made with create(): HammeriteArchBrushTool.create(editor), HammeriteAudioPortalTool.create(editor), HammeriteBrush2D.create(brush, map_2d, cam_2d, settings), HammeriteBrushTool.create(editor), HammeriteCellPreview2D.create(map_2d, cam_2d), HammeriteChoiceProperty.create(initial, choices, allow_empty = true), HammeriteClipTool.create(editor), HammeriteCommandMatcherEntry.create(entry_id, entry_label, entry_run), HammeriteConeBrushTool.create(editor), HammeriteContextMenu.create(map_editor), HammeriteCubeBrushTool.create(editor), HammeriteCurveProperty.create(initial = null), HammeriteCylinderBrushTool.create(editor), HammeriteDecalTool.create(editor), HammeriteDockBox.create(box_id, box_title, box_content), HammeriteDockColumn.create(column_side), HammeriteEditorCamera.create(cam), HammeriteEditorCommand.create(command_id, command_label, command_action), HammeriteEditorHandles.create(editor), HammeriteEditorHandlesOffered.create(handle_owner, offered), HammeriteEditorTool.create(editor), HammeriteEntity2D.create(entity, map_2d), HammeriteEntityBrowserEntityBrowserItem.create(type_name, props), HammeriteEntityFacing.create(turned, handle_color = Color(1, 0.8, 0.25, 1)), HammeriteEntityTool.create(editor), HammeriteExtrudeTool.create(editor, origin), HammeriteFaceEditTool.create(editor, popup), HammeriteFaceHighlight3D.create(colors = null), HammeriteFaceMoveTool.create(editor, origin), HammeriteFragmentOutlines2D.create(chunks, layer, map_2d), HammeriteGrid.create(div = 1.0), HammeriteLightmapTool.create(editor), HammeriteManualBrowser.create(the_manual, the_workspace = null), HammeriteManualRenderer.create(manual), HammeriteMap2D.create(editor, cam_2d), HammeriteMapSave.create(of, to), HammeriteMarqueeSelection2D.create(cam, mode), HammeriteMessageLogLogEntry.create(msg, ts, t), HammeriteNarrowState.create(map_ref, visible_brushes, visible_entities = null), HammeriteNavigationTool.create(editor), HammeritePickResult.create(e, b, f), HammeritePolygonTool.create(editor), HammeritePrefabTool.create(editor), HammeritePropTool.create(editor), HammeritePropertyItem.create(default_value, hint = null), HammeriteQuadViewportContainer.create(editor), HammeriteRampBrushTool.create(editor), HammeriteScreenLines.create(dedupe = false, added = null), HammeriteSelectTool.create(editor), HammeriteSkyTool.create(editor), HammeriteStairBrushTool.create(editor), HammeriteTerrainTool.create(editor), HammeriteTransformHelper.create(transform_origin, camera, transform_grid), HammeriteTransformTool.create(editor, mode), HammeriteVariantProperty.create(initial = null), HammeriteVertexGizmo.create(editor), HammeriteVertexTool.create(editor) - every argument with the default it had. Without arguments, new() still works.

Constants that are methods now:

  • HammeriteAudioEditorExtension: MENU_COMMAND → menu_command()
  • HammeriteAudioPortalTool: PICK_RADIUS → pick_radius(), OUTLINE_OFFSET → outline_offset()
  • HammeriteTransformGizmo3D: AXIS_REACH → axis_reach()
  • HammeriteVertexGizmo: PICK_RADIUS → pick_radius(), HANDLE_SIZE → handle_size(), HANDLE_HOVER_SCALE → handle_hover_scale(), HANDLE_TINT → handle_tint(), HANDLE_TINT_HOVERED → handle_tint_hovered()
  • HammeriteVoidWindowOverlay3D: COLOR → color(), LIFT → lift()
  • HammeriteBrush2D: DETAIL_OUTLINE → detail_outline()
  • HammeriteCellPreview2D: WALL_HALF_WIDTH → wall_half_width(), PORTAL_HALF_WIDTH → portal_half_width()
  • HammeriteCommandMatcher: NO_MATCH → no_match(), AT_LABEL_START → at_label_start(), AT_WORD_START → at_word_start(), AS_INITIALS → as_initials(), INSIDE_LABEL → inside_label(), IN_ORDER → in_order(), IN_MENU_OR_KEYWORDS → in_menu_or_keywords(), PER_WORD_ALONG → per_word_along(), RECENT → recent()
  • HammeriteMapEditorCore: ANY_TYPE → any_type()
  • HammeriteEditorCamera2D: CAMERA_OFFSET → camera_offset(), ENTITY_PICK_MARGIN_PX → entity_pick_margin_px(), DRAG_THRESHOLD → drag_threshold(), ZOOM_TIMEOUT → zoom_timeout()
  • HammeriteEditorCamera3D: DRAG_THRESHOLD → drag_threshold(), MAX_PAN_DELTA → max_pan_delta(), ORBIT_DISTANCE → orbit_distance(), ORBIT_PITCH_LIMIT → orbit_pitch_limit(), PICK_DISTANCE → pick_distance()
  • HammeriteEditorHandles: GRAB_RADIUS → grab_radius(), RADIUS → radius(), HOVER_LIGHTEN → hover_lighten()
  • HammeriteEditorWorkspace: PATH → path(), SETTLE → settle()
  • HammeriteEntity2D: DEFAULT_SIZE → default_size(), CENTRE_DOT → centre_dot()
  • HammeriteEntityFacing: ID → id(), REACH_PAST_BOX → reach_past_box(), LEAST_REACH → least_reach()
  • HammeriteFacePicking: COINCIDENT_DISTANCE → coincident_distance()
  • HammeriteFragmentOutlines2D: HALF_WIDTH → half_width()
  • HammeriteGrid: MIN_DIVISION → min_division(), MAX_DIVISION → max_division()
  • HammeriteEditorCommands: FILE → file(), EDIT → edit(), SELECTION → selection(), VIEW → view(), VIEWPORT_3D → viewport_3d(), TOOLS → tools(), HELP → help(), MENU_ORDER → menu_order(), CTX_ENTITY → ctx_entity(), CTX_INSTANCE → ctx_instance(), CTX_BRUSH → ctx_brush(), CTX_FACE → ctx_face(), CTX_SELECTION → ctx_selection(), CTX_VIEW → ctx_view(), CONTEXT_ORDER → context_order()
  • HammeriteDarkMissionImporter: TEXELS_PER_SPAN → texels_per_span(), SPAN_UNITS → span_units(), WORLDREP_CHUNK → worldrep_chunk(), FIXED_SPAN → fixed_span(), SURFACE_PLANE_SLACK → surface_plane_slack(), NOT_DRAWN_RENDER_TYPES → not_drawn_render_types(), MOVING_LIGHT_MODES → moving_light_modes(), MATERIAL_SKY → material_sky(), MATERIAL_WATER → material_water(), MATERIAL_DEFAULT → material_default()
  • HammeriteDarkPrimals: CUBE_POINTS → cube_points(), CUBE_FACES → cube_faces(), WEDGE_POINTS → wedge_points(), WEDGE_FACES → wedge_faces(), DODECAHEDRON_POINTS → dodecahedron_points(), DODECAHEDRON_FACES → dodecahedron_faces()
  • HammeriteManual: INDEX → index()
  • HammeriteManualBrowser: MIN_SIZE → min_size_constant(), MONO_FACES → mono_faces()
  • HammeriteManualExport: ADDON_MANUAL_DIR → addon_manual_dir(), EXTRA_ROOTS_SETTING → extra_roots_setting()
  • HammeriteManualRenderer: MISSING → missing()
  • HammeriteMap2D: CUT_REACH → cut_reach(), OFF_CUT_ALPHA → off_cut_alpha()
  • HammeriteMap2DGizmos: DASH_PIXELS → dash_pixels()
  • HammeriteQuadViewportContainer: ENTITY_DRAG_KEY → entity_drag_key(), PROP_DRAG_KEY → prop_drag_key(), VIEW_MENU_MARGIN → view_menu_margin(), VIEW_NAMES → view_names(), HOLD_MARGIN → hold_margin()
  • HammeriteRulerInteraction: RULER_WIDTH → ruler_width(), TICK_MAJOR_SIZE → tick_major_size(), TICK_MINOR_SIZE → tick_minor_size(), SLICE_GRAB → slice_grab(), AXES → axes()
  • HammeriteBrushTool: DEFAULT_PLANE → default_plane(), DEFAULT_THICKNESS → default_thickness()
  • HammeriteArchGeometry: EPSILON → epsilon(), MIN_ARC_DEGREES → min_arc_degrees(), MAX_ARC_DEGREES → max_arc_degrees()
  • HammeritePropTool: PROP_CLASSNAME → prop_classname(), PROP_TYPE → prop_type(), DRAG_THRESHOLD → drag_threshold()
  • HammeriteSelectTool: DOUBLE_CLICK_DISTANCE → double_click_distance()
  • HammeriteStairBrushTool: MAX_STEP_HEIGHT → max_step_height(), ANGLE_EPSILON → angle_epsilon(), FLAT_EPSILON → flat_epsilon()
  • HammeriteTerrainTool: RADIUS_STEP → radius_step()
  • HammeriteTransformHelper: FACE_ON_LIMIT → face_on_limit(), COHERENT_NORMAL_LIMIT → coherent_normal_limit()
  • HammeriteActiveTexturePanel: SIDE_MIN → side_min(), SIDE_MAX → side_max()
  • HammeriteChoiceProperty: NOTHING → nothing(), POPUP_LIST_HEIGHT → popup_list_height(), POPUP_MIN_WIDTH → popup_min_width()
  • HammeriteCommandLauncher: MIN_SIZE → min_size_constant()
  • HammeriteCurveProperty: HEIGHT → height(), REACH → reach()
  • HammeriteDockBox: DRAG_KEY → drag_key()
  • HammeriteEditorCanvas: STEPS → steps(), MIN_SCALE → min_scale(), MAX_SCALE → max_scale()
  • HammeriteEditorTheme: CANVAS → canvas(), SURFACE → surface(), RAISED → raised(), LINE → line(), LINE_BRIGHT → line_bright(), TEXT → text(), TEXT_DIM → text_dim(), TEXT_FAINT → text_faint(), ACCENT → accent(), ACCENT_SUNK → accent_sunk(), WARNING → warning(), DANGER → danger(), VARIANT → variant()
  • HammeriteEntityBrowser: CELL → cell(), PICTURE_HEIGHT → picture_height()
  • HammeriteEntitySearchPopup: MIN_SIZE → min_size_constant()
  • HammeriteFaceLoopHelper: EPSILON → epsilon()
  • HammeriteMeasurementLabels3D: PIXEL_SIZE → pixel_size(), UPRIGHT_LEEWAY → upright_leeway()
  • HammeriteLayerTree: COLUMN_ICONS → column_icons(), COLUMN_ICON_SIZE → column_icon_size(), DRAG_KEY → drag_key(), ABSENT_COLOR → absent_color(), STALE_COLOR → stale_color(), UNBAKED_COLOR → unbaked_color(), BAKE_CHECK_DELAY → bake_check_delay(), MIN_HEIGHT → min_height(), MAX_HEIGHT → max_height()
  • HammeriteLightmapToolPopup: PARAMETERS → parameters(), FALLOFF_MODES → falloff_modes()
  • HammeriteMapEditor: ANY_TYPE → any_type(), PANEL_WIDTH_MIN → panel_width_min(), BROWSER_SHARE → browser_share(), ENTITY_SEARCH_SHARE → entity_search_share(), WIRED → wired(), LAUNCHER_SHARE → launcher_share(), AUTOSAVE_SECONDS → autosave_seconds()
  • HammeriteMessageLogStatusLabel: FLASH_SECONDS → flash_seconds()
  • HammeriteProblemBrowser: MIN_HEIGHT → min_height(), GRAPH_KIND_ORDER → graph_kind_order(), BAKE_KIND_ORDER → bake_kind_order()
  • HammeritePropBrowser: CELL → cell(), PICTURE_HEIGHT → picture_height()
  • HammeritePropertyEditor: LABEL_WIDTH_DEFAULT → label_width_default(), LABEL_WIDTH_MIN → label_width_min()
  • HammeritePropertyEditorDocumentedLabel: WIDTH → width()
  • HammeriteSelectionPanel: ICONS → icons(), PREFAB_BADGE → prefab_badge()
  • HammeriteSelectionPropertyEditor: ENTITY_DOC_PREFIX → entity_doc_prefix(), RUNTIME_REFRESH_INTERVAL → runtime_refresh_interval()
  • HammeriteSkyToolPopup: NUMBERS → numbers(), COLOURS → colours()
  • HammeriteTextureBrowser: MIN_SIZE → min_size_constant(), ZOOM_STEP → zoom_step()
  • HammeriteTextureBrowserItem: PER_UNIT → per_unit(), SMALLEST → smallest(), LARGEST → largest(), CAPTION → caption(), bg_texture → bg_texture()
  • HammeriteTextureBrowserLayout: COLUMN → column(), MIN_ZOOM → min_zoom(), MAX_ZOOM → max_zoom()
  • HammeriteToolHelpPanel: KEY_PATTERN → key_pattern(), PARAMETERS_OPEN → parameters_open()
  • HammeriteUvEditor: SHAPE_COLOR → shape_color(), SHAPE_FILL_COLOR → shape_fill_color(), HANDLE_COLOR → handle_color(), PIVOT_COLOR → pivot_color(), HANDLE_RADIUS → handle_radius(), ROTATE_HANDLE_OFFSET → rotate_handle_offset(), FIT_MARGIN → fit_margin(), ZOOM_SPEED → zoom_speed(), MIN_ZOOM → min_zoom(), MAX_ZOOM → max_zoom(), PAN_SPEED → pan_speed()
  • HammeriteVariantProperty: TYPES → types()
  • HammeriteVolumeWheel: DEADZONE → deadzone(), RADIUS_INNER → radius_inner(), RADIUS_OUTER → radius_outer()
  • HammeriteNavigationTool: ROUTE_ARRIVAL_TOLERANCE → route_arrival_tolerance(), ROUTE_COLOR → route_color(), LINK_COLORS → link_colors()
  • HammeriteNavigationEditorExtension: MENU_COMMAND → menu_command()

The editor's three scenes are gone: scenes/ui/property_editor.tscn, scenes/3d/vertex_gizmo.tscn and scenes/3d/transform_gizmo_3d.tscn. Each class builds the same nodes itself: HammeritePropertyEditor.new(), HammeriteVertexGizmo.create(editor) and HammeriteTransformGizmo3D.new() where a game instanced the scene.

The autoloads are thin scripts over native classes. HammeriteAudio is scripts/hammerite_audio.gd, one line extending HammeriteAudioAutoload, and HammeriteNavigation scripts/hammerite_navigation.gd extending HammeriteNavigationAutoload: the same paths and uids, so a project.godot naming them is unchanged, and the same members, which the reference documents. HammeriteEditor stays a script, and makes the editor's classes by name so that it compiles in a game installed without the editor: its editor and workspaces are typed Control, and integrate() answers one. Assign it to a HammeriteMapEditor to have it typed.

var map_editor: HammeriteMapEditor = HammeriteEditor.integrate(self)   # as in 1.x; a cast now, not a type

The migration knows the editor's scripts. The editor's scripts are deleted: a 1.x scene or resource naming one does not load until it is migrated, and tools/migrate_to_2.sh rewrites it. It places each of the 60 editor scripts whose class is a Node (the three scenes' scripts among them) as its native class, and writes the four Resources a user's settings were saved as - HammeriteEditorWorkspace, HammeriteMapEditorSettings, HammeriteGrid and HammeriteEditorColors - as theirs. The audio and nav editor scripts are RefCounted, which no scene places. A library without the editor (the runtime package) has none of those classes, so a file naming one is reported skipped, with its classes and that the full package migrates it, rather than failed. The autoload scripts stay where they were and are left alone.

Defaults are never shared instances. The engine takes a class's defaults from one instance it keeps, so a property a constructor filled was everybody's default, and the Godot editor printed "Instantiated ... used as default value" for each as it opened. HammeriteMap.probe_set is null until the map has one - once it is lit, or read from a file that has one - and a map made with HammeriteMap.new() and saved writes no probe set where it wrote an empty one. HammeriteMapEditorSettings.colors is null until an editor uses the settings; an editor's own always have them. HammeriteMapEditor.settings is made the first time it is asked for.

City of Thieves, a worked example: what it changed to run on the native editor.

  • HammeriteEditorTheme.CANVAS, TEXT, TEXT_DIM, TEXT_FAINT, ACCENT, WARNING and DANGER are canvas(), text(), text_dim(), text_faint(), accent(), warning() and danger().
  • HammeriteMapEditor.ANY_TYPE is any_type(), in register_property_editor(HammeriteMapEditor.any_type(), ...).
  • HammeriteChoiceProperty.new(...) and HammeriteCurveProperty.new(...) are create(...), and HammeriteEditorHandles.new(editor) create(editor).
  • Its spawn flags and light preset property controls override _get_value, _set_value_no_signal, _set_disabled and _set_indeterminate, and drop their set_value; its resource picker, extending HammeriteChoiceProperty, calls setup(...) where it called super(...).
  • Its patrol route tool calls setup(editor), overrides _get_tool_name and _get_status_text, and makes its panel with HammeriteEditorToolSimpleToolPopup.new().
  • Its Dark mission import names HammeriteDarkMissionImporterDarkObject and HammeriteDarkMissionImporterDarkPatrol, and HammeriteEditorHandles.gather() answers Array[HammeriteEditorHandlesOffered].

The shapes that change

A static variable becomes a pair of static methods, get_<name>() and set_<name>(value): GDScript reaches no static property of a native class.

HammeriteDataFile.last_error            # 1.x
HammeriteDataFile.get_last_error()      # 2.0
HammeriteSkybox.dome_material = sky     # 1.x
HammeriteSkybox.set_dome_material(sky)  # 2.0

A constant that is not an integer becomes a static method of its name in lower case: ClassDB binds only integer constants and enums. Integer constants and enums are unchanged (HammeriteBrush.VolumeType.AIR, HammeriteMap.BakeState.BAKED, HammeriteCarving.PLANE).

HammeriteDataFile.MAP_EXTENSION         # 1.x
HammeriteDataFile.map_extension()       # 2.0

A const of your own made from one is a constant expression no longer, since a method call is not: make it a static var or a literal.

const BASE: String = HammeriteReferences.BASE              # 1.x
static var BASE: String = HammeriteReferences.base()       # 2.0

The same goes for a match pattern, which has to be a constant: match the value itself (&"rotation").

An inner class becomes a class of its own, named owner and inner: GDExtension has no nested classes. Change every type annotation, is, typed array and .new() that names one.

1.x 2.0
HammeriteMap.BakeWarning HammeriteMapBakeWarning
HammeriteMap.GraphWarning HammeriteMapGraphWarning
HammeriteMap.LightingSnapshot HammeriteMapLightingSnapshot
HammeriteMap.BVHHitResult HammeriteMapBVHHitResult
HammeriteFace.UvAnchor HammeriteFaceUvAnchor
HammeriteDataFile.ReadReport HammeriteDataFileReadReport
HammeriteBVHBuilder.BVHNode HammeriteBVHBuilderBVHNode
HammeriteSubtractiveCompiler.Result HammeriteSubtractiveCompilerResult
HammeriteGizmoBuffer.Primitives HammeriteGizmoBufferPrimitives
HammeritePrefab.Placement HammeritePrefabPlacement
HammeriteLightmapBakeReport.PassSnapshot HammeriteLightmapBakeReportPassSnapshot
HammeriteLightmapperDiagnosticReport.PassData HammeriteLightmapperDiagnosticReportPassData
HammeriteLightmapper._Patch HammeriteLightmapperPatch (made with create(face, rect, world_rect, normal))
HammeriteLightmapper._BakedDecal HammeriteLightmapperBakedDecal
HammeriteNavigationRuntime.Prepared HammeriteNavigationRuntimePrepared
HammeriteAudioRuntime.Changes HammeriteAudioRuntimeChanges
HammeriteAudioMaterialSampler.WallSurvey HammeriteAudioMaterialSamplerWallSurvey
HammeriteAudioManager._SoundData HammeriteAudioManagerSoundData
HammeriteAudioManager._PositionalSoundData HammeriteAudioManagerPositionalSoundData
HammeriteAudioManager._EnvironmentalSoundData HammeriteAudioManagerEnvironmentalSoundData

Core's, audio's and nav's other inner classes are internal: HammeriteMap.AuthoredFaces and VariantWorld (both experimental in 1.x), HammeriteLightmapPacker.Group and Row, HammeriteLightmapperState.Grid, HammeriteLightmapper._EdgeNeighbour, HammeriteDataFile._Reader and _Writer, the compiler's (below), HammeriteAudioAmbience.Bed, HammeriteAudioNodeGraph._Search and HammeriteNavigationMeshWriter._RoomGround.

HammeriteEntitySets' own sets (Everything, Lights, Logic, Movers, Scenes, Particles) are internal classes nothing outside names: make them with HammeriteEntitySets.lights() and the rest, as before. Their constants moved onto HammeriteEntitySets: Logic.TINT is logic_tint(), Logic.RELAY_ICON logic_relay_icon() (and TIMER_ICON, COUNTER_ICON, TOGGLE_ICON, AUTO_ICON likewise), Movers.TINT movers_tint(), Scenes.EMPTY_BOX scenes_empty_box(), Particles.TINT particles_tint().

A constructor that takes arguments becomes static create(...): a native object is made with none. The arguments are named for what they are (origin, not p_origin). Without arguments, new() still works.

HammeriteSkyPropertySchema.new(&"clouds", "Clouds")      # 1.x
HammeriteSkyPropertySchema.create(&"clouds", "Clouds")   # 2.0

A coroutine cannot be a native method. HammeritePrefab.generate_thumbnail() and HammeritePrefabSet.regenerate_all_thumbnails() were coroutines; awaited, they answer as they did:

await prefab.generate_thumbnail(generator)        # true once preview_texture is set, as in 1.x
await prefab_set.regenerate_all_thumbnails(generator)

The lightmapper's bake(), quick_bake(), prepare(), transfer() and write_lightmaps() were coroutines too, and are awaited as before:

var lightmapper := HammeriteLightmapper.create(map, settings)   # new(map, settings) in 1.x
lightmapper.yield_to = get_tree()
if await lightmapper.bake(progress) == OK:
    HammeriteDataFile.write(map, path)

They run on a stack of their own on the main thread and give yield_to a frame where the script awaited one. What they answer is their answer when they never had to wait for a frame, and otherwise the signal HammeriteLightmapper.finished(result), which carries it. Their return type is Variant.

Not awaited, what the prefab methods answer is a Signal to await - HammeritePrefab.thumbnail_generated(has_thumbnail) and HammeritePrefabSet.thumbnails_regenerated() - while a generator that is a coroutine is still drawing, and the answer itself when it answered at once.

A script that extends a native class still does, and still overrides its virtuals; but it cannot call a bare virtual on an object that does not override it, so a native class with one binds a public method that calls it (HammeriteEntitySet.extend_editor(editor) for _extend_editor).

Containers are shared, as a script's were. A getter answers the object's own array or dictionary, typed as before, so changing it in place is changing the object - entity.properties[&"name"] = "x" works as it did. A typed array or dictionary of a moved class is typed by the class's name now, not by its script: get_typed_class_name() answers &"HammeriteBrush" where get_typed_script() answered the script.

Class by class

Static variables

  • HammeriteMap.local_cache_directory → get_local_cache_directory() / set_local_cache_directory()
  • HammeriteBrush.surface_material, sky_material → get_/set_ each; chroma_bleed → get_chroma_bleed(), set with the existing set_chroma_bleed(value, map = null)
  • HammeriteFace.looks_log → get_looks_log(), the log itself (cleared in place); looks_log_epoch → get_looks_log_epoch() / set_looks_log_epoch()
  • HammeriteSubtractiveCompiler.parallel, verify_program → get_/set_ each
  • HammeriteDataFile.last_error, last_warnings → get_/set_ each
  • HammeriteSkybox.dome_material → get_dome_material() / set_dome_material()
  • HammeriteTextureLibrary.search_paths, fallback_name, generation → get_/set_ each (the array get_search_paths() answers is the library's own); HammeriteModelLibrary.library → get_library() / set_library()
  • HammeriteNavigationRuntime.profiles → get_profiles() / set_profiles(), which warns of a bad table as the setter did. The HammeriteNavigation autoload's profiles is unchanged.
  • HammeriteAudioRuntime.changes, sound_types, mix_settings, materials, presets, material_resolver, ambient_resolver, sounds and ambient_bus → get_/set_ each: HammeriteAudioRuntime.get_sounds().search_paths = [...], HammeriteAudioRuntime.set_material_resolver(resolver). The setters do what assigning did (a new table reaches every manager, new presets declare the brush schema again). The HammeriteAudio autoload's properties are unchanged. A multi-line lambda assigned to one is given a name first and passed to the setter.
  • HammeriteAudioPortalProperties.NAMESPACE → get_namespace()

Constants that are methods now

  • HammeriteBrush: BRUSH_MATERIAL → stock_brush_material(), GHOSTED_BRUSH_MATERIAL → stock_ghosted_brush_material(), TRANSPARENT_BRUSH_MATERIAL → stock_transparent_brush_material(), CLIP_EPSILON → clip_epsilon(), SKY_MATERIAL → stock_sky_material(), VERTEX_WELD_EPSILON → vertex_weld_epsilon()
  • HammeriteDataFile: MAP_EXTENSION → map_extension(), CACHE_EXTENSION → cache_extension(), PREFAB_SET_EXTENSION → prefab_set_extension(), _MAGIC → _magic(), _TAG → _tag()
  • HammeriteDecalProjection: SURFACE_OFFSET → surface_offset(), ENTITY_CLASSNAME → entity_classname(), PLANE_TOLERANCE → plane_tolerance(), FACING_TOLERANCE → facing_tolerance()
  • HammeriteEntity: EDITOR_ONLY_PREFIX → editor_only_prefix(), KEY_BOX_BASIS → key_box_basis(), KEY_ROTATION → key_rotation(), KEY_SCALE → key_scale(), LEAST_SCALE → least_scale()
  • HammeriteEntityFiles: SCENES → scenes(), IMAGES → images()
  • HammeriteEntitySets: LIGHTS → lights_id(), LOGIC → logic_id(), MOVERS → movers_id(), SCENES → scenes_id(), PARTICLES → particles_id(), EVERYTHING → everything_id()
  • HammeriteEntityTypeRegistry: KEY_ROTATION_AXES → key_rotation_axes(), POINT_ENTITY_BASES → point_entity_bases(), BRUSH_ENTITY_BASES → brush_entity_bases()
  • HammeriteFace: TEXELS_PER_UNIT → texels_per_unit(), _FORMER_NAMES → _former_names(), UV_LOCK_PROPERTY → uv_lock_property()
  • HammeriteLightingProperties: SCHEMA_ID → schema_id(), KEY_CASTS_SHADOW → key_casts_shadow(), KEY_BAKE_STATIC → key_bake_static(), KEY_ENTITY_BAKE_STATIC → key_entity_bake_static(), KEY_ENTITY_CASTS_SHADOW → key_entity_casts_shadow(), DEFAULT_CASTS_SHADOW → default_casts_shadow(), DEFAULT_BAKE_STATIC → default_bake_static()
  • HammeriteMap: BASE_ENTITY_PROPS → base_entity_props(), BOUNDARY_NUDGE → boundary_nudge(), CELL_HASH_CELL_SIZE → cell_hash_cell_size(), LIGHT_TYPES → light_types(), PROBE_GATHER_RADIUS_CELLS → probe_gather_radius_cells(), PARTIAL_BAKE_FINGERPRINT → partial_bake_fingerprint(), WALL_OCCLUDER_THICKNESS → wall_occluder_thickness(), VOID_WALLS_KEY → void_walls_key(), SLIVER_GAP_MAX → sliver_gap_max()
  • HammeritePortal: PROPERTY_NAMESPACE → property_namespace(), NAME_FIELD → name_field()
  • HammeriteReferences: TEXTURE → texture(), DECAL → decal(), MODEL → model(), ENTITY_CLASS → entity_class(), BASE → base()
  • HammeriteSkybox: STOCK_DOME_MATERIAL → stock_dome_material(), _FORMER_NAMES → _former_names()
  • HammeriteSubtractiveCompiler: EPSILON_DIST → epsilon_dist(), MIN_CONTACT_AREA → min_contact_area(), WALL_GRID_CELL_SIZE → wall_grid_cell_size(), EXPOSURE_OFFSET → exposure_offset(), EXPOSURE_INSET → exposure_inset()
  • HammeriteWaterProperties: SCHEMA_ID → schema_id(), KEY_CLARITY → key_clarity(), KEY_TINT → key_tint(), KEY_DISTURBANCE → key_disturbance(), DEFAULT_CLARITY → default_clarity(), DEFAULT_TINT → default_tint(), DEFAULT_DISTURBANCE → default_disturbance()
  • HammeriteShaderGlobals: every name, EXPOSURE → exposure(), ALL → all(), DEFAULTS → defaults() and so on
  • HammeriteLightCurve: UNIFORM_LUT → uniform_lut() and the rest of the UNIFORM_*, MIN_LIT_LEVEL → min_lit_level()
  • HammeriteTextureLibrary: DECALS → decals(), EXTENSIONS → extensions()
  • HammeriteCompileProgress: STAGES → stages()
  • HammeriteBrushFillRegistry: WATER → water(), STOCK_WATER_MATERIAL → stock_water_material()
  • HammeriteVersion: STRING → string()
  • HammeriteAABBGrid: DEFAULT_CELL_SIZE → default_cell_size()
  • HammeriteGizmoBuffer: BOX_EDGES → box_edges()
  • HammeriteTerrain: ID_PROPERTY → id_property(), CELL_PROPERTY → cell_property(), HALF_PROPERTY → half_property(), SPACING_PROPERTY → spacing_property(), MIN_THICKNESS → min_thickness(), UP_EPSILON → up_epsilon(), EDGE_EPSILON → edge_epsilon() (MAX_CELLS is an integer and stays)
  • HammeriteSkyPresets: EARLY_MORNING → early_morning(), NOON → noon(), EVENING → evening(), DUSK → dusk(), NIGHT → night(), OVERCAST_GREYING → overcast_greying(), OVERCAST_LIGHT → overcast_light(), OVERCAST_SUN → overcast_sun(), TIMES → times()
  • HammeriteBrushEntityLighting: BLEND_SECONDS → blend_seconds(), REGATHER_DISTANCE → regather_distance()
  • HammeriteDecalSurface: COVERAGE_THRESHOLD → coverage_threshold()
  • HammeriteLightmapLight: STATIC_LIGHT_TYPES → static_light_types(), DIRECTIONAL_LIGHT_TYPES → directional_light_types(), SPOT_LIGHT_TYPES → spot_light_types(), DEFAULT_DIRECTION → default_direction()
  • HammeriteLightmapPacker: SHARED_ATLAS_FILL → shared_atlas_fill()
  • HammeriteLightmapperAntialias: RANGE_SIGMA → range_sigma()
  • HammeriteLightmapperDiagnosticReport: DEFAULT_DIRECTORY → default_directory()
  • HammeriteLightmapperState: WATER_CLUSTER_SIZE → water_cluster_size(), CLUSTER_SIZE → cluster_size(), CLUSTER_NEAR → cluster_near()
  • HammeriteMeshTriangleBVH: BOX_SLACK → box_slack()
  • HammeriteLightmapper: _CAUSTIC_WATER_DEPTH_RANGE → _caustic_water_depth_range()

  • HammeriteMap3D: NEAR_PLANE → near_plane(), FAR_PLANE → far_plane()

  • HammeriteCarriedLight3D: METRES_PER_UNIT → metres_per_unit(), UNITS_PER_METRE → units_per_metre() (SHADOW_LAYER_NUMBER and SHADOW_LAYER are integers and stay)
  • HammeriteSkyRenderer: SKY_MATERIAL → stock_sky_material(); its static sky_material → get_sky_material() / set_sky_material()
  • HammeriteDecal3D: DECAL_MATERIAL → decal_material(); HammeriteSpotLight3D: UNBOUNDED_CONE_LENGTH → unbounded_cone_length()
  • HammeriteStaticBrush3D: DETAIL_TINT → detail_tint(); HammeriteAirBrushPreview3D: IDLE_COLOR → idle_color(), SELECTED_COLOR → selected_color()
  • HammeritePropModel: BRUSH_MATERIAL → brush_material(), TEXTURE_KEY_PREFIX → texture_key_prefix(); HammeritePropScene: EMPTY_BOX → empty_box()
  • HammeriteThumbnailGenerator: THUMBNAIL_BACKGROUND → thumbnail_background(), DEFAULT_ENTITY_BOX → default_entity_box(), DEFAULT_ENTITY_TINT → default_entity_tint(), FRAMED_LOOSELY → framed_loosely(), FRAMED_TIGHTLY → framed_tightly()
  • HammeriteEntityTargets: WIRE_COLOR → wire_color(); HammeriteLogicEntity, HammeriteMover, HammeritePathCorner: TINT → tint(); HammeriteLogicAuto, HammeriteLogicCounter, HammeriteLogicRelay, HammeriteLogicTimer, HammeriteLogicToggle: ICON → icon()

  • HammeriteNavigationBuilder: MAX_WALKABLE_SLOPE_DEGREES → max_walkable_slope_degrees(), NAV_ROLE_KEY → nav_role_key() (PropRole is an enum and stays)

  • HammeriteNavigationLinkBuilder: DEFAULT_STEP_HEIGHT → default_step_height(), MAX_DROP → max_drop(), MAX_JUMP_SPAN → max_jump_span(), MAX_JUMP_RISE → max_jump_rise(), ENTITY_TYPE → entity_type(), TARGET_KEY → target_key(), NAME_KEY → name_key(), ONE_WAY_KEY → one_way_key()
  • HammeriteNavigationLinkData: NO_PORTAL → no_portal() (Kind is an enum and stays); HammeriteNavigationAgentProfile: MEASURED_SHORT → measured_short()
  • HammeriteNavigationBrushProperties: SCHEMA_ID → schema_id(), KEY_TRAVEL_COST → key_travel_cost(), DEFAULT_TRAVEL_COST → default_travel_cost()
  • HammeriteNavigationRuntime: STAGES → stages(), DERIVED_KEY → derived_key() (CACHE_VERSION is an integer and stays)
  • HammeriteNavigationMeshWriter: LINK_NAME_META → link_name_meta() (DEFAULT_LAYERS stays); HammeriteNavigationFloorEdges: BUCKET_SIZE → bucket_size()
  • HammeriteNavigationOverlay3D: LIFT → lift(), OPEN_COLOR → open_color(), CRAMPED_COLOR → cramped_color(), UNUSABLE_COLOR → unusable_color(), FALLBACK_CRAMPED_CLEARANCE → fallback_cramped_clearance(); HammeriteNavigationTool.CRAMPED_CLEARANCE, made of it, is cramped_clearance()

A key used as a match pattern has to be a constant: match the property's own name (&"nav_link_target"), or compare against HammeriteNavigationLinkBuilder.target_key() outside the match.

  • HammeriteAudioRuntime: GRAPH_KEY → graph_key(), AMBIENCE → ambience(), SOUND_FILE → sound_file()
  • HammeriteAudioBrushProperties: SCHEMA_ID → schema_id(), KEY_ZONE_TYPE → key_zone_type(), KEY_PRESET → key_preset(), KEY_WEATHER_EXPOSURE → key_weather_exposure(), KEY_EXCLUDE → key_exclude(), KEY_AMBIENT → key_ambient(), KEY_AMBIENT_VOLUME → key_ambient_volume() (PRESET_AUTO and ZoneTypeChoice stay)
  • HammeriteAudioManager: BUS_NAME → bus_name(), _SILENT_DB → _silent_db(), _MIN_PREDELAY_MSEC → _min_predelay_msec(); a game's const made of BUS_NAME is a static var
  • HammeriteAudioCalculations: SPEED_OF_SOUND, AIR_ABSORPTION_COEFFICIENT, OPEN_AIR_CUTOFF_HZ, MIN_CUTOFF_HZ, OCCLUSION_DB_PER_OCTAVE, DETOUR_UNITS_PER_OCTAVE, AIR_UNITS_PER_OCTAVE → the same in lower case (open_air_cutoff_hz()...)
  • HammeriteAudioWater: SURFACE_LOSS_DB → surface_loss_db(), SURFACE_CUTOFF_HZ → surface_cutoff_hz(), UNDERWATER_CUTOFF_HZ → underwater_cutoff_hz()
  • HammeriteAudioZone: METRES_PER_UNIT → metres_per_unit(), SPEED_OF_SOUND → speed_of_sound() (ZoneType stays)
  • HammeriteAudioMaterialSampler: SKY → sky(), OPEN_AIR_SKY_SHARE → open_air_sky_share(), SMALL_ROOM_SIZE → small_room_size(), LARGE_ROOM_SIZE → large_room_size(), CORRIDOR_ASPECT → corridor_aspect(), CORRIDOR_MAX_WIDTH → corridor_max_width()
  • HammeriteAudioInterpolator: PRESET_BLEND_RADIUS → preset_blend_radius(); HammeriteAudioPortalProperties: DEFAULT_MAX_ATTENUATION_DB → default_max_attenuation_db(), DEFAULT_OCCLUSION → default_occlusion()

Made with create()

HammeriteDecalProjection.create(origin, normal, size, angle), HammeriteBrushPropertySchema.create(id, label = ""), HammeriteSkyPropertySchema.create(id, label = ""), HammeriteDataFileWrite.create(...), HammeriteBrushFill.create(...), HammeriteReferenceChange.create(...), HammeriteEditorHandle.create(...), HammeriteQuad3D.create(...), HammeriteOrientedBoundingBox.create(...), HammeriteAABBGrid.create(...), HammeriteEntityBox.create(...), HammeriteAssetLibrary.create(type, extensions, convert), HammeriteVertex.create(position, faces = []), HammeriteBakeOccluder.create(owner_entity, world_vertices), HammeriteBrushEntityLighting.create(node, drives_itself = false), HammeriteRoomVisibility.create(map), HammeriteWorldrepCompile.create(map), HammeriteLightmapper.create(map, bake_settings = null), HammeriteLightmapperState.create(map), HammeriteLightmapLight.create(entity), HammeriteLightmapPacker.create(texture_size, padding), HammeriteLightmapFaceData.create(face, bounds, world_bounds, verts_3d, verts_2d, lightmap_scale), HammeriteMeshChartData.create(entity, chart_index, triangles), HammeriteMeshTriangleData.create(entity, tri_index, surface_index, vertices, lightmap_scale, uvs = [], texture = null, normal = Vector3.ZERO), HammeriteLightmapperPatch.create(face, rect, world_rect, normal), HammeriteStaticBrush3D.create(brush), HammeriteAirBrushPreview3D.create(brush), HammeriteAudioMaterial.create(name, reverb, absorption, transmission_loss, freq_bias, category), HammeriteAudioPreset.create(id, name, room_size, damping, spread, wet, dry, abs_low, abs_mid, abs_high), HammeriteAudioNode.create(node_id, pos), HammeriteAudioHearing.create(mixer), HammeriteAudioAmbience.create(map, parent) - every argument with the default it had.

Other signatures

  • HammeriteMap.native_cell_index() answers a RefCounted rather than an Object. Handed out as a bare Object, a reference-counted index lost a reference each time a script asked for it.
  • HammeriteLayer's name is its name property as before; its accessors are _get_name()/_set_name(), so as not to hide Resource.get_name().
  • HammeriteBrushEntity._brushes keeps its name - it is what a file stores - with _get_brushes()/ _set_brushes(), answering the stored array; get_brushes() answers a copy, as before.

  • HammeriteEntityTypeRegistry.is_entity_class(class, brush) takes a class's name (&"HammeriteSpotLight3D") as well as a script, and entity_classes() lists the native entity classes beside the scripts'.

  • The thumbnail generator's generate_thumbnail(), generate_entity_thumbnail() and generate_mesh_thumbnail() were coroutines: each answers the picture at once where it has it, and otherwise a signal to await - awaited, as before.
  • HammeriteBrush3DHelper.BrushFollower is the internal class HammeriteBrushFollower, which a script cannot name.
  • HammeritePlatform.path and HammeritePathCorner.next keep their names; their accessors are get_path_name()/ set_path_name() and get_next_name()/set_next_name(), since Node has get_path().
  • HammeriteAudioPreset, HammeriteAudioZone and HammeriteAudioPortal keep their name property; its accessors are _get_name()/_set_name(), so as not to hide Resource.get_name().
  • A script on HammeriteEnvSound or HammeriteEnvSoundEnvironment overrides _play_sound(), a virtual now, and calls play_sound() - the class's own - where it called super(). It drops super() from _apply_entity_property (the class applies its own properties first) and super(delta) from _process (the class does its frame from its notification). The privates a game reached are bound under their own names: _sound_file, _volume, _autoplay, _loop, _sound_handle, _is_simulating, _runtime_properties, and _max_distance, _attenuation_scale, _use_audio_propagation, _debug_draw or _direction; _stop_sound() and _get_map_3d() are bound methods.

```gdscript # 1.x func _play_sound() -> void: _sound_file = pick_a_recording() super()

# 2.0 func _play_sound() -> void: _sound_file = pick_a_recording() play_sound() `` - **HammeriteAudioReceiver3DandHammeriteAudioEmitter3D** do their tree and frame work from notifications, so a script on one keeps its own_enter_tree,_ready,_processand_exit_treeand cannot callsuper()in them. A receiver registers again once ready, after a script's_readyhas set its types, which is whatsuper()at the end of_ready` was for: drop it.

The native kernel's classes and switches

1.x's native kernel was optional: a set of HammeriteNative* classes that GDScript called where one was built, each behind a switch that turned it off. In 2.0 the library is the classes, so the kernel's classes are their C++ and the switches are gone with the GDScript they chose between. None of it was supported API - the classes were undocumented and the switches tagged experimental - and there is no project setting to remove: 1.x had none.

1.x kernel class In 2.0
HammeriteNativeKernel, and HammeriteNative.KERNEL_VERSION, KERNEL_SOURCE, KERNEL_CLASS, OLDEST_CLASS and verdict() HammeriteLibrary: the package the library was built as (package()), its modules(), and the hash of the sources it was built from (source()), which HammeriteNative.problem() compares with a checkout's. There is no version to raise: tools/build_native.sh --bump is gone, and HammeriteNative.source_hash() takes the modules too. Both are experimental
HammeriteNativeAABBGrid HammeriteAABBGrid itself
HammeriteNativeCarver, HammeriteNativeWalls, HammeriteNativePortals HammeriteSubtractiveCompiler's own C++
HammeriteNativePickIndex, HammeriteNativeOccluders HammeriteMap's own C++
HammeriteNativeCellIndex still registered, as the index HammeriteMap.native_cell_index() answers (a RefCounted); internal
HammeriteNativeBrushes still registered, as HammeriteBrush's corners; internal
HammeriteNativeProbeGrid HammeriteLightProbeSet's own C++
HammeriteNativeTexels, HammeriteNativeGather, HammeriteNativeAtlas the lightmapper's own C++
HammeriteNativeSoundRoutes, HammeriteNativePortalWalks HammeriteAudioNodeGraph's and HammeriteAudioGraphBuilder's own C++, in the audio module
HammeriteNativeNavGround, HammeriteNativeNavLinks HammeriteNavigationFloorEdges' and HammeriteNavigationLinkBuilder's own C++, in the nav module; HammeriteNativeNavLinks.probe() is HammeriteNavigationLinkBuilder._probe()
HammeriteNativeScreenLines HammeriteScreenLines' own C++, in the editor module

Kernel classes added after 1.0 went the same way: HammeriteNativeDataReader (HammeriteDataFile), HammeriteNativeRoomMesh (HammeriteMap3D), HammeriteNativeSurfaceJoins (the compiler), and HammeriteNativeLegacyResources, whose reading of 1.x files is HammeriteMigration's now. HammeriteNative itself stays a script, experimental, saying whether the library is loaded and current (available, has(), problem(), package(), modules()).

1.x switch the kernel class it named
HammeriteAABBGrid.use_native, NATIVE_CLASS HammeriteNativeAABBGrid
HammeriteBrush.native_corners, NATIVE_BRUSHES HammeriteNativeBrushes
HammeriteMap.native_cells, NATIVE_CELL_INDEX HammeriteNativeCellIndex
HammeriteMap.native_picking, NATIVE_PICK_INDEX HammeriteNativePickIndex
HammeriteMap.native_occluders, NATIVE_OCCLUDERS HammeriteNativeOccluders
HammeriteSubtractiveCompiler.native_walls, native_carving, native_portals and their NATIVE_WALLS, NATIVE_CARVER, NATIVE_PORTALS the walls, carver and portals
HammeriteLightProbeSet.native_grid, NATIVE_PROBE_GRID HammeriteNativeProbeGrid
HammeriteLightmapperAntialias.use_native, NATIVE_ATLAS HammeriteNativeAtlas
HammeriteLightmapperState.native_gather, NATIVE_GATHER HammeriteNativeGather
HammeriteLightmapper._native_texels, _NATIVE_TEXELS, _native_atlas HammeriteNativeTexels, HammeriteNativeAtlas
HammeriteAudioNodeGraph.native_routes, NATIVE_SOUND_ROUTES HammeriteNativeSoundRoutes
HammeriteAudioGraphBuilder.native_walks, NATIVE_PORTAL_WALKS HammeriteNativePortalWalks
HammeriteNavigationFloorEdges.native, NATIVE_KERNEL HammeriteNativeNavGround
HammeriteNavigationLinkBuilder._native_jumps, _NATIVE_NAV_LINKS HammeriteNativeNavLinks
HammeriteScreenLines.native_lines, NATIVE_SCREEN_LINES, native_screen_lines() HammeriteNativeScreenLines

And those added after 1.0: HammeriteSubtractiveCompiler.native_joins, HammeriteDataFile.native_fill, HammeriteMap3D.native_room_mesh, NATIVE_ROOM_MESH, _use_native_rooms and _inward_surfaces_natively(). What the suites held each kernel to against the GDScript is now a fixture of what the GDScript answered, recorded before it went (tests/fixtures/kernels.json in core, audio, nav and the editor).

What the suites reached

Private members are not API, but a game reaching into one should know where it went.

  • Bound under the same name: HammeriteMap._worldrep_compiler, _worldrep_valid, _graph_valid, _lighting_memo, _void_chunks, _sliver_bounds, _sliver_gap_of, _rebuild_baked_light_registry(), _as_variant(), _face_address(), _read_worldrep_cache(), _middles_of(), _sliver_gaps(); HammeriteBrush._fill_uniforms(); HammeriteFace._plane, _texture, _lightmap, _uv_transform, _lightmap_scale, _brush; HammeriteSubtractiveCompiler._program_memo, _brush_volume(), _air_regions(), _punched(), _section(); HammeriteDataFile._replace_file().
  • Bound under the same name, the lightmapper's: HammeriteLightmapper._state, _packer, _map, _lightmaps, _rd, _quick, _decals, _mesh_charts, _mesh_triangles_in (read-only where only read), _get_faces(), _unwrap_mesh_entity(), _apply_decals(), _sky_radiance(), _report_bake_warnings(), _save_diagnostic_image(), _swizzle_mesh_triangle_light_assignments() and the statics _fold_light(), _padding_light(), _padding_sample_point(), _permute_patch_slots(), _caustic_water_color(), _dilate_caustic_water(), _baked_light_of(), _write_face(); HammeriteLightmapperState._face_exterior_cache, _mesh_bvh, _pass_faces, _water_emitter_rows, _face_is_exterior(), _fill_exterior_cache(), _gather_grids() and the statics _face_transparency(), _face_uv_bytes(), _mask_layer_size(); HammeriteLightmapPatches._faces, _face_of, _triangle_of, _texture_rects, _world_rects, _normals, _areas, _texture_centers, _flags, _padding_lights (read-only); HammeriteLightmapLight._sky_followed(). The rest of their private members are their own C++.
  • Changed: HammeriteMap._variant_worlds[id].compiler → _variant_compiler(id); HammeriteSubtractiveCompiler._solid_frag_cache and _air_frag_cache → _carve_fragments() (every cached fragment) and _carve_serial(brush_id) (0 when not cached, a number that changes on each carve); HammeriteBrush._warned_undeclared → _get_warned_undeclared(); HammeriteDataFile._MAGIC, _TAG → _magic(), _tag(); HammeriteDataFile._upgrades → _get_upgrades(); HammeriteReferences._resolvers → _get_resolvers(); HammeriteBakeOccluders._providers → _get_providers(); HammeriteWorldrepCompile._stages → _get_stages() (a copy, each stage [names, run] as before) and _under_way → _get_under_way().
  • Gone: HammeriteMap._pick_index, _rebuild_cell_hash(); the compiler's _bface_cache, _kept_by_air, _air_region_grid(), _classify_fragment_faces(), _kept_owners(), FaceEntry, WallMachinery and the rest of its inner classes, which are its own C++.

  • The node classes: what a test drove a frame with is a method of its own, since a native class answers _process as a notification: HammeriteMap3D._run_frame(delta), HammeriteDynamicLight3D._run_frame(delta), and _step(delta) on the logic entities and the movers (their _process and _physics_process). The privates the suites read are bound under their own names (HammeriteMap3D._entity_index, _room_meshes, _cull_from()...), as properties where they were variables. HammeriteMap3D.native_room_mesh, NATIVE_ROOM_MESH, _use_native_rooms and _inward_surfaces_natively() are gone with the script they chose against, and HammeriteNativeRoomMesh is the map's own C++, no longer registered.

  • hammerite-nav (#270): HammeriteNavigationFloorEdges.native and NATIVE_KERNEL, and HammeriteNavigationLinkBuilder._native_jumps and _NATIVE_NAV_LINKS, are gone with the GDScript they chose between, and HammeriteNativeNavGround and HammeriteNativeNavLinks are nav's own C++, no longer registered; what the suite held them to is hammerite-nav/tests/fixtures/kernels.json, recorded from the GDScript. The arithmetic check HammeriteNativeNavLinks.probe() is HammeriteNavigationLinkBuilder._probe(), and moved from core's suite to nav's. HammeriteNavigationRuntime._attached → _get_attached(), the dictionary itself; HammeriteNavigationMeshWriter._BODY_SLACK → _body_slack(). Bound under the same name: HammeriteNavigationLinkBuilder._index_surfaces(), _window_links(), _drop_links(), _jump_links(), _authored_links() and _obstructions, HammeriteNavigationAgentProfiles._values_of(), HammeriteNavigationLinkEntity3D._label and _editor_active. Gone: the link builder's _linked_pairs, the GDScript fallbacks the kernels had replaced (_furthest_from, _edges_near, _weld_index, _split_edges, _closest_points, _crossing_is_open, _ground_between) and the mesh writer's _RoomGround.

  • hammerite-audio (#269): HammeriteAudioNodeGraph.native_routes and NATIVE_SOUND_ROUTES, and HammeriteAudioGraphBuilder.native_walks and NATIVE_PORTAL_WALKS, are gone with the GDScript searches they chose between, and HammeriteNativeSoundRoutes and HammeriteNativePortalWalks are audio's own C++, moved out of core and no longer registered; what the suite held them to is hammerite-audio/tests/fixtures/kernels.json, recorded from the GDScript. Bound under the same name: the manager's _active_sounds, _audio_graph, _listener_position, _ambience and most of its private methods (_hearing_path(), _voice_bus(), _voice_cutoff(), _apply_absorption_tilt(), _hash_position_pair(), _use_graph(), _get_or_load_stream()...); the emitter's and the receiver's _handle and _manager (read-only); the graph's node_map, zone_by_region and spatial_index. The graph's _rank, _id_at, _preset_by_id and _Search, and the builder's private steps, are their own C++.

  • hammerite-editor (#271): HammeriteScreenLines.native_lines and native_screen_lines() are gone, and core's HammeriteNativeScreenLines with them: the editor's HammeriteScreenLines lays its lines itself, and what the suite held the kernel to is hammerite-editor/tests/fixtures/kernels.json, recorded from the script. The editor's private members are its own C++, but for those its suites and the audio and nav editor tools reach, which are bound under the same names. A property the script wrote plainly, beside a set_<name>() that does more (a brush tool's remembered sides, which set_sides() makes an undo step of; the map editor's active_tool, which set_active_tool() switches), has _set_<name>_value() as its setter.

Experimental classes

The port kept the experimental classes' API as it kept the supported ones', with the same shapes changed. What else changed in them between 1.0 and 2.0, beside new members and arguments added with defaults:

  • HammeriteBrushTool and the brush tools (the cube, cylinder, cone, ramp, stair, arch and polygon tools): their on_* steps (on_update_bounding_box(), on_finish()...) are the classes' own and not virtuals, so a script cannot override them, and one that does does not compile. A tool of a game's own extends HammeriteEditorTool. on_bounding_box_resized() is gone: the tool calls on_bounding_box_changed() and shows the size itself. The cylinder tool's Inverse is gone (inverse, inverse_brushes, set_inverse(), update_inverse_brushes()): an air cylinder is the round hole it made.
  • HammeriteClipTool: finish_pick_points_state() is gone, and add_preview_brush(brush) takes no source brush id.
  • HammeriteSubtractiveCompiler.surface_edge_key() is gone: a surface's edges are keyed by their face and their index in its vertices (HammeriteMap.get_worldrep_surface_edges()).
  • HammeriteLightmapPacker.add_face() answers nothing, and get_texture_size() a Vector2.
  • HammeriteMapEditorCore.KEY_ROTATION_AXES is HammeriteEntityTypeRegistry.key_rotation_axes(), and HammeriteDecalTool.DECAL_CLASSNAME HammeriteDecalProjection.entity_classname().
  • Constants that held a loaded resource are gone, each class loading its own: HammeriteEntity2D.FONT, HammeriteMap2DGizmos.font, HammeriteGizmoRenderer3D.OUTLINE_MATERIAL, HammeriteScreenLines.SHADER, and HammeriteVertexGizmo.HANDLE_TEXTURE and HANDLE_TEXTURE_SELECTED.
  • HammeriteFaceEditorPopup.UV_LOCK_DEFAULT, UV_LOCK_ON and UV_LOCK_OFF are integer constants of the same values, no longer an unnamed enum.
  • HammeriteNative (above).

Behaviour

  • A moved class has no script. get_script() is null, and get_class() answers the class's own name rather than its native base's. is HammeriteMap works as before.
  • Every bake made before this reads STALE once, and wants baking again, if its map has a sky. A sky's part of the lighting fingerprint (HammeriteMap.lighting_fingerprint(), which get_bake_state() compares with the one a bake recorded) was every stored property of the skybox, by name and in its script's order, the script's own uid and path among them - so renaming colour to color dated every bake there was, though nothing lit had changed. It is now what the bake reads of the sky, worked out as the lightmapper works it out, in a fixed order under a tag that says which line it is (sky2|...): what a sky aperture emits (ambient_color() in linear colour times light_energy), whether that light claims a slot of its own and with which group (light_is_dynamic, light_group), and - only when a directional light the bake reads follows the sky - the sun's direction, colour and energy. A rename, a property added, the sky's name, its model and where it is seen from, a game's own sky properties and a sun nothing follows date no bake from now on; a change to what the bake reads of the sky still does, and changing what it reads changes the tag. The brushes' and entities' parts of the fingerprint, and the program fingerprint, are unchanged to the bit, so a map with no sky reads as it did.
  • HammeriteEntitySet is a virtual class: HammeriteEntitySet.new() succeeds where the @abstract script refused, and a set with no _register_types fails when it is added.
  • HammeriteEntitySets.any_name and the other providers can no longer be passed as a Callable value from GDScript: GDScript cannot make a Callable of a native static method.
  • The registries convert some bad values where the scripts raised a script error: String() of a non-string _editor_rotation_axes, bool() of a null or a string casts_shadow/bake_static, float() of a non-number clarity.
  • HammeriteBrush.volume_type and interior_exterior are 32-bit enums: a file holding a value beyond 32 bits reads back as another. Only absurd values are affected.
  • The data file keeps GDScript's typed-member refusals when reading a class that was a script: a value of the wrong type leaves the member alone rather than being converted, so old files read as they did.
  • HammeriteAABBGrid.query_after with after below -1 no longer answers -1.
  • HammeritePrefab and HammeritePrefabSet: a 1.x .tres or .res of a prefab or a set, its array typed by the prefab's script, is migrated to the native classes.
  • HammeriteBakeOccluder.from_mesh reads a surface of an ArrayMesh that has no index array straight through, as it always said it did; the script stopped on a script error there and answered null.
  • HammeriteTerrain.convert of a brush whose top has no corners is an error and answers no prisms, where the script stopped on a script error. HammeriteTerrain.of() answers a plain null for a brush that is not terrain, which compares as the script's did.
  • A Resource of a moved class opened in the Godot editor runs its constructor there now: a new HammeriteMap made in the inspector has its default layer.
  • Property docs: an inspector reading a class's ## comments out of its script (HammeritePropertyDocs) has no script to read for a moved class; its words are in the class reference, Godot's own help.
  • Speed: nothing answers differently, but sooner. The compile benchmark's 256 rooms compile in 303 ms rather than 626, and a one-brush edit in 25 rather than 42; the persist benchmark's largest loads from a .res in 113 ms rather than 220.
  • A null an entity's signal hands on - HammeriteTrigger.fired(by) with nobody, HammeriteMap3D.map_changed with no map - is a plain null, as the scripts' was.
  • map_3d.gd and eye_adaptation_3d.gd are gone: a 1.x scene that names them by path is migrated to name the class (type="HammeriteMap3D"), as a scene saved since does.
  • hammerite-nav's classes are native (#270): the ground, the links, the profiles and their table, the report, the brush properties, the floor's edges, the cache, the mesh writer, the runtime and the link entity, in the library's nav module, and the overlay the Navigation tool draws in its nav_editor module, which only the full package has. The HammeriteNavigation autoload, HammeriteNavigationTool and HammeriteNavigationEditorExtension followed with the editor (The editor). A table of profiles or a .nav.res 1.x saved is migrated to the native class. A navigation cache 1.x saved is taken up as it was: every fingerprint is worked out to the byte as the scripts worked it out, and a .nav.hcache the native classes save is the scripts' file byte for byte.
  • A .nav.res cache comes back without its report's warnings, as in 1.x: a resource keeps no array typed inside a dictionary, and the report takes its warnings only as an Array[String], as the script's member did when set by name. Assigning an untyped array to HammeriteNavigationBakeReport.warnings leaves it as it was, where the script raised an error.
  • Navigation is built in half the time or less (HammeriteNavigationRuntime.prepare()): a Thief mission's 5.7 s is 2.8 s, a smaller one's 1.5 s is 0.56 s. Reading a saved cache costs what it did, most of it the fingerprint and the data file.
  • hammerite-audio's classes are native (#269), in the library's audio module, HammeriteEnvSound and HammeriteEnvSoundEnvironment as native subclasses of HammeritePointEntityNode3D. The HammeriteAudio autoload, HammeriteAudioPortalTool and HammeriteAudioEditorExtension followed with the editor (The editor). A graph baked into a 1.x map, or a preset, material, mix or sound table saved by 1.x, is migrated to the native class. HammeriteAudioGraphBuilder.fingerprint() is the script's to the byte, so a graph 1.x baked reads current where it did.
  • A native audio class has no script to read its property docs from (HammeritePropertyDocs): its words are in the class reference.
  • An audio method given a null map refuses it with an error and returns what the script's call would have, where 1.x raised the error from the line that dereferenced it.

Hammerite 2.0 · built from 6d57ab2d, 2026-10-17