M0' scaffold: Godot 4.7 + GDExtension + libgr2 wired up (macOS)

- extension/: godot-cpp submodule (master, API 4.7), Metin2Model node
  registered, links xrender-poc/libgr2 via sibling-dir reference.
- project/: Godot 4.7 project — orbit camera + DirectionalLight3D (shadows)
  + WorldEnvironment/procedural sky + placeholder cube; main.gd probes the
  extension and can run libgr2 on a real .gr2 (MTGODOT_PROBE_GR2).
- build.sh one-shot build into project/bin/.
- docs/GODOT-POC-PLAN.md (phased: Phase 1 macOS -> M0'/M1/M2/M2.5).

Verified on this machine:
  [mtgodot] Metin2Model registered OK — libgr2 linked
  probe_gr2(warrior_cheongrin.gr2) -> gr2 OK: format_version=6 sections=8
  Metal Forward+ renders the scene (test/golden/m0-scaffold.png).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
This commit is contained in:
Claude
2026-08-29 09:09:55 +09:00
commit c52b8657c4
21 changed files with 839 additions and 0 deletions
View File
+10
View File
@@ -0,0 +1,10 @@
[configuration]
entry_symbol = "mtgodot_library_init"
compatibility_minimum = "4.7"
reloadable = true
[libraries]
macos.debug = "res://bin/libmtgodot.macos.template_debug.dylib"
macos.release = "res://bin/libmtgodot.macos.template_release.dylib"
+1
View File
@@ -0,0 +1 @@
uid://hc7fxxrjvpr8
+61
View File
@@ -0,0 +1,61 @@
[preset.0]
name="macOS"
platform="macOS"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../build/export/mtgodot-poc.app"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
export/distribution_type=1
binary_format/architecture="arm64"
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
application/icon=""
application/icon_interpolation=4
application/bundle_identifier="org.internal.mtgodotpoc"
application/signature=""
application/app_category="Games"
application/short_version="1.0"
application/version="1.0"
application/copyright=""
display/high_res=true
xcode/platform_build="14C18"
xcode/sdk_version="13.1"
xcode/sdk_path="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
xcode/sdk_name="macosx13.1"
codesign/codesign=0
codesign/identity=""
codesign/certificate_file=""
codesign/certificate_password=""
codesign/entitlements/allow_jit_code_execution=false
codesign/entitlements/allow_unsigned_executable_memory=false
codesign/entitlements/allow_dyld_environment_variables=false
codesign/entitlements/disable_library_validation=true
notarization/notarization=0
privacy/microphone_usage_description=""
privacy/camera_usage_description=""
privacy/location_usage_description=""
privacy/address_book_usage_description=""
privacy/calendar_usage_description=""
privacy/photos_library_usage_description=""
privacy/desktop_folder_usage_description=""
privacy/documents_folder_usage_description=""
privacy/downloads_folder_usage_description=""
privacy/network_volumes_usage_description=""
privacy/removable_volumes_usage_description=""
ssh_remote_deploy/enabled=false
+125
View File
@@ -0,0 +1,125 @@
extends Node3D
## mtgodot-poc — M0' harness.
##
## Builds the minimal scene the plan asks for (orbit camera + DirectionalLight3D
## with shadows + WorldEnvironment/sky + placeholder cube + an empty Metin2Model),
## then reports whether the GDExtension loaded.
##
## M1 will replace the cube with Metin2Model.load_gr2(...) output.
const ORBIT_SPEED := 0.01
const ZOOM_STEP := 0.3
const PITCH_LIMIT := 1.45
var _cam: Camera3D
var _yaw := 0.6
var _pitch := 0.45
var _dist := 4.0
var _target := Vector3(0.0, 1.0, 0.0)
var _dragging := false
func _ready() -> void:
_build_environment()
_build_sun()
_build_camera()
_build_placeholder()
_probe_extension()
print("[mtgodot] M0' harness ready — drag: orbit, wheel: zoom, F2: screenshot")
# Batch capture: MTGODOT_AUTOSHOT=/abs/path.png godot --path project
var autoshot := OS.get_environment("MTGODOT_AUTOSHOT")
if autoshot != "":
await get_tree().create_timer(0.8).timeout
var img := get_viewport().get_texture().get_image()
img.save_png(autoshot)
print("[mtgodot] autoshot -> ", autoshot)
get_tree().quit()
func _build_environment() -> void:
var we := WorldEnvironment.new()
var env := Environment.new()
var sky := Sky.new()
sky.sky_material = ProceduralSkyMaterial.new()
env.background_mode = Environment.BG_SKY
env.sky = sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
we.environment = env
add_child(we)
func _build_sun() -> void:
var sun := DirectionalLight3D.new()
sun.rotation_degrees = Vector3(-45.0, -35.0, 0.0)
sun.shadow_enabled = true
add_child(sun)
func _build_camera() -> void:
_cam = Camera3D.new()
_cam.current = true
_cam.fov = 55.0
add_child(_cam)
_update_camera()
func _build_placeholder() -> void:
var cube := MeshInstance3D.new()
cube.name = "Placeholder"
cube.mesh = BoxMesh.new()
cube.position = _target
add_child(cube)
func _probe_extension() -> void:
if not ClassDB.class_exists("Metin2Model"):
push_error("[mtgodot] Metin2Model NOT registered — GDExtension failed to load")
return
var m := ClassDB.instantiate("Metin2Model") as Node3D
m.name = "Metin2Model"
add_child(m)
print("[mtgodot] Metin2Model registered OK — ", m.call("libgr2_info"))
# Optional: point MTGODOT_PROBE_GR2 at a real .gr2 to exercise libgr2 end to end.
var probe := OS.get_environment("MTGODOT_PROBE_GR2")
if probe != "":
print("[mtgodot] probe_gr2(", probe, ") -> ", m.call("probe_gr2", probe))
func _update_camera() -> void:
var offset := Vector3(
cos(_pitch) * sin(_yaw),
sin(_pitch),
cos(_pitch) * cos(_yaw)) * _dist
_cam.position = _target + offset
_cam.look_at(_target, Vector3.UP)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
match mb.button_index:
MOUSE_BUTTON_LEFT:
_dragging = mb.pressed
MOUSE_BUTTON_WHEEL_UP:
_dist = maxf(0.5, _dist - ZOOM_STEP)
_update_camera()
MOUSE_BUTTON_WHEEL_DOWN:
_dist = minf(50.0, _dist + ZOOM_STEP)
_update_camera()
elif event is InputEventMouseMotion and _dragging:
var mm := event as InputEventMouseMotion
_yaw -= mm.relative.x * ORBIT_SPEED
_pitch = clampf(_pitch + mm.relative.y * ORBIT_SPEED, -PITCH_LIMIT, PITCH_LIMIT)
_update_camera()
elif event is InputEventKey:
var k := event as InputEventKey
if k.pressed and k.keycode == KEY_F2:
_screenshot()
func _screenshot() -> void:
var img := get_viewport().get_texture().get_image()
var path := "user://shot_%d.png" % Time.get_ticks_msec()
img.save_png(path)
print("[mtgodot] screenshot -> ", ProjectSettings.globalize_path(path))
+1
View File
@@ -0,0 +1 @@
uid://clyxqop8cvbbe
+6
View File
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://main.gd" id="1_main"]
[node name="Main" type="Node3D"]
script = ExtResource("1_main")
+19
View File
@@ -0,0 +1,19 @@
; Godot 4.7 project — mtgodot-poc Phase 1 (macOS)
; See ../docs/GODOT-POC-PLAN.md
config_version=5
[application]
config/name="mtgodot-poc"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.7", "Forward Plus")
[debug]
gdscript/warnings/untyped_declaration=1
[rendering]
renderer/rendering_method="forward_plus"
anti_aliasing/quality/msaa_3d=2