Native, AngelScript, and Managed C# Debugging
This is the Engine-owned route for diagnosing native failures, mixed native/script stack traces, fatal-process diagnostics, Visual Studio data inspection, live AngelScript execution, and Managed C# compile/load/callback failures. It follows the current build configurations, platform helpers, exception and stack-trace implementation, AngelScript endpoint, managed baker/runtime sources, bundled VS Code adapter source, Engine tests, and checked embedding-project evidence.
An embedding project owns concrete target names, executable paths, working directories, bake prerequisites, sub-configs, credentials, crash-storage policy, editor installation, and the scenario that reproduces its game bug.
Fast route decision
- Choose a native debugger for crashes, native exceptions, memory, threads, or a mixed stack whose owning frame is C++.
- Choose the AngelScript debugger for live script stepping and variables. The
current Engine contract requires
AngelScript.DebuggerEnabled, exposes a TCP endpoint on a process-selected port in43000..44999, and uses UDP port43001for discovery; the project owns editor wiring and remote-access policy. - For Managed C#, start with Roslyn/MSBuild diagnostics, the generated
.gen.sln, managed baker/runtime logs, and a focused managed test. The AngelScriptfosadapter does not debug C#; attach a compatible native/managed debugger only after matching the generated sources, assemblies, runtime payload, and symbols. - Choose a focused Engine or project test when the failure is deterministic and the changed contract can be observed without an interactive attach.
An attach is diagnostic evidence. Keep the original reproduction and add a repeatable regression route after the cause is fixed.
Contract status
This page describes the current reusable Engine contract. Engine source and Engine-owned tests are normative. Last Frontier and FOnline TLA are pinned workflow evidence only; their launch names, binary prefixes, ports beyond Engine defaults, test suites, and product policy do not extend Engine support.
Debugging has four separate evidence layers:
- a reproducible failure and complete original log;
- a matching binary, runtime libraries, and native symbols;
- live debugger or AngelScript attach evidence from the failing execution;
- a focused regression test or repeatable project scenario after the diagnosis.
A readable stack is not proof that the executable, symbols, and source came from the same build. A successful attach is not proof that the debugger control being displayed is implemented by the live Engine transport.
Scope and authority
The Engine owns:
- build-configuration semantics, compiler/linker symbol flags, sanitizer variants, and generated application targets;
is_run_in_debugger,break_into_debugger, native stack capture/resolution, exception callbacks, crash handlers, and the diagnostic self-test;- mixed AngelScript/native stack layers and the current runtime debugger endpoint;
- Managed C# baker/runtime diagnostics, generated project ownership, and the boundary between Engine logging and external managed-debugger tooling;
- MSVC Natvis/NatJMC files attached to generated solutions;
- the
BuildTools/angelscript-debuggeradapter source and its declared VS Code configuration schema; - focused native tests for stack-trace and exception behavior.
The embedding project owns:
- which application, configuration, resource set, database, account, and gameplay route to launch;
.vscode/launch.json, task dependencies, editor-extension installation, and multi-process naming;- native dump collection, retention, privacy, upload, symbol-store, and incident policy;
- gameplay/script regression suites and release-platform qualification.
Web and Android have additional runtime boundaries. Use Web Build, Packaging, and Browser Debugging or Android Build, Packaging, and Device Debugging after proving that the symptom is platform-specific.
Source paths inspected
The current contract was re-derived from:
BuildTools/cmake/stages/Init.cmake,EngineSources.cmake, andThirdParty.cmake;BuildTools/cmake/helpers/Build.cmakeandBuildTools/cmake/helpers/State.cmake;BuildTools/natvis/essentials.natvis,unordered_dense.natvis, andfonline.natjmc;- the GLM, ImGui, small-vector, and ufbx visualizers under
ThirdParty/; Source/Essentials/BasicCore.cpp,StackTrace.*,BaseLogging.*,FatalError.*,ExceptionHandling.*, andLogging.cpp;Source/Common/DiagnosticSelfTest.cppandSource/Frontend/ApplicationInit.cpp;Source/Scripting/AngelScript/AngelScriptBackend.cpp,AngelScriptContext.cpp,AngelScriptGlobals.cpp,AngelScriptHelpers.cpp, andAngelScriptDebugger.*;Source/Scripting/Managed/ManagedScriptBackend.*,ManagedRuntime.*,ManagedScripting.*,ManagedHost/ManagedLoadContextHost.cs,CoreScripts/ScriptSynchronizationContext.cs, andAnalyzers/SyncCoverAnalyzer.cs;Source/Tools/ManagedScriptBaker.*,Source/Applications/ManagedScriptBakerApp.cpp, andSource/Tests/Test_ManagedScriptBaker.cpp;Source/Common/Settings.inc;Source/Tests/Test_StackTrace.cpp,Test_ExceptionHandling.cpp, andTest_ScriptBuiltins.cpp;BuildTools/angelscript-debugger/package.jsonand its TypeScript sources;- exact project snapshots in
BuildTools/ExternalProjectEvidence.json.
Evidence layers and support matrix
| Surface | Current Engine capability | Evidence limit |
|---|---|---|
| Windows native | MSVC/clang-cl application targets, PDB emission outside MinSizeRel, debugger detection, DebugBreak, Engine SEH diagnostics, generated MSVC visualizers |
The Engine does not create or retain minidump files or operate a symbol server. |
| Linux native | Debug information outside MinSizeRel, -rdynamic, GDB/LLDB-compatible binaries, /proc/self/status debugger detection, signal/terminate diagnostics |
Core-dump enablement, collection, symbol storage, container permissions, and retention are host/project policy. |
| macOS native | Debug information outside MinSizeRel, -rdynamic, sysctl(P_TRACED) detection, debug trap, Engine signal diagnostics |
No checked Engine LLDB launch profile, crash-report archive, or release qualification is supplied. |
| AngelScript runtime | Loopback-by-default TCP endpoint, UDP discovery, line breakpoints, pause/continue/step, script stack, read-only local values, stop/abort/error events | No authentication, encryption, published VSIX, pinned adapter dependency lock, live endpoint CI, global-value inspection, expression evaluation, or state mutation contract. |
| Managed C# runtime | Roslyn/MSBuild compile diagnostics, generated source/project/solution, managed baker and runtime logs, native host frames, load-context and scheduler tests, and standard debugger-compatible assemblies | Engine ships no C# editor adapter, launch profile, symbol server, hot reload, or live managed-debugger acceptance gate. The fos adapter is AngelScript-only. |
| Mixed stack in logs | Script layers plus native frames, origin/catch distinction, safe crash-path output and process-local resolution cache | Native symbol quality depends on the exact binary, libraries, debug data, platform unwinder, and execution mode. MemorySanitizer and ThreadSanitizer disable native stack capture. |
Source/Tests validates stack and exception primitives. It does not currently exercise a real TCP/UDP AngelScript attach session. Project static checks and launch profiles prove integration shape, not the live protocol end to end.
Fast route selection
For a runtime-facing script failure, debugger route selection starts with the owning frame: native C++ needs matching native symbols, AngelScript execution needs its script adapter, and Managed C# failures need the managed diagnostics and assemblies. Use a focused test when the boundary is reproducible without live stepping.
| Symptom family | Start with | Proof boundary |
|---|---|---|
| Native assertion, C++ exception, signal, SEH failure, or lifecycle invariant | Matching native symbols, original log, then the smallest native target under a debugger | Focused Source/Tests/Test_*.cpp case when the boundary is reusable. |
| Script compile, binding, remote-call, or nullability failure | Scripting Runtime and Testing before live attach | Minimal compile/bake fixture or owning test; use attach only for execution-state questions. |
| AngelScript breakpoint, stepping, script stack, or local value | Development config with AngelScript.DebuggerEnabled = True, then a fos attach profile |
Verified breakpoint/stop at the intended process and source revision. |
| Managed C# compiler/analyzer failure | First diagnostic in CompileManagedScripts, generated .gen.csproj/.gen.sln, and the configured source/reference/analyzer set |
Reproduce with the same ManagedScriptTargetFramework, SDK, assemblies, and generated API. |
| Managed C# load, callback, async, or lifetime failure | Managed baker/runtime log plus a focused Test_ManagedScriptBaker or test_managed_*.py case |
Match content-hashed assemblies, runtime payload, backend load scope, target role, and continuation context before interactive attach. |
| Mixed script/native exception | Engine log’s unified trace first, native debugger second | Preserve throw origin and catch site; isolate the reusable boundary in a native test. |
| Memory corruption, race, uninitialized read, or undefined behavior | The narrow supported sanitizer configuration before manual watch-window inspection | Reproducer under the owning sanitizer lane; debugger evidence supplements it. |
| Client host/runtime load failure | Client Runtime Split and Updater | Host/runtime ABI and selector tests before gameplay diagnosis. |
| Browser or Android failure | Platform guide after generic native/script behavior is ruled out | Browser/device evidence for the exact package. |
Build configurations and symbols
BuildTools/cmake/stages/Init.cmake defines the reusable configuration contract. expr_DebugInfo is true for every native configuration except MinSizeRel:
- MSVC-compatible builds add
/Ziand link with/DEBUG:FULLwhen debug information is enabled; - Linux and macOS use
AddNativeOptimizationFlags, which adds-gunder the same condition; - Linux and macOS add
-rdynamicso executable symbols are available to runtime resolution; - MSVC
DebugandRelWithDebInfoalso receive/JMC; - MSVC-only
Release_Debuggingderives fromRelWithDebInfoand adds/dynamicdeoptplus/DYNAMICDEOPT.
Do not use MinSizeRel for a diagnosis that requires source-level native frames. Do not mix a PDB, dSYM/DWARF file, executable, client runtime library, or native extension from different builds, even when names and commit labels look similar.
Debug symbols are not debug semantics
FO_DEBUG=1, DEBUG, and _DEBUG are emitted only for Debug, Debug_Profiling_Total, Debug_Profiling_OnDemand, and Debug_San_Address. Other configurations receive NDEBUG and FO_DEBUG=0, even though most still carry debug information.
This distinction matters:
RelWithDebInfois normally the best first reproduction for release-like behavior with symbols;Debugchanges assertions, CRT selection, optimization, and timing and can hide or expose a different failure;Release_Extis the full-optimization/LTO route and still has native debug information, but stepping and local inspection may be degraded;Release_Debuggingis an MSVC-specific dynamic-deoptimization route, not a cross-platform configuration name.
Windows
Use Visual Studio or a cppvsdbg profile with the exact generated executable, sibling runtime libraries, native extensions, and PDBs. Keep the working directory at the embedding-project root unless its generated config explicitly says otherwise. Break on thrown C++ exceptions only when the exception itself is unexpected; expected throw-as-signal paths can be diagnosed at their reporter or invariant boundary.
Generated MSVC projects include Engine Natvis and NatJMC inputs automatically. A copied executable without its matching PDB and libraries is not a complete diagnostic artifact.
Linux
Use GDB or LLDB against the exact executable and shared objects. Preserve the original environment, working directory, config, resource paths, and allocator/sanitizer selection. The Engine adds -rdynamic; non-PIE is used for most normal executable routes, while baker/client-library and MemorySanitizer-related targets have relocation requirements that differ.
When a crash occurred outside the debugger, retain the Engine log before attempting a second run. An OS core is additional evidence only when the host was configured to produce and preserve it.
macOS
Use LLDB with the matching executable, libraries, and debug data. is_run_in_debugger checks P_TRACED through sysctl, and break_into_debugger uses __builtin_debugtrap. The Engine source is capable of native symbol/stack diagnostics, but the repository does not currently claim a checked macOS editor profile or crash-artifact lane.
Sanitizer and platform limits
Use Testing for the exact sanitizer matrix. The main debugging interactions are:
- MSVC supplies
San_AddressandDebug_San_Address; - native Clang supplies Address, Memory, Memory-with-origins, Undefined, Thread, DataFlow, and Address+Undefined configurations where the toolchain supports them;
- AddressSanitizer, MemorySanitizer, and code-coverage builds switch AngelScript to
AS_MAX_PORTABILITYso native call trampolines do not defeat instrumentation or terminate while an instrumented frame unwinds a registered-function exception; - MemorySanitizer and ThreadSanitizer builds compile the stack/exception layer with
HAS_NATIVE_TRACE=0; expect sanitizer diagnostics, not the normal native mixed-stack contract; - sanitizer timing, allocation, stack size, and calling-convention behavior differ from a release build, so reproduce the original configuration as well.
Native debugging
Launch, attach, and reproduce
- Record the exact Engine revision, embedding-project revision, target, configuration, config/sub-config, command line, working directory, and resource revision.
- Preserve the first failing log and any OS diagnostic before adding logging or changing build mode.
- Reproduce in
RelWithDebInfowith matching symbols unless debug-only semantics are the subject of the bug. - Launch under the native debugger when the Engine’s debugger-aware behavior matters. Late attach can observe process state, but it does not refresh the Engine’s cached debugger-presence decision.
- Stop at the narrow invariant, throw site, sanitizer report, or faulting instruction. Inspect the full thread set, not only the selected frame.
- Reduce the failure to the smallest Engine test or project scenario that preserves it.
- Re-run the original configuration after the fix; a Debug-only success is not release-like acceptance.
Exceptions, assertions, and memory failures
ReportExceptionAndContinue records a non-fatal caught exception. ReportExceptionAndExit and strong assertions record diagnostics and terminate or break according to their contract. The exception-safety tier model and entity-lifecycle throw-as-signal rules live in Exception Safety.
AngelScript throw(...) and verify(...) context arguments are formatted by GetScriptObjectInfo(). Entity handles include the declared script type, entity name, runtime id, and proto id, or <none> when no proto exists, so production exceptions identify the involved objects instead of reporting only a base type such as Critter or AbstractItem. Primitive, enum, string, and null context values keep their compact representation. Test_ScriptBuiltins.cpp pins this entity context through the real global throw binding.
Use break-on-throw carefully. AngelScript bindings and engine lifecycle code can throw as part of an intentional reporting path. Start from the log’s fixed message and context parameters, then place a focused breakpoint at the owning invariant or reporter. For memory corruption, prioritize ASan/MSan/UBSan/TSan evidence and the first invalid access over a later secondary assertion.
Core and minidump boundary
The Engine writes crash diagnostics to its log. It does not currently create Windows minidumps, configure Linux core limits, collect macOS crash reports, upload dumps, or manage a symbol store.
An embedding project or operator may add those facilities, but must define:
- exact executable/library/symbol provenance;
- dump enablement and storage location;
- retention, access control, encryption, and deletion;
- treatment of credentials, player data, chat, network buffers, and memory-resident secrets;
- upload failure behavior and incident ownership;
- a restore/replay procedure that does not require production credentials.
Do not describe a platform’s default crash reporter as an Engine-owned guarantee.
Debugger detection and debugger breaks
is_run_in_debugger() is process-cached on its first call:
- Windows uses
IsDebuggerPresent(); - Linux reads
TracerPidfrom/proc/self/status; - macOS queries
KERN_PROC_PIDand testsP_TRACED.
break_into_debugger() emits DebugBreak, __builtin_debugtrap, or SIGTRAP only when that cached result is true. Because exception handling asks this question during early process initialization, launching outside a native debugger and attaching later is not guaranteed to make Engine-triggered breaks active.
When a debugger is detected at startup, the Engine does not install its fatal signal/SEH handlers. This lets the native debugger receive the fault directly, but it also means the normal out-of-debugger fatal crash-to-log path is not the evidence to expect from that run. Preserve one non-debugger crash run when the crash-log contract itself is under test.
The AngelScript debugger is independent of is_run_in_debugger; attaching the fos adapter does not make the process native-debugger-aware.
Visual Studio Visualizers
Generated MSVC solutions attach these visualizers without a manual Visual Studio install step:
BuildTools/natvis/essentials.natvis: Engine borrow/owner pointers,propagate_const, stack data, engine exceptions, hashed strings, colors, positions, and time values;BuildTools/natvis/unordered_dense.natvis:ankerl::unordered_densetables and segmented vectors;BuildTools/natvis/fonline.natjmc: Engine Just My Code classification;- vendored visualizers for GLM, ImGui,
gch::small_vector, and ufbx.
BuildTools/cmake/stages/EngineSources.cmake attaches the Engine visualizers, and ThirdParty.cmake attaches supported dependency visualizers only for MSVC-generated projects. Natvis improves inspection; it does not change object lifetime, pointer validity, or optimizer behavior.
Visual Studio Solution Folders
For MSVC CMake generators, a target should be created while its intended CMAKE_FOLDER is active. Repository helpers and the final regrouping pass place application, command, core-library, and third-party targets in generated solution folders. Folder placement is navigation only and has no effect on symbols or linkage.
Quick Validation
- Build a narrow native target outside
MinSizeReland confirm its matching symbol artifact exists. - Launch it under the native debugger from the embedding-project root.
- Inspect an Engine pointer and
StackTraceData; confirm the appropriate visualizer is loaded on MSVC. - Trigger or stop at a controlled exception/assertion path and compare the debugger location with the Engine log.
- Run a second out-of-debugger diagnostic self-test only in an isolated workspace when the crash-log route itself must be proved.
Stack Trace Architecture
The Engine captures a bounded native return-address array and optional pre-resolved script layers in StackTraceData. Native symbol resolution is deferred until formatting or explicit resolution. Resolved native frames are cached process-wide by instruction address under a bounded cache so repeated reports do not reload the same symbol information unnecessarily.
Native capture now uses bundled LLVM libunwind on Linux, system libunwind on macOS, Windows unwind tables on 64-bit and frame pointers on 32-bit; a crash can start from its saved POSIX/SEH register context. Linux symbolization uses bundled libbacktrace with dladdr fallback for newly loaded modules; macOS uses dladdr, Windows DbgHelp with executable/module directories in its search path. An unresolved frame retains module+offset for offline lookup. A normal trace starts at its requesting caller; a crash trace starts at the faulting instruction. Managed entry birth stacks are saved as resume points and unwound only if a report needs them, while the opening frame remains active.
Native call stacks come from platform capture when GetStackTrace() runs. FO_TRACE_ZONE(Category) is a separate, category-filtered Tracy timing zone, not a manual call-stack entry; see placing zones.
AngelScript bridge
AngelScriptContext.cpp registers a script stack provider without making Essentials depend on AngelScript headers. The provider walks the active context and parent context chain, resolves each function declaration and original .fos file/line through the preprocessor translator, and preserves the native birth anchors used to splice nested script re-entry into the native stack.
Script frames are captured eagerly because an AngelScript context can be reused or changed after capture. The captured layers live behind immutable shared storage so copying an Engine exception remains noexcept.
Unified frame ordering
The formatted trace is most-recent first and can interleave native bridges with nested script layers:
[Native] code below the active script/native bridge
[Script] active child context
[Native] bridge between child and parent contexts
[Script] parent context
[Native] caller and process entry
Simple traces with no native birth anchors place script frames before the native tail. FormatStackTrace marks every frame [Script] or [Native]; safe crash output falls back to hexadecimal addresses when full resolution is unavailable.
API surface
| Function | Purpose |
|---|---|
GetStackTrace() |
Capture native addresses and currently available script layers. |
GetStackTraceEntry(deep) |
Resolve one unified frame by zero-based depth. |
ResolveStackTrace(st) |
Resolve and interleave all captured frames. |
FormatStackTrace(st) |
Produce the human-readable mixed trace. |
SafeWriteStackTrace(st) |
Write through the low-allocation crash/log path with address fallback. |
stack_trace::clear_resolved_cache() |
Clear process-wide resolved native entries. |
stack_trace::get_resolved_cache_size() |
Inspect the current cache size for tests/diagnostics. |
SetScriptStackTraceProvider(provider) |
Install or clear the higher-layer script provider. |
HasScriptStackTraceProvider() |
Observe provider registration in tests. |
BaseEngineException captures its origin trace at construction. This is why a later catch/report can retain the throw site instead of replacing it with only the reporter’s stack.
Exception reporting and deferred formatting
MakeErrorStackTrace() produces CatchedStackTraceData: an optional origin from BaseEngineException plus a fresh catch-site trace. Formatting uses the origin when present and marks the catch location; non-Engine exceptions have only the catch-site trace.
The exception callback receives the message, already-captured CatchedStackTraceData, and fatal flag. Integrations that forward diagnostics must resolve/copy the data while its provenance is still known and must preserve script/native frame identity.
Logging and crash-path primitives
Normal exception callbacks use the structured logging path. Immediate repeated exception messages are collapsed into a later count. Fatal and low-memory paths use synchronous base logging and SafeWriteStackTrace; if formatting or symbol resolution fails, raw addresses are retained instead of suppressing the report.
Common.AsyncLogWrite controls normal asynchronous log delivery. Fatal crash output suspends it and flushes synchronously so a headless process does not depend on stderr or an unfinished writer thread.
Explicit low-level fatal exits use ReportFatalAndExit or ReportStrongAssertAndExit from FatalError.cpp. This early layer follows StackTrace and BaseLogging, writes one synchronous native report, and delegates only process termination to ExitApp(false), avoiding a reverse dependency on ExceptionHandling. Raw ExitApp(false) remains status-only: controlled compiler/input failures can return a non-zero status without being mislabeled as crashes, while true fatal callers report before exiting.
Crash-to-log guarantee and self-test
Outside a native debugger, the Engine’s own handlers cover supported Windows SEH failures, POSIX fatal signals, and termination. POSIX signals capture from ucontext_t, write synchronously, restore the default action, and re-raise the signal. Windows SEH captures the exception CONTEXT; a reporter thread writes the report even if the faulting thread exhausted its stack. A second crash cannot recursively emit another report. Long-lived Engine worker threads install a POSIX alternate signal stack so stack-overflow diagnostics have space to run; third-party-created threads need the same setup before deeply recursive Engine work. The controlled FO_SELFTEST_CRASH modes also include main_bad_call and thread_bad_call for a null function-pointer call.
FO_SELFTEST_CRASH is an environment-only destructive diagnostic hook run during application initialization after logging and exception callbacks are ready. Supported base modes are main_null_read, main_null_write, main_wild_write, main_stack_overflow, main_fpe, main_abort, main_noexcept_throw, main_throw, main_strong_assert, main_basic_strong_assert, main_fatal_exit, and main_failure_exit; replace main_ with thread_ to run the corresponding worker-style thread route.
Run it only against an isolated disposable process and workspace. It intentionally crashes or terminates the process. An unknown mode logs a warning and continues. The Engine repository does not itself provide a subprocess acceptance runner; checked Last Frontier evidence exercises the Linux headless route, but that project test is not normative Engine proof.
Coverage
Source/Tests/Test_StackTrace.cpp covers provider registration, script-layer order, nested native/script interleaving, truncation, formatting, cache reuse/eviction behavior, individual entry lookup, safe writing, and throwing-provider containment. Test_ExceptionHandling.cpp covers Engine exception payloads, origin/catch behavior, callback replacement, and fatal/non-fatal reporter inputs.
A recoverable ImGui assertion carries only the stringified expression. ImGuiExt::Init therefore installs an error callback that logs ImGui error in window '<name>': <message> immediately before the assertion fires. For an unbalanced Begin/End in a headless client or mapper test, use that line to identify the owning window; ImGui’s own debug log is unavailable because IMGUI_DISABLE_DEBUG_TOOLS is enabled.
The current Engine suite does not open the AngelScript TCP/UDP endpoint, attach the VS Code adapter, validate Natvis in Visual Studio, or execute every crash mode as a subprocess. Those are explicit integration gaps, not implied by the native unit-test result.
Network latency emulation
Network.ArtificalLags (milliseconds, 0 disables) delays both inbound and outbound client batches in ClientConnection::ProcessConnection. Each batch independently samples ArtificalLags / 2 .. ArtificalLags; Network.ArtificalLagsJitter adds another 0 .. jitter milliseconds. The emulation delays delivery but does not throttle the network pump.
Both directions are required to reproduce authority divergence. Inbound delay makes the client learn server state late; outbound delay makes the server learn client actions late. Because related messages receive independent samples, their delay difference produces divergence even though a truly fixed equal delay would cancel. Use a modest base and larger jitter to model occasional stalls.
Server-to-client movement includes an offset_time, allowing the client to fast-forward a late movement. Client-to-server movement has no elapsed-time field; Process_Move starts it from the server’s current frame time, so the authoritative critter trails the client by roughly one-way delay while walking. Settings participate in the generated compatibility hash; adding or renaming one does not require a manual compatibility-marker edit.
AngelScript debugger
Enablement and runtime cost
Set AngelScript.DebuggerEnabled = True only in a development config or command-line override. The default is False. When enabled, AngelScriptBackend retains line cues, disables bytecode optimization, creates the endpoint, and installs a line callback on script contexts.
This changes script build/execution characteristics and adds line-processing overhead. Do not enable it in production, benchmarks, or acceptance runs that claim normal script performance. The AngelScript compile-time AS_DEBUG define follows native Debug configurations and is separate from the runtime AngelScript.DebuggerEnabled setting.
Endpoint and discovery contract
The runtime:
- binds TCP to
AngelScript.DebuggerBindHost, whose Engine default is127.0.0.1; - selects one port in
43000..44999, starting fromprocess_id % 2000; - advertises a newline-delimited JSON protocol version
1; - answers UDP probe
fos-debug-discover-v1on port43001; - advertises process id as
<pid>:<tcp-port>and target role asserver,client, ormapper; - accepts one active TCP debug session at a time.
The VS Code attach configuration accepts processId, a direct endpoint such as tcp://127.0.0.1:43042, discoveryPort (default 43001), and discoveryTimeoutMs (default 800). Desktop discovery requires Node.js UDP support. The current Engine endpoint is TCP only even though the adapter parser also recognizes pipe and Unix-socket endpoint strings for other transports.
Attach capability matrix
| VS Code action | Live Engine attach status | Notes |
|---|---|---|
| Discover/select server, client, or mapper | Supported | Use the advertised <pid>:<port> when several instances exist. |
| Line breakpoint | Supported | The Engine keys breakpoints by source file basename, so duplicate .fos filenames are ambiguous. |
| Pause / continue | Supported | Pause takes effect at the next AngelScript line callback, not while no script line is executing. |
| Step in / over / out | Supported | Operates on script context depth and original preprocessor-resolved lines. |
| Script stack trace | Supported while stopped | The attach response exposes script frames; use the Engine log/native debugger for the unified native stack. |
| Local variables | Read-only, supported while stopped | Values are formatted snapshots per script frame. |
| Script globals | Not implemented | The adapter’s Globals scope contains attach metadata, not live AngelScript globals. |
| Hover/evaluate/expression | Not a live Engine contract | Current attach mode can fall back to adapter-local/mock behavior. Do not use it as process evidence. |
| Set variable/expression, memory read/write, data/instruction/function breakpoints, reverse execution | Not implemented by live attach | Some controls are advertised by the shared adapter because its mock launch runtime supports them; attach-mode errors or placeholder behavior do not extend Engine capability. |
| Exception/abort/error stop | Supported as runtime events | Inspect the Engine log for the complete exception and mixed trace. |
The source editor uses normal one-based lines; the adapter and endpoint translate internally to zero-based protocol lines. Breakpoint verification currently confirms accepted line numbers, not that a given source basename is unique or executable in the active module.
Security boundary
The debugger protocol has no authentication, authorization, confidentiality, or integrity protection. Discovery also reveals a process role and attach endpoint. Keep AngelScript.DebuggerBindHost = 127.0.0.1 unless an explicit, temporary, trusted-network review permits a different bind.
Never expose TCP 43000..44999 or UDP 43001 to the public Internet, an untrusted LAN, a production pod/service, or a shared CI runner. For remote work, keep the Engine bound to loopback and use an authenticated transport owned by the operator, then configure an explicit local endpoint. Do not pass credentials in debugger config or log evidence.
Adapter delivery status
BuildTools/angelscript-debugger is currently source-capable tooling, not a production-distributed editor product:
package.jsonis private, version0.1.0, and supplies typecheck/build/package scripts;- the repository has no adapter dependency lock file, checked VSIX, marketplace publication record, or required adapter build job;
- the TypeScript test file exercises the adapter’s sample/mock runtime, not the live Engine endpoint;
- attach transport requires the desktop Node.js debug-adapter runtime.
An embedding project may build and review a local VSIX, but must own the selected Node/npm versions, resolved dependency lock, extension artifact hash, install/upgrade path, and editor compatibility. Until the Engine adds those artifacts and a live attach gate, do not call the adapter installation reproducible or release-qualified.
Multi-process selection
Client, server, and mapper instances share UDP discovery port 43001 and choose different TCP ports from the process-derived range. Prefer a process-specific selection rather than attaching to the first response. For deterministic automation, read the runtime log’s AngelScript debugger TCP endpoint line and use an explicit endpoint.
Use unique script filenames across debugger-relevant source roots. Because the Engine’s breakpoint table uses only the extracted filename, paths such as Scripts/Admin/State.fos and Scripts/Client/State.fos cannot be independently targeted by the current transport.
Attach troubleshooting
- Confirm the selected process actually received
AngelScript.DebuggerEnabled = True; a compound launch name alone does not enable it. - Confirm the log contains both the TCP endpoint and UDP discovery port lines.
- Verify the bind remains loopback unless remote exposure was explicitly reviewed.
- When discovery finds nothing, use the logged direct TCP endpoint and check local firewall/extension-host UDP behavior.
- When several targets appear, select the advertised role and
<pid>:<port>deliberately. - Confirm editor sources match the baked script revision loaded by the process.
- Rename duplicate
.fosbasenames before trusting line breakpoints. - Treat unavailable globals, mutation, memory, hover/evaluate, and advanced DAP controls as current transport limits.
- If stepping changes behavior, reproduce again with the debugger disabled because line cues and bytecode optimization differ.
Managed C# diagnostics and debugging
Treat Managed C# failures as four separate layers. Preserve the first failure from the owning layer instead of debugging the final wrapper exception:
- Generation — inspect the generated
.gen.cs,.gen.csproj, and.gen.slnbeside the configured scripts. A missing or stale native export is a code-generation problem, not a Mono problem. - Compilation and analysis — run
CompileManagedScriptsand read the first Roslyn/MSBuild diagnostic. ConfirmManagedScriptTargetFramework,ManagedScriptSourceDirs, extra sources/references, analyzers, configured assemblies, and the selected .NET SDK. Synchronization diagnostics use theFOSYNCids documented in Managed C# Scripting. - Bake and delivery — verify that each resource pack contains the expected target assembly and ManagedRuntime payload, and that packaging selected the target-specific runtime. Missing assemblies may be tolerated by deliberately minimal Engine fixtures; an embedding project’s enabled backend must treat them as a packaging/configuration defect.
- Runtime execution — use the managed backend log to distinguish assembly/load-context failures, indexed-ABI bind/hash/count mismatches, P/Invoke registration, callback signature/invocation, scheduler-context violations, synchronization-cover failures, and GC-root/lifetime defects. Generated hot paths report whether the failure came through
CallMethodIndexed; complex fallback calls retainCallMethodBoxed. Both preserve the originating native exception in the active managed entry instead of letting C++ unwind through Mono. Match the content hash and process role before attaching a debugger.
Fallible internal calls return an error payload and Native.cs throws NativeCallException after Mono has left the native frame. Native throw-site information is retained in the active managed entry by the identity of its message object, not its text, so equal messages do not conflate failures and moving GC does not invalidate the association. Reporting searches current and enclosing entries; reflection and single-cause aggregate wrappers are transparent, but a semantic managed wrapper keeps its own frames. If the originating entry ended before a delayed report, only the managed exception description remains. Diagnose an await failure at that boundary before blaming the later entry-unwind assertion.
For transport diagnosis without a managed debugger, enable
ManagedScript.InteropProbeOnStart or call the engine-owned InteropProbe from a
controlled test. Its INTEROP-TRANSPORT checks separate runtime-invoke/thunk/
UnmanagedCallersOnly availability from production dispatch, wrapper creation,
lookup, GC-handle, and allocation costs. Treat failed checks as correctness
defects; compare latency only on the same quiet host and runtime revision.
AngelScript.DebuggerEnabled and the fos adapter affect only AngelScript. They do not expose C# breakpoints, locals, evaluation, or managed stacks. For live C# stepping, the embedding project must supply and qualify a debugger compatible with the embedded Mono runtime, the exact generated assemblies/symbols, and its target platform. A successful IDE attach is project evidence; it is not an Engine-supported delivery claim until Engine owns a repeatable live acceptance gate.
For deterministic regressions, prefer Source/Tests/Test_ManagedScriptBaker.cpp, managed core/analyzer tests, and the focused BuildTools/tests/test_managed_*.py suite. Add the native aligned-frame and live InteropProbe routes when ABI transport changed. Use Managed C# Scripting for the complete validation matrix and platform/sanitizer limits.
Debugger integration in an embedding project
The project should expose independent routes for:
- native launch under a debugger with the exact generated executable and symbols;
- native attach when process startup cannot be debugger-owned, with the cached-detection limitation documented;
- AngelScript attach to an already running development process;
- Managed C# compile/analyzer inspection through the generated solution and an optional project-qualified live managed attach;
- a compound native launch plus
fosattach when both views are needed; - Web/Android launch only for platform-specific symptoms;
- isolated unit-test launch and destructive crash-diagnostic subprocesses.
Keep binary prefixes, paths, tasks, databases, accounts, ports, and game sub-configs in project-owned files. The reusable requirement is the field/validation contract, not a particular .vscode name.
Project launch-profile checklist
A maintained native profile records:
- target, configuration, executable, runtime libraries, symbol source, and working directory;
- config/sub-config and every command-line override;
- configure/build/bake prerequisites and whether they can create a clean build tree;
- debugger type (
cppvsdbg, GDB/LLDB throughcppdbg, or another reviewed frontend); - environment variables, with secrets excluded from source and reports;
- launch-versus-attach behavior and the late-attach limitation;
- a narrow scenario that proves the profile.
A maintained AngelScript profile additionally records:
- how
AngelScript.DebuggerEnabled = Trueis applied to the intended process; - loopback
AngelScript.DebuggerBindHostpolicy; - discovery port/timeout or explicit endpoint selection;
- multi-instance selection and duplicate-filename policy;
- adapter version, dependency/artifact provenance, and installation route;
- supported attach controls and a live breakpoint/stack/local-value acceptance check.
A maintained Managed C# profile separately records:
- the generated
.gen.sln, target assembly, symbols, target framework, SDK, and analyzer set; - the content-hashed assembly/runtime payload loaded by the selected client, server, mapper, or baker;
- whether the debugger supports the embedded Mono/runtime and target platform;
- the distinction between compile/analyzer evidence, runtime logs, native-host frames, and a live managed attach;
- an acceptance scenario for async continuations, callbacks, remotes, or lifetime behavior affected by the change.
Static validation should reject missing task/compound references, stale setting names, a non-loopback default, and profiles that offer fos attach without enabling the endpoint.
Engine test validation
For a reusable native regression:
- select or add the smallest
Source/Tests/Test_*.cppcase; - build the embedding project’s generated unit-test target with matching symbols;
- run the exact Catch2 case that reproduced the failure;
- run the broader Engine unit-test target when Essentials, scripting, threading, or shared runtime behavior changed;
- run the appropriate sanitizer lane for memory/concurrency/undefined-behavior defects;
- repeat the original application scenario after the test is green.
Game scripts, content, bake commands, process names, and gameplay fixtures remain project-owned. A project test can demonstrate compatibility but cannot be the sole normative proof for Engine behavior. For Managed C#, pair it with the relevant baker, analyzer, runtime, packaging, or load-context Engine test.
Client host and runtime validation
Native clients can use a small host executable plus a sibling client runtime library. Diagnose host/runtime loading independently from gameplay:
- build the host and runtime from one revision/configuration;
- confirm the expected runtime alias and matching symbols are adjacent to the host;
- launch the bundled pair;
- test an explicit compatible
--ClientLibPath; - test an incompatible
--ClientLibCompatibilityVersionand verify failure rather than silent wrong-library loading; - test an invalid alternate path and the documented embedded fallback;
- run
Source/Tests/Test_ClientRuntimeApi.cppafter ABI or selector changes.
Package layout and updater rollout are owned by Packaging and Release and Client Runtime Split and Updater.
Project evidence and extraction rules
BuildTools/ExternalProjectEvidence.json pins both project snapshots. Current evidence shows:
- Last Frontier keeps Windows/Linux native launch profiles, an explicit
fosattach profile, compounds that launch with--AngelScript.DebuggerEnabled True, a loopback base bind, and a checked static workflow validator. Its Linux pipeline also exercises Engine crash self-test modes. These are strong project practices but remain project-owned. - FOnline TLA independently carries Windows/Linux native profiles and
foscompounds. At the pinned revision, the compounds do not themselves enableAngelScript.DebuggerEnabled, while its base config disables the debugger and binds it to0.0.0.0. This is useful negative compatibility evidence, not a template to promote.
Reusable rules were re-derived from Engine source. Never copy Last Frontier target names into Engine docs, never promote TLA’s wildcard bind, and never infer live attach coverage from a static launch file. A project revision change requires re-verifying the complete cited files before updating the evidence decision.
Troubleshooting by layer
| Observation | Likely layer | Next action |
|---|---|---|
| Breakpoints are hollow and no endpoint lines exist | Endpoint not enabled or startup failed | Verify effective AngelScript.DebuggerEnabled, then inspect startup logs and port availability. |
| Discovery is empty but TCP endpoint is logged | UDP/firewall/extension-host issue | Attach to the exact logged tcp://127.0.0.1:<port> endpoint. |
| Wrong client/server/mapper stops | Multi-instance selection | Select the advertised role and <pid>:<port>; avoid first-response automation. |
| Breakpoint stops in another file with the same name | Basename collision | Rename one .fos file; current Engine breakpoints are basename-keyed. |
| Globals or hover values look synthetic | Adapter feature exceeds live attach transport | Use read-only locals, logs, or native inspection; do not treat the value as Engine evidence. |
| Native frames are addresses only | Missing/mismatched symbols or resolver limitation | Match binary/libraries/debug data and inspect platform unwinder availability. |
Crash appears in debugger but no FATAL ERROR! log |
Process started under native debugger | Expected debugger-aware route; reproduce once outside the debugger to test crash logging. |
| Engine-triggered break does not fire after attach | Debugger presence was cached before late attach | Relaunch under the native debugger. |
| MemorySanitizer trace lacks native frames | Intentional HAS_NATIVE_TRACE=0 configuration |
Use MSan report and a matching non-MSan symbolized reproduction. |
| Debug build passes but release-like build fails | Semantic/optimization/timing difference | Reproduce in RelWithDebInfo, then sanitizer or Release_Debugging where supported. |
| Dump/core is missing | Host/project collection not configured | Configure the OS/operator-owned dump route; Engine only guarantees its documented log path. |
Maintenance triggers
Re-audit this page in the same change when modifying:
- configuration names,
expr_DebugInfo,expr_DebugBuild, symbol/linker flags, sanitizer wiring, PIE/LTO, or output layout; is_run_in_debugger,break_into_debugger, stack capture/resolution/cache, exception callbacks, crash handlers, logging flush, alternate signal stacks, orFO_SELFTEST_CRASHmodes;- Engine or third-party Natvis/NatJMC files and their CMake attachment;
AngelScript.DebuggerEnabled,AngelScript.DebuggerBindHost, AngelScript line cues/optimization, context setup, endpoint ports/protocol/commands/events, breakpoint keys, stack/locals, or security boundary;- Managed baker diagnostics, generated project layout, analyzer ids, assembly/load-context logging, callback scheduler checks, runtime payload identity, or managed-debugger support claims;
- adapter schema, discovery/transport, DAP capability mapping, dependency/toolchain delivery, tests, or publication;
- project launch/evidence files cited by
ExternalProjectEvidence.json.
Update the canonical English and Russian pages together, refresh the normalized translation source hash, regenerate external evidence, snippets, locale/site/search/routes, and AI delivery, then run the focused debugging gate plus aggregate documentation validation. Runtime changes additionally require the owning native, TypeScript/adapter, process, and project integration tests.
Validation checklist
- Run
BuildTools/tests/test_docs_debugging.pyand aggregate documentation tests. - Run
Source/Tests/Test_StackTrace.cppandTest_ExceptionHandling.cppafter native stack/exception changes. - Run the relevant sanitizer and
Test_ClientRuntimeApi.cpplanes when those boundaries changed. - Confirm PDB/DWARF artifacts and MSVC visualizers from a freshly generated project.
- Prove one native launch under a debugger and one out-of-debugger crash-log route on every platform whose behavior changed.
- For AngelScript, prove one live attach: endpoint log, deliberate process selection, breakpoint, pause/step, script stack, and read-only locals.
- For Managed C#, preserve generated-project and
CompileManagedScriptsevidence, verify the packaged assembly/runtime identity, run the focused managed tests, and label any live IDE attach as project-qualified evidence. - Verify advanced adapter controls remain described according to the live Engine transport, not the mock runtime.
- Confirm debugger bind is loopback, no credentials are present, and dump/log evidence follows project privacy policy.
- Re-run exact checked project evidence and keep project-specific names out of the Engine procedure.
See also
- Testing for unit, sanitizer, coverage, and integration boundaries.
- Profiling for Tracy capture after the correctness boundary is understood.
- Scripting Runtime for backend ownership and execution.
- Managed C# Scripting for the complete C# backend contract.
- Exception Safety for invariant and termination policy.
- Client Runtime Split and Updater for host/runtime diagnostics.
- Web Build, Packaging, and Browser Debugging and Android Build, Packaging, and Device Debugging for platform-specific routes.