CMake as the single source of truth: all six platforms verified, legacy projects retired#79
Merged
Conversation
Replace the glob-based pre-"target" CMake (basics.cmake/torque2d.cmake + libraries/*.cmake) with modern, target-based CMake that lists engine sources EXPLICITLY, so CMake — not the filesystem or a hand-maintained .sln — becomes the source of truth. - cmake/EngineSources.cmake: explicit cross-platform engine source list - cmake/PlatformSources.cmake: Win32 sources (other platforms stubbed) - engine/lib/CMakeLists.txt: libogg/libvorbis/lpng/ljpeg/zlib as static targets (only the real library sources, excluding the standalone tools the old recursive glob wrongly compiled) - GoogleTest built via add_subdirectory and linked for the in-engine tests Reconciles drift vs the VS2022 project: adds 2d/editorToy and math/noise; includes 2d/gui/guiImageButtonCtrl.cc (on disk but never added to the .sln); drops dead sfx/spine; excludes mobile-only bitmapPvr.cc on Windows. Load-bearing Windows settings (documented inline + in CLAUDE.md): static /MT runtime for all configs (avoids _DEBUG -> tinyXML "#define DEBUG" -> Box2D C1017), /Zc:wchar_t- (wchar_t == engine UTF16), C++17, _HAS_STD_BYTE=0; drops the old spurious unconditional DEBUG=1. Verified: configures + builds Debug and Release with the VS2022 generator and the resulting exe launches (Project Manager UI, clean OpenGL init). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provide double-click "generate the project file" scripts for users who do not
want to learn CMake (they install CMake once; pre-built binaries still cover the
least-advanced users):
- generate-vs2022.bat (tested, builds + runs)
- generate-vs2026.bat (CMake 4.x supports the VS18 2026 generator; needs
VS2026 installed to generate)
- generate-xcode.command / generate-make.sh (scaffolded, verify on platform)
Scaffold the remaining desktop/mobile Apple + Linux platforms in the modern
CMake (ported from the old recipe / the maintained Xcode + Xcode_iOS projects;
the old CMake never actually supported iOS):
- cmake/PlatformSources.cmake: explicit _MACOS, _LINUX, _IOS source lists
- CMakeLists.txt: APPLE (macOS frameworks), UNIX (X11/Xft/OpenGL/FreeType via
find_package, openal, pthread), and a distinct iOS branch (TORQUE_IOS, since
APPLE is also true on iOS) with the UIKit/OpenGLES framework set
- macOS/Linux/iOS are SCAFFOLDED, not yet verified on-platform
Docs and hygiene:
- README: step-by-step VS2022 generation guide for non-CMake users
- cmake/BUILD-PLATFORM-NOTES.md: status board + per-platform checklists and
known gotchas (SDL2 omitted, iOS needs bitmapPvr.cc + GameKit, WSL GUI, etc.)
- .gitattributes: fix a stray pasted line; pin LF for *.sh/*.command and CRLF
for *.bat so the scripts work on every platform
Windows still configures and builds clean (383 TUs); the macOS/Linux/iOS blocks
are inert on Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… files
Rewrite PR-builds.yml so each job generates a project file from the CMake
source of truth and builds it:
- Windows: VS2026 (windows-2025-vs2026) and VS2022, each x64 + Win32, via the
CMake VS generators (replaces the retired VS2019 jobs and direct msbuild).
- Linux: x86_64 and 32-bit (-m32 multilib) via the Unix Makefiles generator.
- macOS: the Xcode generator.
- iOS: the Xcode generator with -DCMAKE_SYSTEM_NAME=iOS, built without signing.
Each job pulls a recent CMake (lukka/get-cmake — the VS2026 generator needs
4.x), configures, builds Debug+Release, and uploads the package artifact.
Also: checkout v2->v4, add a concurrency cancel, and drop the now-unneeded
setup-msbuild / windows-sdk-install steps.
Windows is verified; macOS/Linux/iOS are freshly migrated and these jobs are
the verification loop for them (expected to need iteration). Running the unit
tests headlessly is a planned follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Android build was stale/broken: its hand-maintained ndk-build Android.mk
referenced dozens of deleted engine files. Rebuild it on the CMake source of
truth and modernize the Android Studio project.
CMake:
- CMakeLists.txt: Android branch builds the shared library libtorque2d.so
(OUTPUT_NAME torque2d), with the platformAndroid sources, GLES/EGL/OpenSLES/
log/android/z links, and IMPORTED prebuilt freetype(.a)+openal(.so) for
arm64-v8a. Desktop-only bits (gtest, testing/* sources, UNICODE defines,
the Windows .rc) are gated off; every `UNIX AND NOT APPLE` block now also
excludes Android (Android is UNIX).
- cmake/PlatformSources.cmake: explicit TORQUE_PLATFORM_SOURCES_ANDROID.
- engine/lib/CMakeLists.txt: don't build the bundled zlib on Android (lpng
links the NDK system libz instead).
Gradle (engine/compilers/android-studio): AGP 3.5.0 -> 8.6, Gradle 5.4.1 -> 8.7,
jcenter -> google()/mavenCentral(), compileSdk 28 -> 34, add namespace,
externalNativeBuild ndkBuild -> cmake (root CMakeLists), abiFilters arm64-v8a,
ANDROID_STL c++_static + C++17, package libopenal.so via jniLibs, manifest
exported=true. Deleted the stale Android.mk/Application.mk and the committed
.cxx ndk-build cache (now gitignored).
CI: new headless Android job (gradlew assembleDebug on ubuntu with the NDK).
Target is arm64-v8a only (only ABI with prebuilt freetype/openal). macOS/Linux/
iOS/Android remain scaffolded-not-verified; Windows still configures clean (383
TUs). The Android CI job is the verification loop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nManagement{})
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The global *.a / *.so gitignore rules were hiding the vendored prebuilt Android libraries, so CI had no libfreetype.a / libopenal.so to link libtorque2d.so against. Add .gitignore exceptions and commit the arm64-v8a binaries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Linux scaffold was never built; it failed to link on three wrong
assumptions. Fix them in the UNIX (non-Apple) block of the root CMakeLists:
* SDL 1.2 is REQUIRED, not optional. The platformX86UNIX back-end calls
1.2-only APIs (SDL_GetVideoSurface, SDL_WM_*, SDL_*GammaRamp,
SDL_GL_SwapBuffers) and pulls X11_KeyToUnicode out of libSDL. This is
NOT SDL2. Resolve it via find_library/find_path (the latter so the
`#include <SDL/SDL.h>` style headers resolve).
* detectX86CPUInfo comes from platform/platformCPUInfo.asm, which is
32-bit-only NASM (does not assemble for elf64) and is referenced only
when TORQUE_64 is undefined. So define TORQUE_64 on 64-bit (asm unneeded)
and assemble the asm via NASM (elf32) on 32-bit.
* Bitness macros: 64-bit gets TORQUE_64 (__amd64__ is auto); 32-bit gets
`i386` (bare i386 isn't predefined under standard C++ but types.gcc.h's
CPU detection keys off it). Path chosen via CMAKE_SIZEOF_VOID_P.
Verified building, linking, and running both 32- and 64-bit (Ubuntu 22.04 /
WSLg): both launch, init SDL 1.2 + OpenGL, open a window, and run the main
loop.
CI (PR-builds.yml): add libsdl1.2-dev (+ :i386) and nasm; pin both Linux jobs
to ubuntu-22.04. On 24.04, libsdl1.2-dev is the SDL2-based sdl12-compat shim,
which lacks X11_KeyToUnicode and would fail to link; 22.04 still ships genuine
SDL 1.2.15.
Docs: update BUILD-PLATFORM-NOTES.md status + Linux round, and generate-make.sh
dependency/status header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Android NDK / Linux toolchains define __aarch64__, but types.gcc.h only recognized Apple's __arm64__, so arm64 builds failed CPU/endian detection (Unsupported Target CPU, Endian define not set) and TORQUE_CPU_X64 went undefined (hashTable.h pointer->U32 cast error). Match both spellings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er') The engine uses the C++17-removed 'register' keyword in ~35 files. GCC tolerates it but Clang (Android NDK, macOS/iOS) treats -Wregister as an error. Add -Wno-register for non-MSVC compilers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TORQUE_CPU_X64 means 'any 64-bit CPU' and is now set for arm64 too, so the x86 SSE inline asm in mMathSSE.cc was wrongly compiled on arm64 (invalid 'd' input constraint). Guard on the x86-specific TORQUE_CPU_X86_64 instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T2DActivity.h includes <android_native_app_glue.h>, which is vendored in engine/source/platformAndroid; add that dir to the Android include path (matches the old Android.mk LOCAL_C_INCLUDES). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unner) The Linux jobs ran `cmake --build --parallel` with no job count. For the Unix Makefiles generator that passes `make -j` with NO limit, which starts every ready translation unit at once — measured at 100+ concurrent g++ processes. On a hosted runner that exhausts RAM and the OOM killer reaps the runner agent, surfacing as "The runner has received a shutdown signal" with no compile error. It reproducibly killed the memory-heavier 64-bit Debug build (-g); the 32-bit Release job happened to stay just under the limit. Pin all three Linux build invocations to `--parallel "$(nproc)"`. Windows (VS), macOS/iOS (Xcode) generators cap at the core count by default, so only the Make-generator Linux jobs were affected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the CMake build produce Apple Silicon binaries for both macOS and
iOS, with both the Unix Makefiles and Xcode generators.
macOS (arm64):
- zlib: define HAVE_UNISTD_H on Unix so zconf.h pulls in <unistd.h>;
modern clang errors on the otherwise-implicit read/write/close/lseek
in gz*.c (the old build's gnu89 C dialect masked this).
- Force-include a Cocoa prefix header (tools/CMake/macOS-Prefix.h) for
the platformOSX .mm back-end, which uses AppKit/Foundation types at
file scope (NSApplicationMain, NSEvent, NSCursor, ...) and relied on
the legacy Xcode prefix header. __OBJC__-guarded, so C/C++ TUs are
unaffected.
- Fix one stray include in osxCocoaUtilities.mm ("fileDialog.h" ->
canonical "platform/nativeDialogs/fileDialog.h").
- Pin CMAKE_OSX_ARCHITECTURES=arm64 and CMAKE_OSX_DEPLOYMENT_TARGET=11.0
(Big Sur floor for arm64; still supports a future Metal renderer).
Both overridable on the command line.
iOS (arm64 simulator):
- Predefine TORQUE_OS_IOS (the engine's OS detection gates its iOS
branch on it but only defines it inside that branch — a chicken-and-egg
that otherwise selects the desktop-GL back-end and fails).
- Force-include a UIKit/Foundation prefix header (tools/CMake/iOS-Prefix.h).
- Define NO_REDEFINE_GL_FUNCS: the debug outline-GL macro
(#define glDrawArrays glDrawArraysProcPtr) leaks into the modern SDK's
gl.h (dragged in via UIKit->CoreImage) and collides the engine's
same-named variable with the SDK's function decl. This is the engine's
own escape hatch; outline/wireframe debug draw becomes a no-op on iOS.
- Add graphics/bitmapPvr.cc (PVR textures, excluded from desktop builds)
and link GameKit for platformiOS/GameCenter.mm. No code-signing for the
simulator build.
Both targets build and link with 0 errors. The desktop binary launches
without crashing; full GUI/GL runtime should be confirmed from an
interactive desktop login (it blocks on a window-server session when run
headless). Docs/status board updated in cmake/BUILD-PLATFORM-NOTES.md,
including a comparison against the legacy Xcode/Xcode_iOS projects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zutil.h has an ancient Classic Mac OS branch that does `#define fdopen(fd,mode) NULL` under `MACOS || TARGET_OS_MAC`. But TARGET_OS_MAC is 1 on ALL modern Apple platforms (macOS and iOS), which DO have a real fdopen() — so the macro clobbers the SDK's <stdio.h> fdopen declaration (_stdio.h:318) and fails to compile. This is what broke the macOS and iOS CI jobs (Xcode 16.4, SDK 15.5 / iPhoneOS 18.5): the prior commit's HAVE_UNISTD_H pulls <unistd.h> in early via zconf.h, which defines TARGET_OS_MAC before zutil.h's branch, tripping the stub on the newer SDKs (it was latent on the older local SDK 15.2). Guard the stub with !defined(__APPLE__) so only true Classic Mac OS gets it; modern Apple uses the real fdopen. Verified by reproducing the exact CI commands locally: macOS Debug+Release (Xcode generator) and iOS device Release (CODE_SIGNING_ALLOWED=NO) both build clean (arm64). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same Classic-Mac-OS landmine as the zlib fdopen fix: pngpriv.h includes <fp.h> (Classic Mac OS's floating-point header, which doesn't exist on modern macOS/iOS) whenever TARGET_OS_MAC is defined — and TARGET_OS_MAC is 1 on all modern Apple platforms. This failed the macOS/iOS CI jobs (SDK 15.5 / iPhoneOS 18.5) right after the zlib fix unblocked the build and it reached libpng. Exclude modern Apple (!defined(__APPLE__)) so it uses <math.h>. Verified locally by forcing -DTARGET_OS_MAC=1: the original pngpriv.h fails with "'fp.h' file not found" (the exact CI error) and the patched one compiles clean. The other Apple-compiled vendored libs are clear (ljpeg already handles __APPLE__; libogg/libvorbis branch only on _WIN32). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… returns child dirs
The X11 back-end's Platform::dumpDirectories diverged from the verified Win32
implementation in two ways, both of which made getDirectoryList(path) return
just the path itself instead of its immediate subdirectory names:
1. recurseDumpDirectories was started at currentDepth=0 instead of -1. The
child-recursion guard is `currentDepth < recurseDepth`, so the common
depth==0 call (getDirectoryList) gave `0 < 0` == false and descended into
no children.
2. In noBasePath mode it pushed the base path itself for the empty-subPath
root call, rather than only non-empty subpaths.
Symptom: the in-engine Project Selector calls getDirectoryList(getMainDotCsDir())
to enumerate top-level project folders, so on Linux it found none -- the Toy Box
(and every other project) was missing from the startup list. Affected all Linux
getDirectoryList callers, not just the selector.
Fix matches the Win32 back-end: start the recursion at -1, and in noBasePath
mode store only non-empty subpaths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resizing the window did not update the engine: the SDL_VIDEORESIZE handler only
called Game->refreshWindow() (a repaint at the old size), so the canvas stayed
locked at its original dimensions. Three parts:
* Add SDL_RESIZABLE to the windowed GL flags. Without it SDL 1.2 fixes the
window size and never emits SDL_VIDEORESIZE.
* Handle SDL_VIDEORESIZE by re-establishing the GL surface at the new size.
Unlike the Win32 back-end (where the GL drawable follows the window and
WM_SIZE just calls Platform::setWindowSize), SDL 1.2's drawable does NOT
track the X window, so the extra area rendered as black / drifted off-top.
Re-setting the video mode recreates the surface and updates
Platform::getWindowSize(), which drives the canvas extent and GL viewport.
* Allow arbitrary windowed sizes in OpenGLDevice::setScreenMode: the
resolution-list check now only constrains fullscreen modes (a window-manager
drag is any size).
Re-creating the GL surface forces a texture-manager reload, so it is debounced:
the latest requested size is remembered and the surface is rebuilt only once the
drag settles (~150ms idle), giving one reload per resize gesture instead of one
per frame.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tform notes build-linux.sh is the Linux counterpart to generate-vs2022.bat / generate-vs2026.bat. The Windows scripts open an IDE that builds for you; with no IDE in the loop on Linux this configures AND compiles (bounding --parallel to nproc, matching the CI OOM fix) and leaves a runnable exe at the repo root. generate-make.sh stays the configure-only option. BUILD-PLATFORM-NOTES.md: mark the 64-bit Linux GUI runtime verified under WSLg, document the script, and record the SDL dev-package gotcha (a 32-bit-prepped box has only libsdl1.2-dev:i386, which still provides sdl-config, so the default 64-bit configure fails find_library(SDL12_LIBRARY) until libsdl1.2-dev:amd64 is installed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engine writes console.log to the working directory (repo root) on launch via setLogMode. It's a per-run artifact, not source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the macOS generator script up to the robustness of the Windows (generate-vs2022.bat) and Linux (generate-make.sh) helpers, and make "Run" from Xcode actually launch the game: - generate-xcode.command: fall back to /Applications/CMake.app when cmake isn't on PATH (the macOS .dmg installs there but doesn't add it to PATH), check for CMakeLists.txt, keep the window open with a clear message on failure (incl. the full-Xcode-vs-Command-Line-Tools xcode-select hint), and tell the user to pick the Torque2D scheme and Build/Run. - CMakeLists.txt: set XCODE_GENERATE_SCHEME + XCODE_SCHEME_WORKING_DIRECTORY to the repo root for the macOS desktop target, mirroring the existing VS_DEBUGGER_WORKING_DIRECTORY. The exe loads main.cs and the asset trees relative to the cwd, so without this, Run-from-Xcode starts in the wrong directory and the engine can't find main.cs. Verified: running the script with cmake off PATH finds CMake.app, generates build/xcode, the scheme's customWorkingDirectory is the repo root, and the project builds clean (arm64 Debug, 0 errors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bare (non-bundled) CMake executable showed "running" but never opened a window. main.mm called NSApplicationMain, which installs the app delegate + menu by loading the bundle's MainMenu nib (NSMainNibFile in Info.plist). The CMake build is a plain executable with no bundle/nib, so NSApp got no delegate — applicationDidFinishLaunching: never fired, runTorque2D was never called, and the process sat in an empty run loop with no window (and at 0% CPU, writing no console.log). Bootstrap AppKit by hand when there is no MainNib bundle: become a regular (foreground) GUI app via setActivationPolicy:Regular, install the AppDelegate, activate, and run. The engine already creates its own NSWindow in runTorque2D, so no nib is needed. The legacy .app path is preserved: if Info.plist names an NSMainNibFile, we still call NSApplicationMain. Verified: the bare exe now starts the engine within ~2s — process goes to RN/~20% CPU (was SN/0% idle), OpenGL initializes on the M2 Pro, and the 1024x768 window screen mode is set. Builds clean with both the Makefiles and Xcode generators (arm64, 0 errors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unch)
GuiListBoxCtrl::compByText/compByID are std::sort predicates but were not
a strict weak ordering:
- compByText used `res <= 0`, so equal strings compared TRUE;
- both reversed the sort by negating the result (`!res`), which makes
equal elements compare TRUE in the descending case.
Both violate the strict-weak-ordering requirement of std::sort. Older
libc++ ran it as undefined behaviour without complaint, but the hardened
std::sort in Xcode 16 / clang 17's libc++ detects it and calls abort() —
so the engine crashed during editor startup (ModuleManager::loadModule
Explicit -> GuiListBoxCtrl::sortByText) as soon as it actually got to run
on macOS.
Fix: compare strictly (`< 0`) and reverse by swapping operands instead of
negating, so equal elements always compare false in both directions.
Verified on macOS (arm64, both generators): the engine now runs past the
sort and loads the editor modules instead of aborting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Platform::getRealMilliseconds() did:
return (U32)([NSDate timeIntervalSinceReferenceDate] * 1000);
timeIntervalSinceReferenceDate is seconds since 2001, so *1000 is ~8e11 ms
in 2026 — far beyond U32_MAX. Converting an out-of-range double to an
unsigned integer is undefined behaviour, and the architectures disagree:
- x86_64 (the old build) truncates/wraps -> a changing value -> the clock
advanced, so it happened to work;
- arm64 (this build) saturates via fcvtzu to 0xFFFFFFFF on EVERY call, so
the time delta was always 0 and the simulation clock never advanced.
Consequence: TimeManager posted elapsedTime=0 every frame, Sim time never
moved, and scheduled events never fired. The engine rendered the editor
background but the Project Manager UI (shown via projectSelector.schedule(
2800,"show")) never appeared, and animations were frozen.
Fix: go through U64 first — a well-defined truncation that wraps mod 2^32
(~49 days, already handled by the engine's unsigned-delta math), matching
how the x86 build behaved. Verified with a standalone arm64 test: the old
cast yields a constant 0xFFFFFFFF (delta 0); the new one advances (delta ~6
over a 5ms sleep). In-engine, the editor now initializes fully (console
trace goes from ~37 lines, idle, to thousands with the loop busy).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running as a bare executable (not a .app bundle), macOS's LaunchServices/
AppleEvent check-in sometimes delivers the launch ("open application")
event more than once. A backtrace at window creation showed the chain:
main -> [NSApplication run] -> _handleAEOpenEvent
-> _sendFinishLaunchingNotification
-> -[AppDelegate applicationDidFinishLaunching:]
-> runTorque2D -> mainInitialize -> ... -> createCanvas
-> Platform::initWindow (a new NSWindow each time)
So applicationDidFinishLaunching: could fire multiple times, and each one
re-ran the entire engine init and spawned another window — intermittently
1 or 3 identical "Torque2D: Rocket Edition" windows depending on how many
launch events arrived (console.log only ever showed one boot because
setLogMode(2) truncates the log each time).
Guard the delegate so the engine boots exactly once per process. Verified
the normal single-boot path is unaffected (one GL init, editor loads).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The macOS target was emitted as com.apple.product-type.tool (a command-line
tool), because MACOSX_BUNDLE wasn't set. When Xcode runs a *tool* that turns
itself into a GUI app (NSApplication / setActivationPolicy:Regular), macOS
relaunches it to grant a GUI session — and relaunching a non-bundle
executable goes through LaunchServices -> Terminal, spawning extra copies of
the app. That was the real cause of the extra "Torque2D: Rocket Edition"
windows (1 from Xcode + 2 relaunched in Terminal = 3), confirmed by process-
tree tracing: the copies were children of Terminal.app/login/zsh and NONE of
the engine's own launch paths (system/NSTask/NSWorkspace) fired.
Set MACOSX_BUNDLE so the macOS desktop target builds a real Torque2D[_DEBUG]
.app with a stable LaunchServices identity (product type ...application) —
launched exactly once, no Terminal relaunch. This is what the legacy Xcode
project did.
Also ad-hoc code sign it ("Sign to Run Locally", CODE_SIGN_IDENTITY="-",
Manual style): a .app must be signed to run on Apple Silicon, but we don't
want to require an Apple developer team for a local build.
No asset repackaging needed: the .app lands at the repo root (the runtime
output dir) next to main.cs, and the engine's getExecutablePath()
(osxFileIO.mm) already searches the bundle's parent directory for main.cs.
NOTE: iOS also outputs Torque2D_DEBUG.app to the repo root but with a FLAT
(non-Contents) layout; building iOS then macOS (or vice versa) into the same
checkout leaves stray files that break codesign ("unsealed contents in the
bundle root"). Delete the stale .app when switching platforms in one tree.
Verified: product type is 'application', the bundle builds clean (arm64,
0 errors) and ad-hoc signs (Signature=adhoc) with both generators. Runtime
(window + boot) must be confirmed from an interactive Xcode session — GUI
apps can't be launched from this headless context (launchd error 153).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion)
FluidColorI::processValue (the per-tick interpolator behind fadeTo/the
editor curtains, splashes and transitions) did:
return start + (U8)mRound((target - start) * progress);
mRound returns F32, and (target - start) is negative whenever a channel
fades toward a SMALLER value (e.g. alpha 255 -> 0). Casting that negative
float directly to U8 is undefined behaviour: arm64's fcvtzu SATURATES it to
0 (the same instruction/issue as the getRealMilliseconds clock bug), so the
delta was always 0 and every fade-OUT froze at its start value. Fade-IN
(positive delta) worked, which is why it was easy to miss.
Concretely: the Project Manager's "torqueCurtain" (a full-screen editorBG
sprite shown on top, then faded to alpha 0 to reveal the UI) never faded,
so the editor booted to a window that only showed the background — the
actual Project Manager was fully rendered underneath the stuck curtain.
Fix: round to a signed int and cast only the final, in-range sum to U8, so
the negative intermediate is never converted to an unsigned type.
Verified on arm64: the old expression yields 255 (stuck) for a 255->0 step;
the fixed one yields 239 (progressing). The S32/F32 processValue overloads
were already safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update BUILD-PLATFORM-NOTES.md to reflect that macOS (arm64) now builds, code-signs, launches as a single window, boots, and the editor renders + animates — verified from Xcode. The notes still said "GUI runtime unconfirmed". Documents the six runtime bugs found between "it builds" and "the editor works" (AppKit bootstrap, std::sort comparator crash, frozen sim clock, duplicate windows from the tool product type, codesign/bundle, frozen fade-outs), and calls out the recurring arm64 trap (float->unsigned SATURATES on arm64 where x86 wrapped) so the pending iOS runtime work knows to look for it. Also flags the shared Torque2D_DEBUG.app output path between the iOS (flat) and macOS (Contents/) builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…note The 32-bit Make build now builds, links, and runs under WSLg (boots + GL init, toybox/project list and window resize confirmed). It falls back to llvmpipe (software GL) because WSLg's hardware-GL passthrough is 64-bit only. Also corrects an earlier inaccuracy: libsdl1.2-dev:amd64 and :i386 do NOT coexist -- they conflict on shared files, so installing one removes the other. Records the non-destructive 32-bit workaround (point SDL12_LIBRARY at the i386 runtime libSDL-1.2.so.0, since the i386 dev symlink is gone once the amd64 -dev is installed) and the full -m32 configure recipe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ce of truth)
With Windows, macOS, and Linux (32 & 64-bit) all CMake-runtime-verified, the
hand-maintained desktop project files are redundant. Delete them from
engine/compilers/:
- VisualStudio 2019 / VisualStudio 2022 (the .sln + .vcxproj tree)
- Xcode (the macOS desktop project)
- Make-32bit / Make-64bit (the Linux Makefiles)
CMake (root CMakeLists.txt + cmake/EngineSources.cmake +
cmake/PlatformSources.cmake) replaces all of them. Kept under engine/compilers/:
android-studio (the Gradle shell that drives CMake via the NDK), and Xcode_iOS +
emscripten as reference recipes until those platforms are CMake-runtime-verified.
cmake-modules is retained because emscripten/CMakeLists.txt includes CopyFiles
from it.
Docs:
- CLAUDE.md: rewrite the Building section to make CMake the single source of
truth; drop the "also add new sources to the per-platform project files"
dual-maintenance rule (there are no such files to sync anymore).
- cmake/BUILD-PLATFORM-NOTES.md: record the retirement; note the macOS
comparison refers to the now-deleted Xcode project (see git history).
- .gitignore: drop the stale Make-32bit/Make-64bit ignore rules.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…untime fixes) The CMake iOS target built and linked but produced a bundle-incomplete .app that launched into a black screen. Make it a real, runnable iOS app and fix the arm64 runtime landmines that surfaced once it actually ran (editor renders, touch works; user-verified in the iOS 18.2 simulator on an iPad Pro M4). Bundle (CMakeLists.txt + tools/CMake): - iOS-Info.plist.in via MACOSX_BUNDLE_INFO_PLIST — the app's window comes from a Main storyboard (T2DAppDelegate creates none in code); without UIMainStoryboardFile + a bundle id + orientations the app had no window. - Bundle the iPhone/iPad GLKit storyboards (copied from the legacy Xcode_iOS project into tools/CMake so the build is self-contained) + a static LaunchScreen (modern iOS needs one for a full-screen drawable). - POST_BUILD copy of main.cs + editor/library/toybox/tools into the flat .app (an installed iOS app is sandboxed and resolves content inside the bundle). Runtime fixes: - iOSTime.mm getRealMilliseconds(): arm64 float->unsigned saturation (the macOS osxTime.mm bug class, in iOS's own time file) -> frozen clock -> black screen. Compute in U64, cast last. - defaultGame.cc: iOS FrameAllocator was 512KB (2013-era) but main.cs boots the desktop editor; a boot alloc overran it and tripped the fatal assert. Bump iOS to the desktop 3MB. - Point-based rendering (Retina packs 2-3x pixels into the same space, so pixel-sized GUIs render half-size): iOSWindow.mm uses point bounds for $pref::iOS::Width/Height, T2DViewController.mm pins contentScaleFactor=1 before createFramebuffer and disables retina touch scaling -> logical res, GL backing, and touch all share one point space (correct GUI size + scene picking). - Touch release (press stuck): iOSInput.mm createMouseUpEvent now falls back to the first occupied slot (simulator mouse-up lands a pixel off the stored coord), sets the cursor, and frees the slot; guiCanvas.cc rootScreenTouchUp now routes the up to the captured control first like the desktop rootMouseUp (a button mouseLock()s on down, so it never released otherwise). Touch-only path; desktop unaffected. Tooling: - generate-xcode-ios.command (arm64 simulator) and generate-xcode-ios-device.command (code-signed device). CMakeLists detects simulator-vs-device from the SDK: simulator keeps CODE_SIGNING_ALLOWED=NO; device uses CODE_SIGN_STYLE=Automatic with the team and bundle id from overridable cache vars (TORQUE_IOS_TEAM, TORQUE_IOS_BUNDLE_ID). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
iOS builds, runs, and is runtime-verified from CMake on both the simulator and a real device, and the build is self-contained (the storyboards + Info.plist were copied out of engine/compilers/Xcode_iOS into tools/CMake in the iOS round). The legacy iOS Xcode project is now redundant, like the desktop projects retired earlier — delete engine/compilers/Xcode_iOS. What remains under engine/compilers/ is android-studio (the Gradle shell that drives CMake via the NDK) and emscripten (a reference recipe until the Web target is CMake-runtime-verified), plus cmake-modules (used by emscripten/CMakeLists.txt). Docs: - README.md: the "Building the Source" section still advertised the deleted per-platform projects (VS 2019/2022, OSX Xcode, Linux Make, Xcode_iOS) as "provided in engine/compilers" with CMake as a mere alternative. Rewrite it so CMake is the source of truth, listing the per-platform generator scripts and pointing to the wiki Building guide. - CLAUDE.md + cmake/BUILD-PLATFORM-NOTES.md: drop Xcode_iOS from the "kept under engine/compilers" notes; emscripten is now the only reference recipe. Mark the iOS-project comparison note as retired (see git history). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
First Android runtime bug, found by running the APK on a real device via Firebase
Test Lab and symbolicating the tombstone with ndk-stack.
Root cause: Android's process cwd is "/", so defaultGame.cc resolved the boot
script to "/main.cs", then chopped the filename at the LEADING slash — leaving an
empty string as the main.cs directory. That empty dir then flowed in as the cwd
for every Platform::makeFullPathName(), making
endptr = buffer + dStrlen("") - 1 = buffer - 1
(point before the buffer) and corrupting the path-building pointer math →
out-of-bounds write → SIGSEGV in catPath() during ModuleManager::registerModule.
Desktop never hit it because main.cs lives in a deep path with interior slashes.
Fixes:
- defaultGame.cc: when the boot script is at the filesystem root, keep "/" as the
main.cs directory instead of truncating to "". (The real fix.)
- platformFileIO.cc: harden makeFullPathName()/catPath() against a degenerate cwd —
compute the remaining length signed and clamp with getMax(...,0), and no-op
catPath when there's no room (len < 3). A latent cross-platform buffer-overflow
that a "" cwd exposed; can't overrun the buffer now regardless of inputs.
With this, Android registers all editor modules and boots into the editor. It then
crashes in font init (AndroidFont::getCharInfo null-derefs a failed FT_New_Face) —
the next bug, documented in cmake/BUILD-PLATFORM-NOTES.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
Wires Emscripten/WebAssembly into the SHARED EngineSources.cmake (the Android
pattern: one source list + guards, not the legacy hand-maintained flat list).
emcc now builds Torque2D_DEBUG.{html,js,wasm,data} via `emcmake`, and it boots
in a browser: WebGL 1.0 context comes up, all subsystems init, the editor
modules load, and the main loop runs (verified headless via Playwright +
`python -m http.server`).
Build wiring (CMakeLists.txt, PlatformSources.cmake, generate-emscripten.sh):
- TORQUE_PLATFORM_SOURCES_EMSCRIPTEN + an EMSCRIPTEN dispatch branch (matched
before UNIX, like Android, since the toolchain sets UNIX=1).
- EMSCRIPTEN=1 defined GLOBALLY (emcc only predefines __EMSCRIPTEN__, but the
engine's types.gcc.h AND vendored ljpeg/jconfig.h key off bare EMSCRIPTEN).
- Net swap: platformNet_Emscripten.cpp replaces the BSD-socket impl.
- emcc flags: -sUSE_SDL=1, -sLEGACY_GL_EMULATION=1, INITIAL_MEMORY+growth,
--js-library platform.js, --preload-file for the script/asset trees. tools/ is
excluded from the preload (build-time-only doxygen/TexturePacker), trimming the
.data bundle 272MB -> 186MB.
Back-end compile/link fixes (years of interface drift in platformEmscripten/*):
string return types (U32 not dsize_t), a stale platState.engine assert, an
include typo, the missing getVerticalSync override (class was abstract), an F64
include, a glArrayElement no-op (LEGACY_GL_EMULATION lacks it), and the net
stub's chicken-and-egg self-guard (#if TORQUE_OS_EMSCRIPTEN before the include
that defines it — same class as the iOS TORQUE_OS_IOS fix).
Runtime fixes:
- EmscriptenFileio recurseDumpDirectories dropped the first char of each scanned
entry ("/editor/ssetAdmin"), so editor modules never loaded — an off-by-one in
the path join (same family as the Android leading-slash bug; web cwd is "/").
- platform.js alerted raw wasm pointers instead of strings (missing UTF8ToString)
and used blocking alert() — the per-assert Platform alert wedged the tab in a
dialog storm. Decode the string; route informational AlertOK to console.
- gFont.cc GFont::create hard-crashed (wasm null deref) on a missing font; its
caller already null-checks, so it now returns a null Resource. Cross-platform
robustness fix, analogous to the open Android font bug.
Remaining blocker: FONTS. The web build has no font backend
(EmscriptenFont::createPlatformFont returns NULL) and the editor GUI hard-asserts
without one (guiTypes.cc:768), so the canvas is black. Same root cause as the
open Android font crash — to be fixed together next. Full writeup in
cmake/BUILD-PLATFORM-NOTES.md (Emscripten round + status board).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
The Emscripten build booted but showed a black screen: the editor GUI needs a
font and the web has no font backend. Wire fonts CACHE-FIRST (the engine's .uft
glyph caches are self-contained and already in the preload) so the Project
Manager renders real text ("TORQUE2D", version, project-tile labels) plus
sprites — stable, no crash. First end-to-end exercise of the GL draw path on web
(text quads + sprite batches under LEGACY_GL_EMULATION). No FreeType, no extra
download.
Most fixes are cross-platform robustness; the asset-path ones are web-specific:
- platformAssert.cc: on TORQUE_OS_EMSCRIPTEN, log-and-continue instead of a
blocking AlertRetry/AlertOKCancel (native confirm()) + forceShutdown(1). A
blocking modal inside the rAF loop wedges the tab, and a per-frame assert made
an un-dismissable dialog storm. This unblocked boot past the (harmless,
non-fatal) "Con::init should only be called once" double-init assert.
- defaultGame.cc: give Emscripten the 3MB frame allocator (like iOS/desktop) — it
boots the same desktop-class editor; only Android keeps the 512KB budget.
- guiTypes.cc GuiControlProfile::getFont(): replace the AssertFatal-on-null with a
graceful fallback to another loaded size (text-render sites deref the result).
- guiProfiles.cs (AppCore + EditorCore): web ($platformUnixType=="emscripten")
picked "monaco" (no .uft, no system font); use "share tech mono". The base
GuiDefaultProfile also hardcoded a non-existent ^EditorCore/gui/fonts dir
(desktop only survived via createPlatformFont); point it at an EXPANDED real dir
under the registered ^EditorCore expando that ships the face
(^EditorCore/Themes/LabCoat/fonts) — the resource manager doesn't resolve the
^Module expando for cache lookups and ^AppCore isn't loaded at editor boot.
- gFont.h GFont::getTextureHandle(): bounds-check mTextureSheets[index]. An OOB
index returned a garbage TextureHandle whose non-NULL object was dereferenced by
lock() -> fatal wasm "memory access out of bounds" in dglDrawText. Return a NULL
(lock-safe) handle so an unrenderable glyph is skipped.
- AndroidFont.cpp: create() now propagates FT_New_Face failure (was return true
even on failure) and getCharInfo() guards the face, fixing the open Android font
crash (createPlatformFont -> null -> handled by getFont above). Shared root cause.
- gFont.cc: log the looked-for path on a font-cache miss (diagnostic).
Build gotcha (documented in BUILD-PLATFORM-NOTES): the --preload-file asset trees
aren't tracked as CMake deps, so a .cs/asset edit needs a forced repackage
(rm build/emscripten/Torque2D_DEBUG.{html,js,wasm,data} then rebuild).
Residual (minor, non-fatal): a few un-baked editor title sizes (black ops one
21/28) log a Vector-out-of-bounds per frame — debug-only (AssertFatal compiles out
of Shipping); the editor renders and is responsive. FreeType-on-wasm (arbitrary
fonts + smaller download) and Android text rendering remain tracked follow-ups.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…render)
The web build previously rendered only the pre-baked .uft sizes; anything else
degraded to a near size or to nothing (Windows' GDI is a universal backstop that
synthesizes any face+size; the web had none). This compiles the vendored FreeType
(2.4.12) to wasm and implements EmscriptenFont on it, rasterizing a bundled Roboto
.ttf for any face/size not in the .uft cache. The editor now renders ALL its text
(the previously-blank "New Project" heading/body now draw), the per-frame Vector
OOB is gone, and web matches desktop font behavior. .uft is kept (designed faces
still use it at baked sizes; FreeType only fills gaps).
- engine/lib/CMakeLists.txt: a `freetype` STATIC target, gated if(EMSCRIPTEN)
(desktop uses system fonts/find_package, Android the prebuilt .a). Built from the
vendored freetype-2.4.12 aggregator .c files (base/sfnt/truetype/smooth/raster/
autofit/psnames/psaux/pshinter), FT2_BUILD_LIBRARY defined. Linked in the root
EMSCRIPTEN block (+~1 MB wasm).
- ftmodule.h trimmed to the TrueType path we compile — ftinit.c registers every
driver listed there, so the default full list link-errored on bdf/pcf/t42/winfnt/
type1/cff/cid/pfr. Safe to edit: that source is only consumed by this from-source
Emscripten build (Android links the prebuilt .a).
- EmscriptenFont.{h,cpp}: mirrors AndroidFont — FT_Init, FT_New_Face on the bundled
.ttf path, FT_Set_Pixel_Sizes, getCharInfo via FT_Load_Char(FT_LOAD_RENDER). Uses
the rendered bitmap dims (width/rows, stride=pitch) so alloc/copy/size stay
consistent (metrics width can be a pixel narrower -> overrun).
- Fallback resolution honors the app/editor font separation: EmscriptenFont reads
$pref::Web::fallbackFont, which each core registers to its OWN Roboto copy
(AppCore -> ^AppCore/fonts, EditorCore -> ^EditorCore/gui/fonts). A shipped game
with editor/ removed falls back to AppCore's; the editor never reaches into the app.
- Roboto-Regular.ttf (SIL OFL) bundled in both font dirs (preloaded into the web
.data) and android-studio assets/fonts/.
- Android wired but NOT tested this round: guiProfiles request "Roboto" (the long-
gone "Droid" isn't on modern devices) + the bundled asset; AndroidFont already
rasterizes. APK run deferred.
Verified on web (headless Chromium): font-create failures 0, OOB ~thousands -> 1,
no crash; screenshot shows the full Project Manager tile text rendered via FreeType.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…or finds the toybox
Platform::dumpDirectories started the recursion at currentDepth 0; the child-
recursion guard is `currentDepth < recurseDepth`, so the common depth==0 call
(getDirectoryList) gave `0 < 0` == false and descended into NO children, returning
an empty list. The editor's project selector enumerates getMainDotCsDir() with
getDirectoryList() to find project folders, so on web it found none and never listed
the toybox (the only default project) — only the "New Project" placeholder showed.
Start the recursion at -1, matching the Win32/x86UNIX back-ends (this exact fix was
applied to x86UNIX during the Linux round but never propagated to Emscripten). Now
getDirectoryList("/") enumerates the immediate child dirs and the Toy Box project
card appears. Verified in-browser.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…t of bounds _StringTable::hashString/hashStringn indexed the 256-entry sgHashTable with a plain `char`. `char` is signed (clang/emcc default), so any byte >= 0x80 made the index negative -> sgHashTable[negative] reads before the array. On desktop that silently read adjacent memory (a wrong-but-harmless hash); on wasm it's a hard "memory access out of bounds" trap. Reproduced on web: pressing the Ctrl key while a GuiTextEditCtrl/GuiConsoleEditCtrl has focus (the console box, or the editor's project-manager screen) delivered a high-valued ascii that got inserted as text, then hashed on insert into the StringTable -> instant crash: hashString <- StringTable::insert <- GuiControl::setText <- GuiTextEditCtrl:: setText <- handleCharacterInput <- GuiConsoleEditCtrl::onKeyDown <- ... This would equally crash on any high-bit input on web (e.g. typing an accented character). Cast the index to (U8) in both hashers. Verified: pressing Ctrl in the web build no longer freezes; the page stays responsive. Note (not fixed here, minor): the Emscripten input layer hands a modifier-only Ctrl press a non-zero `ascii`, so it's treated as a character at all -- worth filtering in EmscriptenInput later, but the hash fix makes it (and all high-bit input) safe regardless. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…(no more phantom glyphs) EmscriptenInputManager::MapKey assigned the raw SDL keysym as each key's ascii, so modifiers (SDLK_LCTRL=306, etc.), function keys, arrows, keypad and locks all carried a bogus, non-zero ascii. Pressing them was then treated as character input and inserted a phantom (unrenderable) glyph into focused text fields -- e.g. tapping Ctrl typed a stray box in the console edit. (The earlier StringTable hash fix stopped the hard crash this caused; this removes the bogus character itself.) The desktop x86UNIX back-end avoids this by deriving the ascii from X11_KeyToUnicode(), which returns 0 for non-character keys -- but emscripten's SDL1 port has no working X11_KeyToUnicode, which is why this code used the keysym + a manual shift switch instead. Filter the default assignment: only printable-ASCII keysyms (0x20-0x7E) carry a character ascii; SDL special keys (>= 0x100) and the 0x7F-0xFF range map to 0. Control keys (< 0x20: Tab/Enter/Backspace/Esc) also map to 0 and stay handled by keycode. Shifted variants are still set by the switch below. Verified in-browser (toybox -> console): pressing Ctrl/Alt/Shift inserts nothing, letters still type, Backspace still edits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…trancy OOB) ProcessMessages() polled SDL events into the shared gPlatState.eventList, cached its size, then indexed the shared list in the loop. Handling an SDL_USEREVENT (SETVIDEOMODE) calls SetAppState() -> Input::reactivate(), which re-enters ProcessMessages and clears+refills that same gPlatState.eventList -- so the outer loop then read past the now-smaller vector: Vector<SDL_Event>::operator[] out of bounds, twice per toybox load (the two red "vector.h @ 578" fatals seen in the console). Non-fatal since the prior assert-handling change, but a real OOB read. Iterate a LOCAL copy of the frame's events so re-entrancy can't corrupt the iteration. Root-caused with a temporary emscripten_log(EM_LOG_C_STACK) in the assert path; stack pointed straight at ProcessMessages -> Vector<SDL_Event>:: operator[]. Verified: loading the toybox now logs zero vector.h:578 fatals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
LightObject::sceneRender disabled GL_BLEND between glBegin/glEnd. On desktop GL that call is illegal inside a begin/end block (GL_INVALID_OPERATION) and is silently ignored, so the light fan still draws with additive blending and fades out correctly. Under Emscripten's LEGACY_GL_EMULATION the immediate-mode geometry is buffered and the real glDrawArrays is deferred to glEnd(), so the early glDisable(GL_BLEND) actually takes effect and the fan renders opaque -- fading to black instead of fading out. Move the disable after glEnd(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
… & blending rounds Marks the Emscripten/Web round DONE in the status board and documents the work after the font rounds: the interaction round (project selector depth-0 dir scan, signed-char StringTable hash OOB, phantom-glyph modifier keys, ProcessMessages event re-entrancy OOB) and the blending round (LightObject glDisable(GL_BLEND) inside glBegin/glEnd, which the web's deferred immediate-mode draw honored). Also updates the "legacy projects retired" note: the emscripten reference recipe is now superseded since the Web target builds via emcmake + PlatformSources.cmake. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
…ocator)
The Android editor now boots, renders and runs on a real Pixel 7 Pro
(verified via Firebase Test Lab) — the last platform in the CMake
source-of-truth migration to be runtime-verified.
Two root-cause fixes, each of which exposed the next crash in the boot
sequence:
- FontManager.java (TTFAnalyzer): only read Macintosh (platformID==1)
TrueType name records, but the bundled Roboto and modern Android system
fonts ship only Windows (platformID==3, UTF-16BE) records. The enumerated
font map came up empty, getFont("Roboto") returned null, AndroidFont
failed, GFont::create returned NULL, and GuiMenuBarCtrl dereferenced the
NULL font -> SIGSEGV. Accept platformID 1/3/0 and decode UTF-16BE for the
Windows/Unicode records.
- defaultGame.cc (FrameAllocator): Android ran on a 512KB frame allocator
while the desktop-class editor it boots needs 3MB (the same fix already
applied to iOS and Emscripten). Once fonts loaded, reading a cached .uft
glyph table overran it -> FrameAllocator::alloc SEGV. Collapsed the now
redundant per-platform split to give every platform the 3MB buffer.
Main UI (Roboto) text renders. Remaining cosmetic gap: un-baked decorative
faces (e.g. "black ops one" titles) with no .uft and no matching
installed/bundled .ttf still render blank. See
cmake/BUILD-PLATFORM-NOTES.md (Android round) for the full write-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
The per-face -> Roboto fallback for un-baked decorative faces (e.g. the "black ops one" editor titles) was attempted twice -- once in C++ (AndroidFont::create, an extra getFontPath JNI call) and once in Java (FontManager.getFont returning Roboto instead of null) -- and both were reverted. Each FTL run went silent right after FileWalker with zero Torque2D-tagged lines and no crash; this is ambiguous (likely lossy FTL logcat capture vs. a real hang) and unprovable from n=1 FTL logs. Since it's cosmetic and the verified-working state already ships, the fallback is deferred pending a re-attempt on a local arm64 device with reliable adb logcat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
With the Web/WASM target now CMake-runtime-verified via the root CMakeLists.txt + generate-emscripten.sh, the last hand-maintained build recipe under engine/compilers/ is obsolete. Remove engine/compilers/emscripten and engine/compilers/cmake-modules (the latter only provided CopyFiles for that recipe; nothing in the active build references either). engine/compilers/ now contains only android-studio, the Gradle shell that drives the root CMake via the NDK. Refresh the docs to match the finished migration: - README.md: Web is available (generate-emscripten.sh), not "not yet available". - CLAUDE.md: all six back-ends wired+verified; emscripten retired (was "stubbed" / "kept until the Web target is CMake-runtime-verified"). - CMakeLists.txt: drop the dangling reference to the now-deleted recipe path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6jsrTtG7fhRRRYG9NvHZW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Replaces Torque2D's hand-maintained per-platform build projects with a single CMake source-of-truth build. Adding an engine file now means editing one explicit list (
cmake/EngineSources.cmake/cmake/PlatformSources.cmake) and regenerating; every legacy.sln/ Xcode project / Makefile / Emscripten recipe has been retired.Platform status — all six build and are runtime-verified
.app.appPer-platform configure flags and the full runtime-fix write-ups live in
cmake/BUILD-PLATFORM-NOTES.md.How it's structured
CMakeLists.txt(target-based) selects the active platform back-end and applies its libs/frameworks/defs.cmake/EngineSources.cmake(cross-platform) +cmake/PlatformSources.cmake(per-OS back-ends) are the authoritative file lists — no globs.generate-vs2022.bat/generate-vs2026.bat,generate-xcode.command,generate-xcode-ios[-device].command,generate-make.sh/build-linux.sh,generate-emscripten.sh.Retired (CMake replaces them)
The VS solutions, the macOS + iOS Xcode projects, the Linux Makefiles, and the Emscripten reference recipe (
engine/compilers/{emscripten,cmake-modules}).engine/compilers/now contains onlyandroid-studio— the Gradle shell that drives the root CMake via the NDK.Load-bearing Windows settings
Static
/MTfor all configs (avoids_DEBUG→ tinyXML#define DEBUG→ Box2D breakage),/Zc:wchar_t-(sowchar_t== the engine'sUTF16), C++17,_HAS_STD_BYTE=0.Notable runtime fixes surfaced along the way
Going from "builds" to "runs" took real fixes, several cross-platform: arm64 float→unsigned saturation bugs (frozen clock/fades on macOS/iOS), a
std::sortstrict-weak-ordering abort, a signed-char string-hash OOB, font cache/CharInfo bounds guards, an Android module-registration path bug, the Android font-name parse (modern WindowsplatformIDrecords) + a 3 MB frame allocator, and the Emscripten immediate-mode GL blending shim. Details per platform in the notes.Known follow-ups (non-blocking, documented)
.uftfaces (e.g. the "black ops one" titles) render blank — a per-face→Roboto fallback is deferred pending a local-device re-test..ttfresolution to shrink the.uftdownload.Con::init should only be called onceboot double-init (logged).🤖 Generated with Claude Code