FOnline Engine — AI Maintainer Guide
AI guide; human entry: README.md. Docs: EN, RU mirror. Engine authoring is English: commit subjects/bodies, PRs/issues, comments, diagnostics, docs, plans and release notes. Russian only mirrors docs; chat language does not change this rule.
Scope
- This repository is the reusable engine submodule used by games such as Last Frontier.
- Engine-owned code lives under
Source/,BuildTools/,Resources/, and engine tests underSource/Tests/. - Game-specific content, scripts, native extensions, CI glue, and launch presets live in the embedding project and should not be moved into the engine unless the behavior is genuinely reusable.
Before Changing Anything
- Check whether the change belongs to the engine or to the embedding game project.
- Read the nearest existing code and follow its style; do not introduce parallel conventions.
- Verify before editing: docs may drift, and when a doc and the live source disagree, the source wins — fix the doc in the same change.
- If behavior changes, update the owning engine doc in
Docs/in the same worktree change. - Do not commit or push unless explicitly asked by the repository owner.
- When pulling, rebasing, or changing the engine revision, follow documentation revision reconciliation: record old/new SHAs, audit every incoming source/test change, regenerate affected models, and document the reconciliation before dropping any safety stash.
- Rebase unpublished local commits onto the fetched base, even on branches with an upstream or earlier pushes. Never rewrite already-pushed commits. Use merge only to reconcile diverged published histories. Before pushing, verify
git merge-base --is-ancestor <destination-remote-tip> HEAD. No reset/amend of pushed commits, force-push, or remote branch deletion. - Every master update follows the mandatory Engine update/version contract: increment minor from the latest published master tip, use the current UTC year and
YEAR.MAJOR.MINOR-dev, add dated EN/RU change and exhaustive migration notes, reconcile every affected owning/generated doc, and validate the exact publication range. Documentation, test, CI, dependency and revert changes count too. Release branches freeze the cut and advance only patch; do not create or publish them without the owner’s command.
Documentation Map
Checkout agents start here and follow the source guides; llms.txt and llms-full.txt are optional website retrieval outputs, not maintainer instructions. Run python BuildTools/docs_prepare.py before documentation validation. Build the site with python BuildTools/docs_site_build.py; dependency pins and public root endpoints live in ignored Workspace/Documentation/, the rendered site in Workspace/DocumentationSite/, and the authored config in Docs/Site/_config.yml. Delivery indexes and reports are ignored build outputs; reviewed Markdown, policies, images, translations, and public contract models remain versioned. Never stage the generated delivery files.
The full maintained index is Docs/en/index.md; use it when a topic is not listed here. Convention-critical docs for maintainers:
- Engine Architecture - engine layer map and where a behavior belongs.
- Source Tree Guide - source-tree navigation.
- Essentials - low-level platform, logging, memory, filesystem, serialization, sockets, utilities, and
vector/small_vectorselection rules. - Configuration and Data Sources - config parsing, settings, data sources, file lookup, and caches.
- Networking and authority - transports, Noise NK secure channel, key pins and rotation, remote calls, and server-authoritative boundaries.
- Docs/en/how-to/build/project-configuration.md - project
.fomain, resource packs, sub-configs, precedence, and validation. - Docs/en/explanation/content-pipeline/baking.md - resource-pack execution, built-in baker ordering, incremental/full rebuilds, reports, payload ownership, and project composition practices.
- Docs/en/how-to/release/security-and-secrets.md - config substitution timing, command-line masking limits, package signing handoff, CI trust boundaries, rotation, revocation, and incident routing.
- Docs/en/how-to/release/operations.md - server process selection, readiness/health evidence, staged rollout, graceful shutdown, and rollback boundaries.
- Engine versioning and release notes and changelog - Engine
VERSIONownership, CalVer, bilingual change notes, and release/migration rules. Update both changelog locales with developer-visible changes. - Docs/en/how-to/release/backup-and-recovery.md - backend-aware backup sets, oplog limits, isolated restore, disaster-recovery drills, and project-owned recovery policy.
- Docs/en/how-to/build/generated-content.md - generated source/resource/metadata/documentation dependency order and recovery.
- Docs/en/how-to/migration/engine-upgrade.md - complete Engine-update, migration, compatibility, and documentation reconciliation route.
- Docs/en/reference/platforms/support-matrix.md - truthful build/smoke/source-capable claims and project release qualification.
- Docs/en/contributing/testing/index.md - test-suite inventory, generated test targets, coverage, and validation routing.
- Docs/en/how-to/testing/gameplay-and-integration.md - boundary-based gameplay/integration tests, deterministic fixtures, process manifests, semantic markers, deadlines, cleanup, and reports.
- Docs/en/how-to/ai-control-protocol.md - project-neutral AI-control transport, command lifecycle, loopback threat boundary, reference client/sample, and project-owned observation/MCP boundary.
- Docs/en/how-to/quality/profiling.md - Tracy build modes, client/server capture boundaries, reproducible workloads, and result interpretation.
- Documentation maintenance - source-grounded docs maintenance workflow.
- Documentation site publication - GitHub Pages/Jekyll navigation/search, preview, rendered-route and pinned Chromium/WCAG gates, CI artifacts, custom-domain contract, and production verification.
- Docs/en/contributing/documentation/ai-evaluation.md - versioned standalone documentation tasks, deterministic retrieval gate, model-family run protocol, and scoring.
- Docs/en/contributing/documentation/snippets.md - fenced-example policy, parser harnesses, generated coverage, external shell checks, and semantic-owner boundary.
- Docs/en/contributing/documentation/translation.md - English/Russian paths, glossary, normalized source hashes, parity, and link/code preservation.
- Docs/description-translations.ru.json and Docs/generated/description-translation-status.json - stable-ID Russian overlays and explicit semantic-translation coverage for prose supplied by generated contract models; regenerate/check with
BuildTools/docs_description_translations.py. - Public Example Repositories - external example portfolio, ownership, exact Engine pins, compatibility lanes, governance overlay, releases, support, and asset provenance.
- llms.txt, llms-full.txt, and docs-manifest.json - generated external-agent routes derived from the documentation manifest; regenerate with
BuildTools/docs_ai_delivery.py, never edit them manually. - Docs/ai-evaluation.json and Docs/generated/ai-evaluation-report.json - reviewed AI task source and deterministic current report; regenerate the report with
BuildTools/docs_ai_eval.py, never edit it manually. - BuildTools/docs_ai_model_eval.py, BuildTools/docs_ai_model_review.py, and Docs/_meta/ai-evaluation/ - optional isolated model-family runner, compact independent-review workflow, and dated internal evidence; keep raw runs in ignored
Workspace/ai-evaluation/and never claim the production target from automatic term matching alone. - BuildTools/SnippetPolicy.json and Docs/generated/snippets.json - reviewed fence/harness policy and generated complete coverage report; regenerate with
BuildTools/docs_snippets.py, never edit the report manually. - BuildTools/DocumentationDiagrams.json, Docs/generated/diagrams.json, and Docs/assets/diagrams/ - source-owned accessible teaching diagrams, provenance, and exact SVG hashes; regenerate with
BuildTools/docs_diagrams.py, never edit the SVG or catalog manually. - BuildTools/DocumentationScreenshots.json, Docs/generated/screenshots.json, and Docs/assets/screenshots/ - versioned tool screenshots, exact capture inputs, recapture triggers, and source/image hashes; regenerate the catalog with
BuildTools/docs_screenshots.py, never replace an image without updating its provenance record. Docs/Site/Data/docs-site.json, Docs/Site/Assets/docs-search.json, Docs/Site/Assets/docs-search.ru.json, and docs-manifest.json#/routing - generated build-time navigation, published per-locale search, and version/locale/legacy-route data from the same manifest; regenerate withBuildTools/docs_site.py, never edit them manually.- Docs/en/contributing/decisions/0006-documentation-version-locale-routing.md - rolling/current documentation identity, deferred release snapshots, planned English/Russian paths, and durable Markdown redirect policy.
- ThirdParty Maintenance - vendored dependency update, pruning, version pin, and
(FOnline Patch)workflow. - Docs/en/explanation/runtime/client-updater.md - client host/runtime split, ABI, updater protocol, and
UpdaterBackend. - Frontend and Rendering - application services, renderer selection and backend contracts, atlases, render targets, logical resolution, shared depth, direct-scene paths, and validation.
- Docs/en/troubleshooting/debugging.md - native, AngelScript, and Managed C# debugging, mixed stacks, crash diagnostics, and validation boundaries.
- Docs/en/contributing/coding-contracts/nullability.md -
T?script /ptr<T>·nptr<T>native boundary contract. - Docs/en/how-to/scripting/lifecycle-and-concurrency.md - script module init, callback ownership,
[[Async]]/Yield, server entity covers, mutable-state ownership, and teardown. - Docs/en/how-to/scripting/managed-csharp.md - Managed C# configuration, generated projects and assemblies, attributes, async scheduling, synchronization-cover analyzers including FOSYNC015, runtime isolation, packaging, platforms, diagnostics, migration, and validation.
- Docs/en/reference/scripting/remote-calls.md - remote-call declaration, direction, handler, authority, baked catalog, and validation contract.
- Native Extensions - project-native C++ roles, hooks, script exports, lifecycle, compatibility boundary, and validation.
- Project-Local Dependencies - project-local library/SDK ownership, role-scoped linking, platform/package delivery, updates, and validation.
- Prototype Format - prototype syntax, inheritance, property applicability, references, migrations, and project-owned semantic validation.
- Docs/en/how-to/content/map-format.md -
.fomapsections, placement ids, ownership, mapper round-trip, side-specific baking, and runtime materialization. - Docs/en/how-to/content/model-format.md -
.fo3dsyntax, FBX/OBJ inputs, layers, attachments, transforms, materials, cuts, runtime composition, and validation. - Text and Localization -
.fotxtsyntax, language normalization, prototype$Text, runtime lookup, renderer color tags, and the project-formatting boundary. - Image And Sprite Formats - image source formats, FOFRM composition, legacy selectors, baked sprite records, runtime factories, atlases, caches, and validation.
- Effect Format -
.fofxsections, passes, render state, shader resources, baking outputs, runtime cache, script values, and validation. - Docs/en/how-to/content/particle-format.md - optional SPARK/Effekseer selection,
.spark/.efkprojauthoring,.spk/.efkbaking, Mapper tools, runtime routes, integrations, and validation. - Docs/en/how-to/tools/particle-authoring.md - operational Particle Preview, SPARK editor, pinned Effekseer editor, focused-viewer, and visible-validation workflow with versioned screenshots.
- Docs/en/how-to/content/font-format.md - FOFNT and BMFont descriptor syntax, resource delivery, slots, binding scale, measurement, wrapping, rendering flags, inline colors, and validation.
- Audio - WAV/ACM/Ogg delivery and decoding, effect-name/variant lookup, music/repeat behavior, mixing, headless boundaries, and project validation.
- Video - experimental Ogg/Theora delivery and decoding, fullscreen queue/input/music behavior, embedded playback, memory, and project validation.
- Frontend and Rendering owns Engine-native GUI input/render primitives; high-level GUI libraries and declarative layouts are project-owned.
- Docs/en/how-to/scripting/style-and-refactoring.md - reusable
.fosmodule construction, source layout, formatter behavior, generated-file discipline, attributed calls, refactoring batches, and validation gates. - Model Animation -
.fo3danimation tuples, authored speed, one-step aliases, common duration metadata, script lookup, and project boundary. - Sprite Root Motion - 2D per-frame
NextX/NextYoffsets, walk/run cycle anchoring, movement-driven frame selection, and project validation. - Docs/en/contributing/contract-change-management.md - multi-domain generated contract diff, shared disposition ledger, and CI enforcement workflow.
- Docs/en/contributing/coding-contracts/smart-pointers.md - native smart-pointer vocabulary (
ptr<T>/nptr<T>borrows,unique_*/refcount_*owners, engine-ownshared_ptr/weak_ptr), raw-pointer allowlist, and audit expectations. - Docs/en/contributing/coding-contracts/exception-safety.md - engine-invariant stability under exceptions: terminate-on-OOM allocation model, entity-lifecycle throw-as-signal contract, and the
FO_STRONG_ASSERTdisposition rules. - Docs/en/contributing/coding-contracts/thread-safety-analysis.md -
FO_TSA_*Clang Thread Safety Analysis annotations, locking primitives, and-Werror=thread-safetyenforcement. - Docs/en/how-to/tools/mapper.md - mapper automation and native mapper helper integration points.
- Docs/en/how-to/tools/mapper-interactive.md - stock interactive Mapper menus, windows, editing, history, save discipline, failure diagnosis, and full-window capture workflow.
- Animation and Particle Viewers - standalone/Mapper AnimationViewer and ParticleViewer build, controls, review evidence, and validation boundaries.
- Web Build, Packaging, and Browser Debugging - web target build/package/browser workflow.
- Android Build, Packaging, and Device Debugging - Android target build, package, device, and release-qualification workflow.
- Docs/en/reference/script-api/index.md - generated human reference for the native-codegen API surface.
- Docs/generated/api.json - canonical machine-readable native-codegen API model.
- Docs/en/reference/cmake/index.md - generated human reference for project options, stages, hooks, and selected CMake helpers.
- Docs/generated/cmake.json - canonical machine-readable CMake project-interface model.
- Docs/en/reference/buildtools/index.md - generated human reference for the main BuildTools command line.
- Docs/generated/cli.json - canonical machine-readable BuildTools CLI model.
- Docs/en/reference/helper-cli/index.md - generated human reference for engine-owned helper-script command lines.
- Docs/generated/helper-cli.json - canonical machine-readable helper CLI model and ownership inventory.
- Native extension reference - generated role, hook, and native binding reference.
- Docs/generated/native-extension.json - canonical machine-readable native-extension model.
- generated prototype-format reference - generated prototype grammar, built-in properties, and validation reference.
- Docs/generated/prototype-format.json - canonical machine-readable prototype-format model.
- Docs/en/reference/map-format/index.md - generated map grammar, ownership, properties, baking, and validation reference.
- Docs/generated/map-format.json - canonical machine-readable map-format model.
- Docs/en/reference/model-format/index.md - generated model-description grammar, assets, composition, animation, and validation reference.
- Docs/generated/model-format.json - canonical machine-readable model-format contract.
- Generated text-format reference - generated text-pack, language, prototype-text, runtime, and validation reference.
- Docs/generated/text-format.json - canonical machine-readable text-format contract.
- Generated effect-format reference - generated effect syntax, render-state, resource, baking, runtime, and validation reference.
- Docs/generated/effect-format.json - canonical machine-readable effect-format contract.
- Docs/en/reference/particle-format/index.md - generated particle backend, source/runtime form, baking, rendering, tooling, integration, and validation reference.
- Docs/generated/particle-format.json - canonical machine-readable particle-format contract.
- Docs/en/reference/font-format/index.md - generated font descriptor, binding, layout, rendering, and validation reference.
- Docs/generated/font-format.json - canonical machine-readable font-format contract.
- Docs/en/reference/packages/index.md - generated human reference for package declarations, support matrix, payloads, and artifacts.
- Docs/generated/package.json - canonical machine-readable package interface model.
- Docs/en/reference/public-contract/index.md - generated public contract index and stability notes.
- First FOnline Headless Project - tested first headless project tutorial.
- Examples/MinimalProject/README.md - canonical engine-owned starter and smoke contract.
- Examples/ContentShowcase/README.md - canonical engine-owned content gallery, provenance, performance-budget, and capture contract.
- Docs/en/reference/public-examples/index.md and Docs/generated/public-examples.json - checked public-example repository registry from
Examples/PublicRepositories.json. - Docs/en/reference/platforms/generated-matrix.md, Docs/generated/support-matrix.json, and docs-manifest.json#/translation_status - generated support evidence and locale parity; regenerate with their owning BuildTools scripts.
- Source/README.md - source-tree overview.
- Source/Tests/README.md - engine unit-test suites.
- BuildTools/README.md - build-tooling notes.
Validation Routing
- Zero tolerance for warnings. Engine C++ builds and codegen must finish clean; there is no acceptable warning backlog (Clang thread-safety analysis already runs as
-Werror=thread-safety). Never introduce a new warning; if a change surfaces one, resolve it at the root in the same change rather than suppressing it. - Engine C++ changes: build and run the embedding project’s generated engine unit-test target (
<ProjectDevName>_UnitTests/RunUnitTests). - Build-system changes: update
BuildTools/cmake/ProjectInterface.jsonwhen the project-facing CMake surface changes, update Project-Local Dependencies when role-scoped project linking or dependency integration changes, regenerate the CLI reference whenBuildTools/buildtools.py::create_parser()changes, run the affected structural/generated-reference checks and the aggregate contract diff, and validate the affected preset or BuildTools command in the embedding project that exercises it. - Platform-packaging changes: update
BuildTools/PackageInterface.jsonwhen targets, platforms, architectures, packs, payloads, or artifacts change; regenerate its model/reference, run the structural package test and aggregate contract diff, validate the relevant package path (Raw,Raw+WebServer, Android package, etc.), and update the platform doc. - Native-extension composition or hook changes: update
BuildTools/NativeExtensionInterface.json, regenerate its model/reference, runvalidate_native_extension_interface.cmake, the aggregate contract diff, and the minimal starter or affected embedding-project path. - Prototype parser, property text loading,
HasProtosmetadata, orBaking.ProtoFileExtensionschanges: updateBuildTools/PrototypeFormatInterface.json, Prototype Format, regenerate/check the prototype-format model/reference, run its focused test and aggregate contract diff, then rebake an affected embedding project. - Map parser, mapper serialization, map baker,
ItemOwnership, static-map loading, or map materialization changes: updateBuildTools/MapFormatInterface.json, Docs/en/how-to/content/map-format.md, regenerate/check the map-format model/reference, run its focused test and aggregate contract diff, then run engine map tests and rebake an affected embedding project. .fo3dparser state/tokens, FBX/OBJ import, model layers, attachments, particles, transforms, materials, cuts, rendering flags, or compile-time model limits: updateBuildTools/ModelFormatInterface.jsonand Docs/en/how-to/content/model-format.md, regenerate/check the model-format reference, runBuildTools/tests/test_docs_model_format.py, the aggregate contract diff, focused model-baker tests, and a visible embedding-project scene..fotxtparsing, language selection/normalization,TextBaker, prototype$Text, text script methods, or inline color tags: updateBuildTools/TextFormatInterface.jsonand Text and Localization, regenerate/check the text-format reference, runBuildTools/tests/test_docs_text_format.py, the aggregate contract diff, focused text-baker tests, and an embedding-project bake plus visible language check..fofxsections/state,EffectBaker, built-in shader resources, renderer bindings,EffectManager, effect script methods, orFO_EFFECT_*limits: updateBuildTools/EffectFormatInterface.jsonand Effect Format, regenerate/check the effect-format reference, runBuildTools/tests/test_docs_effect_format.py, the aggregate contract diff, focused effect-baker tests, and visible checks on every affected backend/profile.ImageBaker, FOFRM fields/flattening, any built-in image loader, the baked sprite container,DefaultSpriteFactory,SpriteManagerimage dispatch/cache, orTextureAtlasupload behavior: updateBuildTools/ImageFormatInterface.jsonand Image And Sprite Formats, regenerate/check the image-format reference, runBuildTools/tests/test_docs_image_format.py, the aggregate contract diff, focused image/atlas tests, and an affected embedding-project bake plus visible client checks.FO_*_PARTICLES,.spark/.efkprojsource parsing,.spk/.efkbaking, backend composition,SparkQuadRenderer, Effekseer callbacks, Mapper particle tools,ParticleManager,ParticleSpriteFactory, script methods, or model-particle integration: updateBuildTools/ParticleFormatInterface.jsonand Docs/en/how-to/content/particle-format.md, regenerate/check the particle-format reference, runBuildTools/tests/test_docs_particle_format.py, the aggregate contract diff, focused baker/runtime/model tests, and an affected embedding-project bake plus visible backend/integration checks.- Mapper/SPARK UI, full-window capture, a versioned screenshot, its owning prose, capture fixture, or source provenance: update Docs/en/how-to/tools/mapper-interactive.md and/or Docs/en/how-to/tools/particle-authoring.md, update
BuildTools/DocumentationScreenshots.json, recapture from the recorded example/profile, regenerateDocs/generated/screenshots.json, and runBuildTools/tests/test_docs_screenshots.pyplus the rendered-site image gates. .fofnt/.fntparsing,Baking.RawCopyFileExtensions,FontManager,FontType,FontFlag,TextFormat,Game.BindFont, text measurement, wrapping, or inline color behavior: updateBuildTools/FontFormatInterface.jsonand Docs/en/how-to/content/font-format.md, regenerate/check the font-format reference, runBuildTools/tests/test_docs_font_format.py, the aggregate contract diff, native unit tests, and an affected embedding-project bake plus focused measurement and visible rendering checks.SoundManager,ResourceManageraudio indexing, WAV/ACM/Ogg decoding,Game.PlaySound,Game.PlayMusic,Audio.*,AppAudio, or raw-copy audio delivery: updateBuildTools/AudioInterface.jsonand Audio, regenerate/check the audio reference, runBuildTools/tests/test_docs_audio.py, the aggregate contract diff, native unit tests, and an affected embedding-project bake plus audible visible-client checks on every claimed platform.VideoClip, Ogg/Theora decoding,Game.PlayVideo, fullscreen queue/input/music/drawing,VideoPlayback,Game.DrawVideoPlayback, or raw-copy OGV delivery: updateBuildTools/VideoInterface.jsonand Video, regenerate/check the video reference, runBuildTools/tests/test_docs_video.py, the aggregate contract diff, native unit tests, and an affected embedding-project bake plus visible video acceptance on every claimed platform.- Native GUI input/render primitives or their script exports: update Frontend and Rendering and the affected generated API, then run focused native tests plus an embedding-project compile/bake and visible interaction checks. High-level GUI libraries and declarative layouts are project-owned; do not describe them as Engine-owned.
- AiControl framing, methods, errors, common command fields, authorization, queue/event bounds, lifecycle, security, reference client, or sample behavior: update
BuildTools/AiControlProtocol.jsonand Docs/en/how-to/ai-control-protocol.md, regenerate/check the AiControl protocol and helper-CLI references, run both focused AiControl test files plus the aggregate contract diff, and validate every affected embedding-project native/client/MCP path. Keep project observations, game actions, administrator tools, and MCP namespaces project-owned. - Model animation tokens, mesh clip durations, alias selection,
ModelInfoBaker,ModelAnimationInfo.foinfo, metadata registration, or duration script methods: update Model Animation, runBuildTools/tests/test_docs_model_animation.py, regenerate/check the native API/reference when exports changed, run the focused native tests, and rebake an affected embedding project. - Image-frame
NextX/NextY, baked sprite offsets,SpriteSheet,MovingContextinterpolation,GeometryHelper::NormalizeHexOffset, orCritterHexViewwalk/run phase behavior: update Sprite Root Motion, runBuildTools/tests/test_docs_sprite_root_motion.pyplus focused image-baker/geometry tests, then validate the affected locomotion in a visible client scene. - Public example portfolio, ownership, common policy/workflow files, starter source, compatibility boundaries, or published example pins: update
Examples/PublicRepositories.jsonand Public Example Repositories, regenerate/check the public-example model, validate affected repositories in both pinned/current modes, and update only verified public links. - AngelScript module construction, source conventions, CoreScripts formatting, namespace/file ownership, attributed calls, mutable globals, or
.fosrefactoring guidance: update the AngelScript style and refactoring guide and its Russian mirror, runBuildTools/tests/test_docs_angelscript_style.py, use the owning Engine or project formatter, and compile/test every affected script role. - Managed C# configuration, generated project/assembly shape, attributes, async callbacks, synchronization covers, analyzers, marshalling, runtime/load-context lifetime, packaging, platform wiring, or migration: update the Managed C# scripting guide and its Russian mirror, run
BuildTools/tests/test_docs_managed_csharp.pyplus affectedtest_managed_*.pyandTest_ManagedScriptBakerroutes, then compile/bake every affected project role. - Fenced documentation changes: declare a supported language, regenerate/check
Docs/generated/snippets.json, runBuildTools/tests/test_docs_snippets.py, and runpython BuildTools/docs_snippets.py --check --external; then run the semantic compile/bake/smoke owner for every claimed outcome. - Script/native API boundary changes: update nullability/API docs, review the source-owned
///@ ApiContractdisposition, run the aggregate contract diff described inDocs/en/contributing/contract-change-management.md(using the specialized API report when deeper symbol context is useful), and run the smallest test target that covers the changed binding. For project remote calls, bake both sides and check the project catalog described inDocs/en/reference/scripting/remote-calls.md. - When a serialized contract changes (entity properties, network messages, save data), update the relevant
///@ MigrationRulemetadata. -
Engine changes that affect network interaction or are otherwise substantial enough to matter for client/server runtime compatibility must force a compatibility-version change by bumping the central marker in
Source/Common/Common.h. Locate and increment the current marker instead of copying a fixed value from documentation:rg -n "MigrationRule Version" Source/Common/Common.h
Behavioral Rules
- Script callers own the complete native entity cover. Before an ordinary server
FO_SCRIPT_APIcall, AngelScript must acquire every existing entity the native call graph can read or mutate, including transitive map/location, holder, controlled-critter, and group dependencies. A native path reachable from that call may validate the prepared package withValidateEntityAccess()and may callEnsureEntitySynced()freely and repeatedly to retain the own lock of an entity already covered by the current context. Both operations are idempotent no-ops once their condition is satisfied, so loops, repeated same-target calls,catchhandlers, and recursion carry no call-count restriction. Each actualEnsureEntitySynced()acquisition never releases/reacquires the caller cover and never parks on a lock: it only ADDS the target’s own lock plus the intermediate ancestor marks, as one all-or-nothing state-mutex transaction. Because the covering ancestor is held exclusively, any block it meets is a foreign thread mid-flight through its own non-blocking pass (releasing marks by lock address rather than hierarchy, or rolling back an ascending-address try-prefix), which clears in microseconds — so the acquisition is MANDATORY and retries the whole batch with bounded back-off until it lands, retaining nothing across the back-off. It throws only when the target is not actually covered or the covered-cover invariant is corrupt, never for ordinary transient contention, and a failure leaves the caller cover intact. It cannot repair an omitted unrelated existing dependency. Fresh parentless entities are captured only by the private trusted registration boundary before publication; that boundary never authorizes discovery or acquisition of pre-existing dependencies. Native code must not callSyncEntity()/SyncEntities()or otherwise obtain missing existing cover on the script’s behalf. The explicit synchronization primitivesGame.Sync,Game.SyncRelease,Game.Lock, andGame.Unlockare the exceptions because changing synchronization state is their script-visible purpose. The whole contract is opt-out: with theServer.SingleThreadedLogicsetting the engine runs its logic on one worker, the cover model is bypassed, and scripts need no synchronization at all (see Docs/ServerRuntime.md). - Fix the source, never mask. When a lower layer returns a wrong result, swallows an error, or silently coerces bad input into “looks fine”, fix it at the source (throw / validate / propagate) instead of papering over it from the caller with pre-checks, try/catch that fabricates a “good” value, default substitutes, or other workarounds. If you find yourself adding a guard that compensates for a bug elsewhere, stop and fix the bug instead.
- Only the current API; preserve existing meaning. Add or extend a contract without changing the meaning of existing valid values, inputs, outputs, units, IDs, defaults or side effects. A replacement uses a distinct name/type/key/format and removes the old surface. No deprecated aliases, fallback parsing, old-API shims or permissive coercions. An unmigrated affected consumer must fail explicitly during compilation/baking/validation; unaffected usage must retain its behavior. This applies to experimental and internal surfaces too. The narrow persisted-property conversion exception is existing
MigrationRulehandling; project-specific database business migrations remain project-owned. An old-build refusal may carry the temporary marker below, which never authorizes an old API bridge. ADR-0002 and the exhaustive migration record own the full rules. - Code that knows an old format carries its removal date. Mark every necessary old-build refusal or transition point, tests included, with
FO_TEMPORARY_COMPAT(Id, "YYYY-MM-DD");(native) or[TemporaryCompat("Id", "YYYY-MM-DD")](managed). One id and date cover all places of one compatibility.BuildTools/temporary_compat.pyfails after the date and is run by the Engine validation workflow; embedding projects can pass their own source directories too. See temporary compatibility. - Keep engine invariants stable under exceptions. An exception thrown partway through a multi-step engine mutation (entity create/register/destroy/invalidate, cross-entity links) must never leave a half-mutated, restart-only state. Engine allocation terminates on OOM (
safe_alloc/safe_allocator), sobad_allocis not a recoverable error and never a reason to build a rollback. A post-mutation invariant that “can only be false if the world is already corrupt” is aFO_STRONG_ASSERT(always-on, deterministic exit), not a swallowableFO_VERIFY_AND_THROW. Entity create/destroy is throw-as-signal, not transactional rollback (events may legitimately destroy or relocate the entity; the function throws but the entity is left in a valid state) — pinned bySource/Tests/Test_EntityLifecycle.cppandTest_ServerMapOperations.cpp, so do not add a blanket create-time rollback. Full rules: Docs/en/contributing/coding-contracts/exception-safety.md. - No singletons. No hidden global / static state for engine-semantic data. State with per-engine semantics (lock sets, entity registries, ticket allocators tied to one engine’s wait queues, mutable runtime caches that could be mixed across engines) lives on an owning instance reachable through the engine object graph — typically
ServerEngine(managers),ServerEntity(per-entity locks, properties, parent), orBaseEngine(ScriptSystem, settings). Do not introduce file-scope mutable statics,staticdata members on engine classes, function-local statics that cache cross-call state, or unnamed-namespace mutable variables. Multiple engine instances may coexist in one process (embedding projects run parallel test suites this way), so hidden globals silently share state across engines, hide cross-test pollution, and turn lifetime-ordering bugs into timing windows. static thread_localis OK only when threads are partitioned by engine ownership and the slot can never observe values from a foreign engine on the same thread. The current example isSyncContext* CurrentContextinSource/Server/EntitySync.cpp: every thread that touches it belongs to exactly one engine, so the thread-local slot is implicitly per-engine. The same logic permits astatic std::atomicwhose value has no per-engine semantics (e.g. a monotonic ticket counter only ever compared inside one lock’s wait queue). When in doubt, prefer instance ownership — but do not pay a mutex-plus-map lookup just to satisfy the rule when athread_local T*is already correctly isolated.staticis otherwise acceptable only for compile-time constants (static constexpr), file-local pure helper functions (static void Helper(...)), andstatic_assert. An existing static that holds per-engine state is a bug to migrate to instance ownership, not a precedent to copy.
Style Notes
-
Managed projects and the runtime C# compiler enable overflow checking by default. Use a local
uncheckedonly for intentional wrapping or bit truncation; preserve checked diagnostics elsewhere. - Prefer existing engine idioms over new local abstractions.
- Enum-entry descriptions use trailing declaration comments; value-layout field descriptions use
// field: ...lines immediately beforeExportValueType. These field lines are metadata, not multi-line prose; type-description prose retains the two-line limit. See generated metadata. - Use
structonly for passive data aggregates: no user-defined constructors, methods, or hidden invariants. When behavior or construction logic belongs on the type, make it aclassand apply full encapsulation with private state and a deliberate public interface. Do not mark such structsfinal— a plain data aggregate needs no inheritance guard, the keyword only complicates it. - The code is the documentation; a comment carries only what the code cannot. Names, types, and structure already state what happens, so a comment that re-tells them is deleted, not shortened — and the default state of a line of engine code is no comment. Write one only for what the reader cannot see: why the code sits here rather than elsewhere, why this approach instead of the obvious alternative, the gist of a genuinely dense block, or a contract that is not derivable from the code (sentinel meanings and units, threshold calibration, concurrency/ownership/lifetime requirements, edge-case exemptions, why a plausible alternative is unsafe). One line is the default and two is the ceiling — no multi-line essays. When the explanation genuinely needs more room, it belongs in the owning
Docs/page with a short link from the code; never link a temporary plan. Do not narrate the next statement, restate a signature or parameter list, enumerate steps the code already spells out, or record history, fixed-bug stories, or benchmark numbers (those belong in commit messages and docs). A comment points at code, never at bookkeeping: no task, ticket, issue, or PR numbers, no dates or “as of” wording, no version or release markers, no author names — that metadata lives in the commit and the tracker, and in code it only rots. Refer to a symbol, a function, a file, or the test that pins the behavior; the sole non-code target is the owningDocs/page named above. Short structural headings are allowed when they materially improve navigation in long flat code. Only the concluding sentence of a comment drops its period (the single line of a one-line comment, the final line of a two-line block, or a trailing comment); every preceding sentence keeps its period. Capitalize prose, not symbols (a leading lowercase code identifier stays as written). Fixed-text banners such as the license header, and commented-out code, are exempt. - A type whose whole content is static members is a namespace, not a class. A deleted constructor plus nothing but statics is a namespace written in class syntax: it can never be instantiated, it has no state and no invariant, and
X::f()reads the same either way — so writenamespace X { ... }and drop thestatic, which at namespace scope would mean internal linkage instead. Two things change with the spelling and are worth knowing: a namespace resolves names in declaration order (a class body does not, so a helper called from an inline function above it must be declared first), and a namespace cannot be a friend. That second point is the exception:safe_allockeeps its class shape precisely because a dozen types declarefriend class safe_allocso its factories can reach their private constructors. There, the class-ness is load-bearing rather than accidental. - Do not write
externon a function. At namespace scope a function already has external linkage, so the keyword states the default and says nothing. Keep it only where it still decides something: a variable declaration, whereexternis what makes the line a declaration rather than a definition, and anextern "C"language-linkage boundary. - A free function that has to carry its module’s name gets that module’s namespace instead.
write_base_log,fs::exists,mem_copy,get_stack_traceandreport_exception_and_continuewere all spelling their module into the identifier because nothing else could — so the module becomes a namespace and the word comes out of the name:logging::write_base,fs::exists,memory::copy,stack_trace::get,exceptions::report_and_continue, alongside thewinapi::/posix::/platform::/utf8::modules that already read that way. Once a module takes a namespace its whole free surface moves in, types included, so it is never split across two scopes. The test is the name, not the module: a function that needed no prefix is already the layer’s vocabulary and stays bare —numeric_cast,safe_call,copy_hold_ref,coarse_sleep,run_async,exit_app, thevec_*helpers. One name is not ours to pick:logcollides with::logfrom<cmath>, which vendored headers call unqualified, so the logging module islogging::. - Naming follows the layer:
Source/Essentials/is snake_case, everything above it is PascalCase. The foundation layer is the engine’s standard-library-shaped vocabulary, so its types, free functions, methods, public fields and private members are spelled the waystd::spells its own —data_writer,safe_alloc::make_unique,logging::write,_read_pos. Every layer above it (Source/Common/,Client/,Server/,Tools/, and an embedding project’s own native sources) stays PascalCase, so the spelling of a name tells a reader which side of the boundary it lives on. Four things keep PascalCase inside Essentials because they are not the layer’s to name: template parameters (CharT,Traits,Alloc,InlineCapacity— the standard library’s own convention), exception type names (XExceptionis an engine-wide convention that Essentials only seeds), the ref-count protocol as AngelScript spells it —AddRef/TryAddRef/ReleaseonasIScriptFunctionandasITypeInfo(refcountableaccepts either spelling andrefcount_ptrdispatches on whichever the pointee declares, sorefcountedusesaddref/release/get_refcountand only the library types keep their own), and foreign API names — the OS calls re-spelled insideWinApi.cpp/Posix.cpp, and the global crash hooks inExceptionHandling.cppthat the vendoredbackward.hppdeclares by name. Module file names stay PascalCase as well (MemorySystem.h), because theEssentials.hinclude-order block andBuildTools/tests/test_essentials_layering.pyare keyed on them. See Docs/en/reference/native/essentials.md. - Reuse the existing MIT source-file header and
#pragma once; match the surrounding module-level layout. - Module layout: put class definitions and static-function forward declarations near the top of the translation unit, then implementations ordered from high-level entry points down to low-level helpers, so a reader meets the public/orchestrating code first.
- Inside a class, what reports on the object comes before what changes it. A class body opens with construction and destruction, then the informational block — the
[[nodiscard]]accessors (Get*,Has*,Is*,Check*) a reader consults to learn what the object holds — and only below them the methods that mutate it, ordered by lifecycle: the initializing ones (Set*,Add*,Attach*,Load*) before the finalizing ones (Remove*,Detach*,Clear*, the teardown helpers). SoCritter::ClearAllAssociationssits at the bottom of the mutating run rather than beside the destructor it serves, andMapManager::ClearStaticMapsbelowViewMaprather than inside the accessor block. Blank lines separate the groups, and a family that already owns a block of its own — theSend_*/Broadcast_*runs, the///@ ExportEventdeclarations — keeps it where the file puts it. - Every standalone native module must be created as a complete
.h+.cpppair and both files must be registered in the owning build source list. A named subsystem/module must never be introduced as a lone header; keep the translation unit as the module anchor even when all current declarations are passive data and no out-of-line implementation is needed yet. Pure template/constexpr/umbrella headers are not standalone modules. - Order code top-down by importance and abstraction level: high-level entry points and orchestration first, secondary helpers and low-level details below, unless a nearby file has a stronger established ordering.
- A
.cppfunction earns a profiling zone by what it explains:FO_TRACE_ZONE(<Category>)as its first statement, followed by one blank line when the body continues. It goes on what frames the capture (main loop and tick steps), on units of work whose cost is non-trivial or varies, on what can block, and on rare expensive operations. It stays off accessors and trivial forwarders, small pure helpers, anything called per element in a hot loop, member-only constructors and destructors, the profiler’s own path (allocator, logging, stack traces, crash handling), functions that do not return during a capture, headers, lambdas and tests. The category is the subsystem the work belongs to, not necessarily the file’s; the categories are theFO_TRACE_COLOR_<Category>lines ofSource/Essentials/BasicCore.h, and a build compiles in only thoseFO_TRACE_CATEGORIESselects (every zone category by default; allocation tracking and log messages are the opt-inMemoryandLog, declared byFO_TRACE_OPT_IN_<Category>).FO_SCRIPT_APIscript-export functions (///@ ExportMethod,///@ EngineHook, and the other script-bound exports) take no zone — the generated binding that calls them opens a namedScriptzone — and neither do the file-local helpers only exports call. The full rules: profiling guide. - Separate semantically distinct stages inside functions and lambdas with a blank line: input/context validation, resource acquisition, ownership transfer, value conversion, callback invocation, and result handling each form their own group. Keep a declaration together with its immediate validation or initialization; do not compress several stages into one uninterrupted run. Match the surrounding code and review these groups manually: a formatter cannot infer them. Applies to native code and managed scripts.
- Surround every control-flow statement (
if/else,for/foreach,while/do,switch,try/catch, and similar constructs) with a blank line before and after the complete statement. Blank lines may be omitted only inside a consecutive run of the same construct (if+if,for+for, and so on). This does not require blank lines just inside the opening or closing brace. - Treat preprocessor directives as transparent to the blank-line rule: do not add or remove blank lines merely because code is enclosed by
#if/#else/#endif; apply spacing to the surrounding C++ blocks as if the directives were not there. - Keep a comment attached to the code block it describes below: put a blank line before the comment, but no blank line between the comment and that block. For control-flow spacing, the comment is part of the following block.
- Prefer
FO_VERIFY_AND_THROWover silently masking unexpected states. - The condition of a verification macro is a predicate, not an operation. Write the condition of
FO_VERIFY_AND_THROW,FO_VERIFY_AND_CONTINUE,FO_VERIFY_AND_RETURN,FO_VERIFY_AND_RETURN_VALUE,FO_STRONG_ASSERTandFO_BASIC_STRONG_ASSERTas if the macro were stripped like anassert: any work the rest of the function depends on — a write, a flush, a read that fills a buffer or advances a reader, an insertion checked through its.second, a file-system change, a jobRun(), a call that fills an out parameter — happens first into a named local, and the macro only reads the answer:bool written = _file.write(data) && _file.flush(); FO_VERIFY_AND_THROW(written, "Can't write resource patch", path);, neverFO_VERIFY_AND_THROW(_file.write(data) && _file.flush(), ...). The macros are never compiled out, so this is not about losing the call: an operation inside a check reads as a check and is skimmed past as one, and a check that acts cannot be moved, reworded or swapped for another variant without changing what the function does. See Docs/ExceptionSafety.md; embedding projects may gate the generic shapes with an audit tool (Last Frontier:Tools/CiChecks/check_verify_conditions.py). - Throw engine exceptions with a fixed message plus context arguments, never a pre-formatted string.
FO_DECLARE_EXCEPTIONexception types andFO_VERIFY_AND_THROWtake a constantstring_viewmessage followed by variadic context values; the base exception preserves the bare message and appends each value as its own- valueline. Writethrow SomeException("Fixed human-readable message", id, path, value);andFO_VERIFY_AND_THROW(cond, "Fixed message", ctx...);, notthrow SomeException(strex("... {} ...", value));. A constant message keeps every occurrence of the same failure identical, so failures group and sort by message uniqueness while the variable parts travel in the arguments — the same rule the script-sideverifymacro follows. Do not build the message withstrex/std::format/+from runtime values. - Never throw anything that is not derived from
std::exception, and treatcatch (...)as a defect handler, not an error handler. Every exception the engine raises comes from theFO_DECLARE_EXCEPTIONhierarchy or the standard library, so a non-std::exceptiontype walks past everycatch (const std::exception&)recovery path and arrives with nothing to report. Wherever acatch (...)sits beside acatch (const std::exception& ex), its entire body isFO_UNKNOWN_EXCEPTION();— which raisesStrongAssertationException, reports it, and terminates the process. Do not log it and continue, fabricate an"Unknown exception"message, count it as an ordinary failure, or rethrow it as a domain exception. The only places that may swallow instead are no-throw teardown/unwind paths (noexceptbodies,scope_exit/scope_failbodies,safe_calltargets, destructors, C-ABI callbacks) and the reporting/logging machinery itself, which must not re-enter while reporting. Full rule: Docs/en/contributing/coding-contracts/exception-safety.md §2.1. - Write for exception safety. A throw must never escape a function leaving a broken invariant. As you write or change engine code, derive the exception-safety level the body provides (
NoThrow/Strong/Basic/None) per Docs/en/contributing/coding-contracts/exception-safety.md (§5 disposition ladder, §8 levels): do fallible/validating work (throwingnumeric_cast, duplicate/collision guards) before the first observable mutation, insert into the authoritative store before any derived cache, and undo an early flag/counter/lock/handle mutation on unwind withscope_fail(noexcept body) or RAII on raw OS/third-party resources. Never aim forNone— it is a defect tier to fix, not to record. Allocation terminates on OOM (§1) so do not write allocation-only rollbacks, and entity create/destroy is throw-as-signal (Basicby design). If an embedding project maintains a per-function audit baseline, update that project-owned baseline in the same integration change; it is not normative engine evidence. - Use
numeric_castfor numeric conversions; do not usestatic_castfor numeric narrowing/widening unless the surrounding code has a specific established reason. - Use fixed-width types (
int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t,float32_t,float64_t,size_t) instead of bareint/floatin new engine code. - Do not use
autofor primitive values or simple obvious types such asstring,hstring,size_t, and the fixed-width aliases. - Do not put top-level
conston an automatic local when removing it leaves the type contract unchanged — writeint32_t count = GetCount();, notconst int32_t count = GetCount();. There is no engine-wide immutability-by-default rule, and parameters are out of scope. Constness that belongs to the value itself stays: aconstpointee (const Item* item), aconstreferent (const Item& item),constarray elements, andconstexpr. Top-levelconston a pointer (int32_t* const pointer) is removable and is diagnosed. A deliberate qualifier is kept with// FO_REDUNDANT_CONST_SUPPRESS: <reason>on the declaration or the line above it, and the reason is mandatory. See local variables; embedding projects gate this together with explicit simple local types and use-after-move (Last Frontier:Tools/LocalVariableValidator/,Tools/ExplicitLocalTypes/). - Use the engine smart-pointer vocabulary from
Source/Essentials/SmartPointers.h—ptr<T>/nptr<T>for borrows,unique_*/refcount_*for owners, engine-ownshared_ptr/weak_ptrviasafe_alloc::make_shared()— instead ofstd::smart pointers or bare rawT*. Raw pointers remain only at the documented ABI/low-level allowlist boundaries; inside a function, bind them to wrappers before ordinary engine work and unwrap with.get()only at the final handoff. A checkednptr<T>and every owner convert toptr<T>implicitly, so do not write.as_ptr()where that implicit conversion applies — aptr<T>parameter, member, typed local or return takes the value directly; spell.as_ptr()only where no conversion can happen (a deducedautolocal, overload or template deduction, a lambda capture, a value needed after its owner moves). See smart pointers; embedding projects may gate this with an audit tool (Last Frontier:Tools/SmartPointerAudit/). - Use the engine container aliases from
Source/Essentials/Containers.h, never theirstd::originals —string,wstring,vector,map,unordered_map,set,list,deque,stringstream,small_vector. Each follows the terminate-on-OOM contract instead of throwingstd::bad_alloc:stringandwstringare the enginebasic_stringfromSource/Essentials/StringObject.h, whose small-string buffer is theFO_STRING_INLINE_CAPACITYbuild option,dequeis the enginebasic_dequefromSource/Essentials/DequeObject.h, whose block size is a template parameter, and the rest are the standard containers instantiated onsafe_allocator. For allocation that cannot be expressed as a C++ container, usesafe_alloc: themake_*family for typed objects, and the raw tier (malloc_raw/calloc_raw/realloc_raw/free_raw,malloc_aligned_raw/free_aligned_raw) for third-party C-ABI hooks. Do not reach for baremalloc/free, and do not usestd::format/std::vformat/std::to_string, which materialise astd::allocatorstring — format into an existing buffer withstd::format_to/std::vformat_toinstead. Exceptions are limited to foreign ABI boundaries, the Essentials modules aboveMemorySystemin the include order, and standard types with no allocator parameter; each one is recorded with a written reason. See Essentials; embedding projects may gate this with an audit tool (Last Frontier:Tools/AllocatorAudit/). - Use the engine callable wrappers from
Source/Essentials/FunctionObjects.h, neverstd::function—function<Sig>(an alias ofmove_only_function<Sig>) is the default, andcopyable_function<Sig>is for a stored callable that is genuinely copied, such as one snapshotted during dispatch or handed to several owners. Both keep a small nothrow-movable target inside the wrapper, so an ordinary closure allocates nothing. When afunctionmember no longer compiles because something copies it, first check whether the copy should be astd::move; reach forcopyable_functiononly when the copy is the actual contract. The sole remainingstd::functionis theStackTrace.hscript-provider hook, which sits above the callable module in the Essentials order. See Essentials. - The engine
stringbehaves asstd::basic_string, so write it as you would the standard string. The one difference is the tunable inline buffer, which is a build option rather than a source-level choice. Three interop rules do not follow from the standard: text handed to a standard string stream is copied throughmake_stream_string, astd::filesystem::pathis built from an engine string withfs::make_path, andgetlinemust be called unqualified so ADL finds the engine overload. See Essentials. - Use
random_generatorfromSource/Essentials/RandomGenerator.h, neverstd::mt19937orstd::uniform_int_distribution. The standard engine costs 5000 bytes of state and 3.6 us to construct, but the deciding reason is that the standard distributions are implementation-defined: the same seed maps to different values on a Windows client and a Linux server.next()draws raw bits,next_below/next_between/next_normalizedare the engine’s own bounded draws and produce one sequence everywhere. See Essentials. - Sleep with
coarse_sleeporprecise_sleepfromSource/Essentials/Threading.h, neverstd::this_thread::sleep_for. The standard call rounds up to the OS timer tick, so a 50 us request parks for 15 ms on a default Windows configuration.coarse_sleepparks and lands within half a millisecond at no CPU cost;precise_sleephits the deadline to within microseconds by spinning the last millisecond, and is for waits whose duration was chosen on purpose. See Essentials. - Call the operating system through
winapi::orposix::, never directly.Source/Essentials/WinApi.*andPosix.*are the only places<Windows.h>and the POSIX headers are included, and their boundary carries engine types rather than OS ones.Platformdispatches between them; add a wrapper there instead of reaching for the OS from a consumer. Three implementations below the modules in the Essentials order keep their own calls because the layering leaves no alternative (BasicCore.cpp,BaseLogging.cpp,StringUtils.cpp), and two more are OS wrappers in their own right rather than consumers (NetSockets.*,ServerServiceApp.cpp). See Essentials. - A setting is read, never written.
Source/Common/Settings.incdeclares every entry with one macro,SETTING(<type>, <Group>, <Name>, <default>), which makes the memberconst;SetRuntimeSetting()rejects a write to any of them, andManagedScriptBakeremits the script-sideSettings.Group.Nameas a get-only property. A value that changes while the game runs therefore cannot live here — it belongs to the system that owns it, reached through that system’s API (MapView::SetVisibleLayers,AudioManager::SetMusicVolume,Application::ScreenState, …).BakerTests::OverrideSettingis the one deliberate exception and is confined to test code. See configuration and data sources. - A setting is addressed through its group.
Source/Common/Settings.incdeclares a group asSETTING_GROUP(<Group>, <virtual bases>)and holds its settings in a nested aggregate named after the group, so engine code readssettings.Network.ServerPortand a config key, a command-line override andGetRuntimeSetting()/SetRuntimeSetting()all spell itNetwork.ServerPort. A bareNamenames no engine setting anywhere — it lands in custom settings, which the config baker reports asUnknown setting— and that is what leaves two groups free to declare the same short name. A///@ Settingdeclaration carries the wholeGroup.Nametoo. See configuration and data sources. - Task-language definition: in requirements, reviews, and conversation about engine code, informal wording such as “pointer”, “raw pointer”, “borrowed pointer”, “указатель”, or “сырой указатель” means a non-owning engine borrow, not permission to write a bare C++
T*. Useptr<T>when presence is guaranteed andnptr<T>when absence is valid. A bareT*is authorized only when the request explicitly requires the literal C++T*type and identifies a documented ABI/low-level boundary; if either condition is missing, the wrapper rule wins. - For
Entityand derived types use pointers, not references. - Prefer
staticfree functions for file-local helpers instead of unnamed namespaces. - Do not add
staticvariables for hidden state or caches; see the Behavioral Rules above for the narrow allowed uses ofstatic. - Add
constandnoexceptwhere they express the semantic contract; do not add them mechanically everywhere possible. Fornoexceptspecifically: aNoThrowexception-safety classification alone never justifies the keyword — the ES baseline records the current fact, whilenoexceptis a contract that blocks future legitimate validating throws. Reserve it for move operations/swap, teardown/unwind-path callables (scope_exit/scope_failbodies,safe_calltargets), C-ABI callbacks, and documented no-throw primitives; see Docs/en/contributing/coding-contracts/exception-safety.md §8. - Use
ignore_unused(...)only for variables/objects; for an intentionally ignored function-call result, write(void)FunctionCall(...). - For C++ string/text construction and parsing, prefer existing engine helpers such as
strexandstrvexwhen they make formatting or token handling clearer. If the helper surface is missing a repeated string-formatting operation, add a reusable helper in the appropriate engine utility layer instead of open-coding ad hoc parsing/formatting at call sites. - Gate conditional compilation on our own (
FO_*/ project) macros with#if FOO/#if !FOO, never#ifdef/#ifndef: every such macro is mandatorily#defined to0or1, so its definedness must never carry meaning — only its value does. - Keep engine-owned source inventories unconditional. Add every engine-owned
.h/.cpppair to its normal CMake source list even when the feature is optional; the files themselves own the#if FO_*guard. Do not mirror a feature toggle aroundAppendList(...), and do not create a second feature-onlyAppendListfor the guarded module. Likewise, do not wrap#includedirectives for self-guarded engine headers in a standalone feature conditional: include them normally and keep the feature guard around declarations/definitions that need it. Conditional third-party targets, link dependencies, and third-party headers whose include paths only exist with the feature may remain gated. - Include layering: non-Essentials engine code must consume Essentials modules through
Common.h, not by including individualSource/Essentials/*.hheaders directly. STL headers are centralized throughBasicCore.h; add or adjust standard-library includes there instead of including<...>headers from higher layers. - Essentials layering is strict. The
Source/Essentials/Essentials.haggregate lists Essentials headers in include order — each Essentials header (and its.cpp) may only depend on headers listed above it in that block. This is a compile- and link-time rule: declaring a function in an early header but defining it in a later module is still a forbidden reverse dependency, even when no downward#includeappears. Public declarations are implemented in their owning module (or an earlier one), andBuildTools/tests/test_essentials_layering.pyenforces both direct-include direction and definition ownership for the namespace-level APIs a header declares. If a low-layer module needs information that physically lives in a higher layer, expose only what the lower layer can produce on its own or take the higher-layer value as a parameter — do not back-channel the include and do not reorder the block to work around it. - Never
#includean STL header outside the engine’sSource/Essentials/BasicCore.h. The standard library is not included per-file; the whole STL surface in use is included centrally throughBasicCore.h. If a standard-library facility is missing, add its include toBasicCore.h, never to the consuming file. - Iterating server-side entities while events may fire: any script-visible event (e.g.
OnItemOnMapAppeared.Fire(...),OnCritterDisappeared.Fire(...)) can re-enter scripts and mutate world state — including destroying the very entity being iterated. The convention is: (1) snapshot the iteration set withcopy_hold_ref(...)so each element is held by ref-count for the duration of the loop; (2) re-validate inside the loop withif (cr->IsDestroyed()) continue;(and after each event for any other entity you keep working with); (3) fire scripted events at the end of the unit of work, after non-revocable state changes are committed. - Keep edited source files ending with exactly one trailing blank line.
- A method the native backend resolves through Mono (
[CallableByEngine]) isinternal. Native lookup (mono_class_get_method_from_name) finds non-public members;privatelooks unused toIDE0051because nothing in C# calls it. Helpers that only their own type uses stayprivate. In CoreScripts an embedding project’s analyzer may gate this (Last Frontier:LF0015);ManagedHostis compiled into its own assembly, which those analyzers never see, so there the rule is held by review alone. - Managed C# member names are PascalCase, whatever their access — private fields included (
Continuations, not_continuations); underscores may separate segments that each start with a capital letter or a digit, never lead or trail a name. A field that must stay a field beside a same-named property gets a name of its own (RecordedGloballybehindGlobalCount). Two surfaces keep a different spelling on purpose: the script-visible value types mirror the native API in lower case (ipos.x,hstr(),timespan.milliseconds), and codeManagedScriptBakergenerates keeps_-prefixed internals (_entityPtr,__event_OnStart) so no property an embedding project declares can collide with them. - Keep docs reusable: describe engine behavior first; mention Last Frontier only as an embedding-project example, never as an engine-doc dependency or validation owner.
- Keep
README.mdhuman-oriented andAGENTS.mdAI-oriented.CLAUDE.mdis intentionally only a pointer to@AGENTS.md.