[{"content":"XII Engine XII is a modular, cross-platform engine developed in C++23 for high-performance, real-time applications. While real-time rendering is one of its central capabilities, XII is designed to support a broader range of applications, including simulations, scientific visualization, interactive systems, and other performance-sensitive workloads.\nAs an engine grows, maintaining consistency across its codebase becomes increasingly important. With multiple subsystems, platform-specific implementations, shared interfaces, and contributors working on different parts of the project, seemingly small implementation decisions can have consequences far beyond the files being modified.\nThis raises an important question: How do we establish engineering practices that preserve correctness, performance, and maintainability without unnecessarily restricting implementation choices?\nTo address this, we have introduced a comprehensive set of C++ Coding Standards and Engineering Guidelines for XII Engine.\nThese guidelines extend beyond traditional formatting and naming conventions. They establish a common engineering philosophy for designing interfaces, managing resources, handling failures, organizing implementations, and making architectural decisions throughout the engine.\nThe emphasis is on several fundamental principles:\nCorrectness before cleverness: Code should preserve documented invariants and communicate its behavior clearly. Explicit ownership and lifetime: Resource ownership, borrowing, destruction, and invalidation must be understandable and enforceable. Cohesive interfaces: Abstractions should represent meaningful responsibilities without exposing unnecessary implementation details. Cross-platform consistency: Implementations must respect shared contracts across supported platforms, compilers, and backends. Evidence-based optimization: Performance decisions should reflect representative workloads and measurable bottlenecks rather than speculation. Predictable failure handling: Expected operational failures should be communicated explicitly, without relying on C++ exceptions. Modern C++23 practices: Language features should improve clarity, safety, or efficiency while remaining compatible with XII\u0026rsquo;s supported toolchains. An important consideration throughout the document is that coding standards should not become a substitute for engineering judgment. Not every abstraction is necessary, not every operation requires another layer of validation, and not every performance optimization justifies additional complexity.\nInstead, the goal is to make routine engineering decisions consistent while allowing carefully justified exceptions when the requirements of a particular subsystem demand them.\nThe standards also introduce contributor and reviewer checklists, helping establish a consistent approach to evaluating correctness, ownership, compatibility, testing, and performance during development.\nUltimately, this initiative is about establishing a foundation for sustainable engineering as XII continues to evolve. Code should not merely work today; its responsibilities, assumptions, and constraints should remain understandable to the people who build upon it tomorrow.\nThe complete guidelines are available in the repository:\nXII Engine - C++ Coding Standards and Engineering Guidelines\n1. Core Principles 1.1 Correctness Before Cleverness Code MUST express the intended semantics and maintain its documented invariants on all supported platforms. Prefer straightforward implementations over clever solutions that obscure ownership, ordering, lifetime, or failure behavior. Avoid introducing generic abstractions without a concrete consumer and a clearly defined responsibility. Do not introduce additional states, registration layers, configuration enums, or caches without a demonstrated need. Make invalid states difficult to represent. Validate untrusted inputs and enforce preconditions at system boundaries. Operations that can legitimately fail MUST expose their failure through an explicit mechanism, such as xiiResult. XII does not use C++ exceptions for ordinary error handling. Callers MUST NOT silently discard failures that affect correctness. Prefer:\nxiiResult LoadConfiguration(xiiStringView sPath) { xiiFileReader file; if (file.Open(sPath).Failed()) return XII_FAILURE; return ParseConfiguration(file); } Avoid:\nvoid LoadConfiguration(xiiStringView sPath) { xiiFileReader file; file.Open(sPath); // Failure is ignored. ParseConfiguration(file); // May operate on an unopened file. } The preferred implementation makes the operation\u0026rsquo;s success or failure observable to the caller and prevents parsing when opening the file fails. It also propagates parsing failures rather than claiming success unconditionally.\nExpected Failures vs. Contract Violations Not every invalid argument warrants a xiiResult.\nA distinction MUST be maintained between expected runtime failures and programming errors:\nExpected runtime failures arise during otherwise valid operations and must be handled explicitly. Examples include inaccessible files, malformed external data, unavailable network services, and resource initialization failures. Contract violations indicate incorrect use of an API or a violated internal invariant. These should be prevented through API design and detected using assertions or other appropriate contract-enforcement mechanisms. For example, if a function requires a nonzero interval as part of its documented contract, and its callers are entirely under engine control, an assertion is more appropriate:\nvoid SetFrameInterval(xiiUInt32 uiInterval) { XII_ASSERT_DEV(uiInterval \u0026gt; 0U, \u0026#34;Frame interval must be greater than zero.\u0026#34;); m_uiFrameInterval = uiInterval; } If the interval originates from an external configuration file, the value should instead be validated at the configuration boundary, where invalid input can be reported as a recoverable failure.\nError-handling requirements:\nUse xiiResult for operations where success or failure must be communicated to the caller. Use assertions to detect violations of documented preconditions and internal invariants. Do not use xiiResult as a substitute for enforcing API contracts. Do not return XII_SUCCESS unless the operation has fulfilled its documented success conditions. Do not silently ignore a failed operation when subsequent execution depends on its success. Propagate failures to the appropriate boundary unless the current function can meaningfully handle or recover from them. Do not use xiiResult when callers require structured error information beyond success or failure. Use an appropriate result type or explicit error output instead. C++ exceptions MUST NOT be used for ordinary error handling. Exceptions are permitted only where unavoidable within deeply isolated platform-specific implementations and MUST NOT propagate across engine API boundaries. Guiding principle:\nExpected runtime failures are values, not exceptions. Programming errors are contract violations, not ordinary runtime failures.\n1.2 Make Ownership and Lifetime Visible Ownership, lifetime, and access semantics MUST be explicit and consistent. A component should make it possible to determine who owns a resource, how long it remains valid, and under what conditions it may be accessed.\nA reviewer should be able to answer:\nCreation: Who creates or acquires the object or resource? Ownership: Who is responsible for managing its lifetime? Destruction: Who destroys or releases it, and when? Lifetime: Can it outlive the object, subsystem, or context on which it depends? Invalidation: What invalidates its pointers, references, views, iterators, handles, or GPU resources? Concurrency: Which threads may access or modify it, and what synchronization is required? Names, types, and interfaces SHOULD communicate these relationships without requiring callers to inspect implementation details.\nOwnership Requirements Every dynamically managed object or resource MUST have a clearly defined owner or ownership policy. Prefer automatic storage and deterministic destruction where practical. Use RAII to associate resource acquisition with cleanup whenever ownership can be represented by object lifetime. Use xiiUniquePtr for exclusive dynamic ownership and xiiSharedPtr only when shared ownership is genuinely necessary. Use non-owning pointers, references, spans, and views for borrowed access. Their lifetime MUST NOT exceed that of the referenced storage. Use opaque, generation-checked handles for independently managed objects whose lifetime is controlled by a registry, manager, or subsystem. Do not expose owning raw pointers without an explicit and documented ownership-transfer contract. Avoid implicit ownership transfer, hidden lifetime extension, and unnecessary shared ownership. Destruction and release operations MUST preserve the owning component\u0026rsquo;s invariants, including when an operation fails. Prefer:\nclass xiiDocument { public: explicit xiiDocument(xiiUniquePtr\u0026lt;xiiDocumentData\u0026gt;\u0026amp;\u0026amp; pData) : m_pData(std::move(pData)) { } // Borrowed access. The reference is valid only while // the document and its data remain alive. const xiiDocumentData\u0026amp; GetData() const { return *m_pData; } private: // Exclusive ownership with deterministic destruction. xiiUniquePtr\u0026lt;xiiDocumentData\u0026gt; m_pData; }; Avoid:\nclass xiiDocument { public: explicit xiiDocument(xiiDocumentData* pData) : m_pData(pData) { } // Ownership and lifetime are ambiguous. xiiDocumentData* GetData() { return m_pData; } private: xiiDocumentData* m_pData; }; The preferred implementation makes exclusive ownership explicit through the type system. The document controls the lifetime of its data, and consumers receive borrowed access without acquiring ownership.\nThe avoided implementation leaves callers uncertain whether the pointer is owned, borrowed, transferred, or expected to be deleted elsewhere.\nHandles and Resource Lifetimes Handles are appropriate when resources are managed independently of the objects that reference them.\nHandles MUST NOT imply ownership unless explicitly documented. A handle MUST be validated before accessing its associated resource whenever its validity cannot be guaranteed by the calling contract. Reusable handle slots SHOULD employ generation counters or equivalent mechanisms to detect stale references. Resource destruction MUST invalidate the corresponding logical handle according to the subsystem\u0026rsquo;s documented lifetime rules. Do not expose internal resource pointers when a handle-based interface is required to preserve lifetime or synchronization guarantees. Avoid using handles for objects whose lifetime is already clearly and safely expressed through ordinary value or reference semantics. A generation-checked handle protects against stale logical references but does not automatically guarantee that the underlying resource remains alive during concurrent access.\nLikewise, GPU resource lifetime must account for asynchronous execution. Releasing a logical handle does not necessarily mean the underlying allocation can be destroyed immediately if submitted GPU work still references it.\nBorrowed Access and Invalidation Non-owning access MUST have a clearly established validity period.\nReturned pointers, references, spans, and views MUST NOT outlive their backing storage. Container mutation, relocation, resource replacement, and subsystem shutdown MUST be considered when establishing validity guarantees. APIs SHOULD avoid returning references to internal storage when subsequent operations may invalidate them unexpectedly. Do not retain borrowed references across asynchronous operations unless their lifetime is explicitly guaranteed. Borrowed access MUST NOT be mistaken for thread-safe access. Synchronization requirements remain independent of ownership. Concurrency and Destruction Ownership and thread safety are separate concerns.\nShared ownership does not imply synchronized access to mutable state. Components MUST document concurrent access restrictions when they cannot be inferred from the API contract. Destruction MUST NOT race with active operations that access the object or resource. Asynchronous operations MUST establish how their dependencies remain valid until completion. Deferred destruction SHOULD be used when resources remain referenced by queued operations, background tasks, or GPU submissions. Synchronization and lifetime guarantees MUST be enforced by the responsible subsystem rather than delegated implicitly to unrelated callers. Guiding principle:\nOwnership determines who is responsible for a resource. Lifetime determines when it is valid. Access rules determine who may use it and under what conditions. These contracts must remain explicit, independently enforceable, and consistent throughout XII.\n1.3 Small Interfaces, Cohesive Implementations Classes, services, and modules MUST have clearly defined responsibilities and expose only the functionality required by their consumers. Public interfaces should describe supported operations rather than reveal implementation details.\nFollow the principle of high cohesion and low coupling. Group functionality that shares a coherent responsibility, and separate functionality that changes for unrelated reasons. Expose the smallest public API that satisfies demonstrated use cases. Do not add methods, overloads, or extension points for hypothetical requirements. Keep internal storage, data structures, synchronization mechanisms, and implementation-specific state private unless explicitly required by a documented contract. Prefer intention-revealing operations over exposing mutable internal state. Maintain invariants within the component that owns them. Callers MUST NOT be required to understand or preserve another component\u0026rsquo;s internal invariants. Avoid unnecessary manager classes, service layers, registries, and wrapper abstractions. Introduce them only when they establish meaningful ownership, coordination, or architectural boundaries. Do not fragment cohesive functionality into multiple classes merely to satisfy an arbitrary class-size or method-count restriction. Prefer stable interfaces that permit implementations to evolve without requiring changes throughout the codebase. Prefer:\nclass xiiResourceRegistry { public: [[nodiscard]] xiiResourceHandle RegisterResource(xiiStringView sPath); void UnregisterResource(xiiResourceHandle hResource); [[nodiscard]] bool IsRegistered(xiiResourceHandle hResource) const; private: xiiHashTable\u0026lt;xiiResourceHandle, ResourceEntry\u0026gt; m_Resources; }; Avoid:\nclass xiiResourceRegistry { public: // Exposes implementation details and permits external mutation. xiiHashTable\u0026lt;xiiResourceHandle, ResourceEntry\u0026gt;\u0026amp; GetResources(); // Callers must understand and maintain internal invariants. void RebuildResourceIndices(); }; The preferred interface allows the registry to control resource registration, removal, and lookup without exposing its underlying container or requiring callers to maintain its internal consistency.\nThe implementation can subsequently replace its hash table, introduce synchronization, or change its indexing strategy without affecting consumers.\nAbstraction Boundaries An abstraction should exist because it establishes a useful boundary, not merely because it reduces the apparent complexity of an individual implementation.\nBefore introducing a new interface, class, or service, consider:\nResponsibility: Does the abstraction own a coherent responsibility? Consumers: Is there a concrete consumer that benefits from the abstraction? Invariants: Does the abstraction protect meaningful invariants or ownership boundaries? Coupling: Does it reduce dependencies, or merely relocate them? Extensibility: Is extension genuinely required, or is the abstraction speculative? Complexity: Does the abstraction simplify the overall system rather than only the code immediately surrounding it? A small public interface does not necessarily imply a small implementation. Complex internal algorithms and data structures are acceptable when they are necessary to fulfill a clearly defined responsibility.\nConversely, a class with few methods is not automatically cohesive if those methods address unrelated concerns.\nGuiding principle:\nExpose capabilities, not implementation details. Abstractions should protect invariants, clarify ownership, and reduce coupling without introducing unnecessary indirection.\n1.4 Think Across Engine Boundaries XII is a modular, multi-platform engine whose components operate within a shared architectural and runtime environment. Changes to one subsystem can affect other modules, platform implementations, plugins, tools, and applications, even when those dependencies are not immediately visible.\nChanges MUST be evaluated against the contracts of the affected subsystems, not merely the compilation or correctness of the modified source file.\nConsider the full dependency chain before modifying shared interfaces, data structures, resource representations, or execution semantics. Preserve module boundaries and dependency direction. Lower-level modules MUST NOT acquire dependencies on higher-level modules merely to accommodate a local implementation. Prefer changes at the layer that owns the relevant responsibility. Do not introduce workarounds in dependent modules when the underlying issue belongs to a shared abstraction. Avoid coupling platform-independent functionality to a particular operating system, graphics API, compiler, or hardware architecture. Changes to public interfaces MUST account for existing consumers, including plugins, tools, scripting interfaces, and external integrations. Changes to shared behavior MUST preserve documented invariants across all supported platforms and configurations. Do not introduce cross-module dependencies, initialization requirements, or global state without a demonstrated architectural need. Compatibility Requirements Compatibility extends beyond source-level compilation. Depending on the affected subsystem, changes may influence binary interfaces, serialized data, runtime behavior, resource lifetimes, and externally consumed formats.\nThe following changes MUST receive compatibility consideration:\nPublic headers and exported symbols: Function signatures, symbol visibility, type layouts, calling conventions, and binary compatibility. Enums and flags: Numeric values, underlying types, bit assignments, serialization mappings, and assumptions made by external consumers. Handles and resource identifiers: Validity rules, generation semantics, ownership, lifetime, and compatibility with existing resource managers. Reflection and type registration: Type names, properties, metadata, inheritance relationships, versioning, and registration behavior. Serialization and binary formats: Schema changes, field ordering, versioning, backward compatibility, and migration requirements. Shaders and GPU interfaces: Resource bindings, constant-buffer layouts, shader permutations, pipeline interfaces, and backend-specific restrictions. Plugin interfaces: Loading, initialization, shutdown, exported functionality, and compatibility across module boundaries. Threading and synchronization: Execution ordering, thread affinity, shared-state access, and CPU/GPU synchronization guarantees. Build configuration: Conditional compilation, feature flags, compiler differences, static and dynamic linking, and platform-specific dependencies. Compatibility requirements MUST be assessed according to the documented contract of the affected interface. Not every internal implementation change requires backward compatibility, but changes to persistent formats or externally consumed interfaces must not silently invalidate existing consumers.\nPlatform and Backend Independence Platform-independent modules MUST express behavior through abstractions that can be implemented consistently across supported backends.\nDo not expose backend-specific types through platform-independent interfaces unless the interface explicitly represents a native interoperability boundary. Do not assume that behavior guaranteed by one platform or graphics API is universally supported. Backend implementations MUST preserve the common interface\u0026rsquo;s documented semantics or explicitly report unsupported functionality. Optional functionality SHOULD be represented through capability queries or existing feature mechanisms rather than assumptions about the active backend. Do not introduce platform-specific branches into shared code when the behavior belongs in a platform implementation. Consider differences in memory alignment, endianness, pointer size, synchronization, filesystem behavior, and compiler support where relevant. An implementation that works on one backend is not necessarily correct across the engine.\nDependency and Change Propagation Changes SHOULD remain localized to the component that owns the affected responsibility.\nWhen a modification requires updates across multiple modules, determine whether the change represents:\nA legitimate evolution of a shared contract. An implementation detail that has leaked across abstraction boundaries. An unnecessary dependency or architectural coupling. A missing responsibility in an existing subsystem. Do not introduce additional abstraction layers solely to prevent every change from propagating. Some coordinated changes are legitimate and preferable to maintaining unnecessary compatibility mechanisms.\nConversely, repeated changes across unrelated consumers may indicate that an implementation detail has been exposed too broadly.\nValidation Across Boundaries Validation MUST reflect the scope and potential impact of the change.\nCompile and test the directly affected module and its relevant dependents. Verify supported platform and backend configurations when modifying shared or platform-sensitive functionality. Test affected resource lifetimes, initialization sequences, shutdown paths, and error-handling behavior. Validate reflection and serialization compatibility when modifying reflected types or persistent data. Verify shader compilation, resource binding compatibility, and pipeline behavior when changing GPU-facing interfaces. Consider existing plugins, tools, and applications that depend on the modified contract. Update relevant tests and documentation whenever observable behavior or public contracts change. A successful local build is necessary but not sufficient evidence of correctness for changes that affect shared contracts.\nWhere complete cross-platform or cross-backend validation is impractical, the limitations SHOULD be documented, and the unverified configurations identified during review.\nGuiding principle:\nA change is correct only when it preserves the intended contracts of every affected boundary. Local correctness must not come at the expense of system-wide consistency, portability, or maintainability.\n1.5 Optimize From Evidence Performance is a fundamental requirement of XII, but optimization MUST be guided by measurable evidence, representative workloads, and an understanding of the underlying system.\nCorrectness, maintainability, and performance are complementary engineering goals. Code should be designed efficiently from the outset without introducing unnecessary complexity in pursuit of unverified performance improvements.\nEstablish correctness before optimizing. An implementation that produces incorrect results more quickly is not an improvement. Prefer efficient algorithms, appropriate data structures, and predictable memory access patterns over low-level optimizations with negligible measurable benefit. Use profiling data, allocation measurements, benchmarks, and representative workloads to identify genuine bottlenecks. Do not sacrifice clarity, safety, or architectural boundaries for speculative performance gains. Conversely, do not disregard allocations, virtual dispatch, cache misses, synchronization overhead, or unnecessary data movement in demonstrated hot paths. Consider performance across the entire operation rather than optimizing isolated instructions without accounting for their broader effects. Performance-sensitive implementations SHOULD document non-obvious optimization decisions and the assumptions on which they depend. Optimizations MUST NOT introduce undefined behavior, violate ownership contracts, or compromise correctness across supported platforms. Algorithmic Efficiency Before Micro-Optimization Algorithmic complexity and data representation generally have greater consequences than isolated instruction-level improvements.\nSelect algorithms based on expected input sizes, access patterns, and computational requirements. Consider both asymptotic complexity and practical costs, including allocation overhead, memory locality, branching, and synchronization. Prefer contiguous storage when access patterns benefit from spatial locality and stable addresses are not required. Avoid repeated computations, allocations, and conversions when their results can be reused safely and meaningfully. Do not introduce caches without considering invalidation, memory consumption, synchronization, and maintenance costs. Avoid unnecessary abstraction overhead in performance-critical paths, but do not assume that abstraction inherently prevents optimization. Prefer:\nvoid UpdateParticles(xiiArrayPtr\u0026lt;Particle\u0026gt; particles, float fDeltaTime) { for (Particle\u0026amp; particle : particles) { particle.m_vPosition += particle.m_vVelocity * fDeltaTime; } } Avoid:\nvoid UpdateParticles(xiiArrayPtr\u0026lt;Particle*\u0026gt; particles, float fDeltaTime) { for (Particle* pParticle : particles) { // Unnecessary indirection if particles can be stored contiguously. pParticle-\u0026gt;m_vPosition += pParticle-\u0026gt;m_vVelocity * fDeltaTime; } } When particles have value semantics and do not require stable individual addresses, contiguous storage can improve memory locality, reduce allocation overhead, and simplify ownership.\nPointer-based storage remains appropriate when object identity, polymorphism, stable addresses, or independent lifetimes are required. The preferred representation depends on the actual contract and workload.\nMemory Allocation and Data Locality Memory management is an important performance consideration, particularly in frequently executed systems.\nAvoid unnecessary dynamic allocations in established hot paths. Prefer reusing existing storage when doing so does not complicate ownership or introduce excessive retained memory. Reserve container capacity when the expected element count is known and repeated growth would be costly. Consider cache locality, memory fragmentation, alignment, and access patterns when selecting data structures. Avoid unnecessary copies of large objects, but do not replace inexpensive value semantics with pointer indirection without justification. Prefer stack allocation for small, bounded temporary objects when their lifetime and size make it appropriate. Use custom allocators, memory pools, or arenas only when their benefits justify the additional complexity and lifetime constraints. Allocation count alone is not a sufficient performance metric. Allocation size, frequency, lifetime, contention, and locality can be equally important.\nDispatch and Abstraction Costs Abstractions SHOULD be evaluated according to their actual runtime costs rather than assumptions about their implementation.\nVirtual dispatch is acceptable when runtime polymorphism serves a meaningful architectural purpose. Avoid repeated indirect calls in proven hot loops when a simpler dispatch strategy provides a measurable improvement. Prefer compile-time dispatch where the set of operations is known and the resulting code remains manageable. Do not replace clear interfaces with complex template machinery solely to eliminate unmeasured dispatch overhead. Consider the effects of inlining, instruction-cache pressure, code size, and compiler optimization when evaluating alternatives. Eliminating an individual virtual call does not necessarily improve overall performance if the surrounding computation is dominated by memory access, synchronization, or other costs.\nConcurrency and Synchronization Parallel execution MUST be evaluated in terms of useful throughput, scheduling overhead, and synchronization requirements.\nDo not assume that additional threads or tasks automatically improve performance. Minimize unnecessary contention, blocking, and shared mutable state in performance-critical systems. Prefer clearly defined ownership and data partitioning over introducing synchronization around poorly structured shared state. Consider lock contention, atomic operations, memory ordering, false sharing, and task scheduling overhead where measurements indicate their relevance. Avoid excessively fine-grained parallelism when scheduling and coordination costs exceed useful computation. Synchronization optimizations MUST preserve the required memory-ordering and visibility guarantees. Do not introduce lock-free algorithms solely because they appear faster. Their correctness and performance must be demonstrated under representative contention. CPU and GPU Performance Graphics and heterogeneous computing workloads require consideration of both CPU and GPU execution costs.\nDistinguish CPU-side submission overhead from GPU execution time when identifying rendering bottlenecks. Measure synchronization stalls, resource transitions, memory transfers, and command submission overhead where applicable. Avoid unnecessary CPU/GPU synchronization, particularly operations that force either processor to wait for work that could otherwise proceed asynchronously. Consider resource lifetime, memory residency, bandwidth, occupancy, and execution dependencies when optimizing GPU workloads. Do not assume that reducing the number of API calls or render passes necessarily improves performance. Evaluate optimizations across supported graphics backends and representative hardware configurations when backend behavior may differ. Reducing CPU overhead is not necessarily beneficial if the change increases GPU execution time, memory consumption, synchronization cost, or frame latency.\nBenchmarking and Profiling Performance claims SHOULD be supported by reproducible measurements.\nEstablish a baseline before implementing a performance optimization. Profile representative workloads rather than relying exclusively on synthetic microbenchmarks. Use appropriate tools to distinguish CPU execution time, GPU execution time, memory usage, allocation behavior, and synchronization overhead. Measure both typical and worst-case behavior when latency or frame-time consistency is important. Account for warm-up effects, caching, compiler optimization settings, background activity, and measurement variability. Compare results under equivalent conditions, including build configuration, hardware, input data, and execution environment. Repeat measurements sufficiently to distinguish meaningful improvements from noise. Include correctness validation to ensure the optimized implementation preserves the original behavior. For real-time workloads, average execution time alone is insufficient. Frame-time variance, latency spikes, and worst-case execution behavior may be more important than peak throughput.\nOptimization Trade-offs Performance optimizations frequently introduce trade-offs between execution time, memory consumption, code size, complexity, and portability.\nBefore accepting a non-trivial optimization, consider:\nEvidence: Has a measurable bottleneck been identified? Impact: Does the optimization meaningfully improve the target workload? Correctness: Are all documented invariants and observable semantics preserved? Complexity: Is the additional implementation and maintenance cost justified? Portability: Does the improvement hold across the relevant platforms and hardware? Regression risk: Could the optimization degrade other workloads or execution paths? Verification: Can the performance improvement be reproduced and tested? An optimization that substantially complicates an implementation for an insignificant improvement SHOULD be rejected unless the improvement satisfies a documented performance requirement.\nConversely, additional implementation complexity may be justified when measurements demonstrate a meaningful improvement in a critical subsystem.\nGuiding principle:\nDesign for efficiency, measure before optimizing, and preserve correctness throughout. Performance improvements should solve demonstrated problems rather than anticipated ones.\n2. Source Layout and Formatting 2.1 Formatting Authority Consistent source formatting is essential for readability, maintainability, and effective code review. XII establishes formatting rules through repository-level configuration files to ensure that source code remains uniform across contributors, development environments, and supported platforms.\nThe root .clang-format file is the authoritative source for C++ formatting rules that can be expressed by ClangFormat. The repository\u0026rsquo;s .editorconfig governs applicable editor settings, including indentation, whitespace, line endings, and character encoding.\nContributors MUST NOT introduce personal formatting conventions that conflict with these configurations.\nFormatting Requirements Modified C++ source code MUST be formatted using the repository\u0026rsquo;s configured ClangFormat rules and compatible formatter version. The repository\u0026rsquo;s .clang-format configuration MUST NOT be overridden through personal editor settings or alternative formatting configurations. Formatting MUST remain consistent regardless of the editor, IDE, operating system, or development environment used. Contributors MUST NOT manually introduce alignment, indentation, or spacing conventions that conflict with the configured formatter. Existing brace placement, declaration layout, spacing, and naming conventions SHOULD be preserved where they are not governed by automated formatting. Formatting changes MUST NOT alter program semantics, preprocessor behavior, or documented interfaces. Source files MUST NOT contain trailing whitespace, inconsistent indentation, unintended mixed line endings, or unresolved merge conflict markers. Source text SHOULD use UTF-8 encoding unless a specific file format, platform interface, or toolchain requires otherwise. File encodings and line-ending conventions MUST NOT be changed without considering repository configuration and tooling compatibility. Scope of Formatting Changes Formatting changes MUST remain proportional to the scope of the work being performed.\nFunctional pull requests MUST NOT include unrelated repository-wide or mass formatting changes. Contributors SHOULD format only the files or code regions affected by their modifications when formatting an entire file would introduce substantial unrelated changes. Existing formatting inconsistencies outside the scope of a change SHOULD be addressed separately unless correcting them is necessary for the current modification. Dedicated formatting changes MUST be separated from functional changes whenever practical. Formatting-only changes MUST NOT intentionally modify program behavior. Keeping formatting changes separate from functional changes reduces review noise, preserves meaningful version-control history, and simplifies regression analysis.\nAutomated Formatting and Tooling Automated formatting SHOULD be integrated into the development workflow to reduce manual effort and prevent inconsistencies.\nEditors and IDEs SHOULD be configured to discover and use the repository\u0026rsquo;s .clang-format and .editorconfig files. Contributors SHOULD use automated formatting before submitting changes for review. Continuous integration MAY enforce formatting compliance using the repository\u0026rsquo;s approved formatter configuration. Automated formatting tools MUST NOT silently rewrite unrelated source files as part of a functional change. Formatter upgrades or modifications to formatting configuration MUST be reviewed independently because they may affect a substantial portion of the codebase. Generated source files SHOULD be formatted consistently when supported by their generators. Contributors MUST NOT manually format generated output when those changes would be overwritten by regeneration. Where automated formatting cannot express a particular convention, contributors SHOULD follow the established style of the surrounding code.\nFormatting Exceptions Certain source constructs may require deliberate formatting to preserve readability, compatibility, or tool-generated structure.\nExamples include:\nPreprocessor macros whose formatting affects continuation or expansion behavior. Generated code whose layout is controlled by an external generator. Platform-specific declarations requiring prescribed syntax or layout. Embedded source languages, shader code, or structured data that follow separate formatting rules. Tables or aligned declarations where automated formatting would significantly reduce readability. Exceptions MUST be narrowly scoped and justified by a concrete technical requirement.\nClangFormat suppression directives SHOULD NOT be introduced merely to preserve personal formatting preferences. Where suppression is necessary, it SHOULD encompass the smallest practical region.\nGuiding principle:\nFormatting is a repository-wide contract, not an individual preference. Automate consistency, minimize unrelated changes, and keep code reviews focused on semantics rather than presentation.\n2.2 Braces and Indentation Consistent brace placement and indentation are essential for expressing control flow clearly and preventing ambiguity. XII follows Allman-style braces for class definitions, function definitions, and multi-line control blocks, with indentation and spacing governed by the repository\u0026rsquo;s .clang-format configuration.\nBraces and indentation MUST make the logical structure of the code immediately apparent without requiring readers to infer statement ownership.\nGeneral Requirements Class, struct, namespace, function, and multi-line control-flow definitions MUST follow the brace placement prescribed by .clang-format. Use consistent indentation according to the repository\u0026rsquo;s configured indentation width. Opening and closing braces MUST clearly delimit the scope they introduce. Closing braces MUST align with their corresponding declarations or control statements. Do not introduce unnecessary nesting when guard clauses or early returns provide a clearer implementation. Braces MUST NOT be omitted when doing so introduces ambiguity or makes the control flow difficult to follow. Do not manually align statements or braces in ways that conflict with the repository\u0026rsquo;s formatter. Prefer:\nclass xiiResourceRegistry { public: void Clear() { m_Entries.Clear(); } private: xiiDynamicArray\u0026lt;xiiUInt32\u0026gt; m_Entries; }; The structure makes access control, function scope, and member declarations immediately distinguishable.\nSingle-Statement Control Blocks Braces MAY be omitted for simple, single-statement conditions and loops when the resulting control flow remains unambiguous.\nThis convention is particularly appropriate for guard clauses, validation checks, and early returns.\nPrefer:\nxiiResult Initialize(xiiDevice* pDevice) { if (pDevice == nullptr) return XII_FAILURE; if (m_bInitialized) return XII_SUCCESS; return InitializeResources(pDevice); } Braces SHOULD be omitted only when all of the following conditions are satisfied:\nThe controlled body contains exactly one statement. The statement remains readable without additional scope. No ambiguity exists regarding control-flow ownership. The condition and its body are sufficiently simple to understand immediately. Omitting braces is consistent with the surrounding code and formatter configuration. Braces SHOULD be introduced when the controlled statement becomes more complex or when additional statements are added.\nMulti-Statement Control Blocks Braces MUST be used when a conditional branch or loop contains multiple statements.\nPrefer:\nif (bNeedsRebuild) { ResetResources(); RebuildResources(); UpdateResourceBindings(); } Avoid:\nif (bNeedsRebuild) ResetResources(); RebuildResources(); UpdateResourceBindings(); In the avoided implementation, only the first statement is controlled by the condition. The remaining statements execute unconditionally, potentially violating the intended control flow.\nBraces are therefore a correctness mechanism as well as a formatting convention.\nNested Conditions and Ambiguous Control Flow Nested conditionals MUST NOT rely on implicit else association.\nAvoid:\nif (bVisible) if (bReady) Process(); else LogNotReady(); The else belongs to the innermost unmatched if, which may not reflect the intended behavior.\nPrefer:\nif (bVisible) { if (bReady) { Process(); } else { LogNotReady(); } } Where possible, simplify nested conditions using guard clauses or combined predicates.\nPrefer when equivalent:\nif (!bVisible) return; if (!bReady) { LogNotReady(); return; } Process(); Early returns SHOULD be preferred when they reduce nesting without obscuring cleanup, resource ownership, or control-flow requirements.\nConditional Branches For if, else if, and else chains, braces SHOULD be applied consistently across branches when any branch requires a multi-statement body.\nPrefer:\nif (state == xiiResourceState::Ready) { ProcessResource(); UpdateStatistics(); } else if (state == xiiResourceState::Pending) { QueueResource(); } else { LogResourceFailure(); } Avoid mixing braced and unbraced branches when doing so makes the structure less predictable.\nSimple conditional chains MAY omit braces where permitted by the formatter and where every branch remains unambiguous.\nLoops and Iteration Loop bodies follow the same rules as conditional statements.\nBraces MAY be omitted for simple, single-statement loops.\nPrefer:\nfor (xiiUInt32 uiIndex = 0; uiIndex \u0026lt; m_Entries.GetCount(); ++uiIndex) ProcessEntry(m_Entries[uiIndex]); Braces MUST be used for multi-statement loop bodies.\nPrefer:\nfor (xiiUInt32 uiIndex = 0; uiIndex \u0026lt; m_Entries.GetCount(); ++uiIndex) { ResourceEntry\u0026amp; entry = m_Entries[uiIndex]; if (!entry.m_bActive) continue; ProcessEntry(entry); UpdateStatistics(entry); } Loop bodies SHOULD remain focused and avoid excessive nesting. Where iteration logic becomes difficult to follow, consider extracting a cohesive operation into a separate function.\nScope and Lifetime Braces also define lexical scope and therefore influence object lifetime and deterministic destruction.\nIntroduce explicit scopes when a shorter lifetime is necessary for correctness, synchronization, or resource management. Do not introduce additional scopes merely for visual grouping when they serve no meaningful purpose. Variables SHOULD be declared in the narrowest practical scope that satisfies their intended use. RAII-managed resources MUST remain alive for the entire duration of operations that depend on them. Scope boundaries MUST NOT accidentally invalidate borrowed references, pointers, views, or iterators. Prefer:\n{ XII_LOCK(m_Mutex); UpdateSharedState(); } // The lock is released before performing independent work. ProcessPendingTasks(); An explicit scope is justified here because it determines the duration of synchronization rather than serving as a purely stylistic grouping.\nMaintenance and Review Code structure SHOULD remain easy to modify without introducing accidental changes in control flow.\nWhen adding statements to an unbraced control block, introduce braces before expanding the body. Review nested branches carefully when modifying conditions or adding else clauses. Avoid formatting that visually suggests a statement belongs to a scope when it does not. Do not rely on indentation alone to express control flow; C++ semantics are determined by statement structure and braces. Prefer restructuring deeply nested logic over adding layers of braces that obscure the operation\u0026rsquo;s intent. Guiding principle:\nBraces define control flow, scope, and lifetime. Use them consistently to eliminate ambiguity, preserve correctness, and make the structure of an implementation immediately understandable.\n2.3 Alignment and Spacing Consistent alignment and spacing improve readability by making relationships between declarations, expressions, and operations immediately apparent. Formatting MUST follow the repository\u0026rsquo;s .clang-format configuration, which determines the exact alignment and spacing rules.\nAlignment should clarify related code without introducing unnecessary whitespace or making maintenance more difficult.\nGeneral Requirements Use consistent spacing around operators, assignments, declarations, and expressions as prescribed by the repository\u0026rsquo;s formatter. Related declarations and initializations MAY be aligned when doing so improves readability and is supported by the formatter. Do not manually introduce alignment that conflicts with automated formatting. Avoid aligning unrelated statements or declarations across large regions of code. Use blank lines to separate logically distinct operations, not individual statements without meaningful separation. Do not introduce excessive vertical whitespace that unnecessarily fragments cohesive implementations. Preserve consistent spacing within classes, functions, namespaces, and control-flow structures. Prefer:\nxiiUInt32 m_uiFrameIndex = 0U; xiiUInt32 m_uiPassCount = 0U; bool m_bActive = false; Related member declarations may benefit from alignment because their types, names, and initializers form a compact logical group.\nAvoid:\nxiiUInt32 m_uiFrameIndex = 0U; xiiUInt32 m_uiPassCount = 0U; bool m_bActive = false; // Unrelated declarations should not dictate alignment. xiiUniquePtr\u0026lt;xiiResourceData\u0026gt; m_pResourceData; Alignment SHOULD NOT introduce large amounts of whitespace merely to maintain common columns across unrelated declarations.\nLogical Spacing Blank lines SHOULD communicate transitions between logically distinct operations.\nPrefer:\nconst xiiUInt32 uiWidth = descriptor.m_uiWidth; const xiiUInt32 uiHeight = descriptor.m_uiHeight; if (uiWidth == 0U || uiHeight == 0U) return XII_FAILURE; const xiiUInt64 uiPixelCount = static_cast\u0026lt;xiiUInt64\u0026gt;(uiWidth) * uiHeight; AllocateStorage(uiPixelCount); InitializeStorage(); Avoid inserting blank lines after every declaration or statement when those statements form one cohesive operation.\nSimilarly, do not compress unrelated stages of an implementation into a single uninterrupted block.\nGuiding principle:\nAlignment should reveal relationships, and whitespace should reveal structure. Neither should introduce unnecessary complexity or compete with automated formatting.\n2.4 Statement Structure Statements MUST communicate their purpose clearly and avoid unnecessary combinations of declarations, assignments, conditions, and side effects.\nPrefer explicit operations and predictable control flow over compact expressions that require additional interpretation.\nGeneral Requirements Declare one independently meaningful variable per statement. Initialize variables at the point of declaration whenever practical. Prefer declarations in the narrowest scope that satisfies their intended use. Avoid assignment inside conditional expressions unless it materially improves clarity. Use parentheses to clarify complex boolean expressions, particularly when mixing logical operators. Do not combine unrelated side effects into a single statement. Avoid comma expressions when ordinary sequential statements communicate the same behavior more clearly. Prefer early returns for invalid inputs and unmet preconditions when they simplify control flow. Do not rely on implicit operator precedence when an expression could reasonably be misinterpreted. Do not introduce temporary variables that merely rename an expression without improving readability, correctness, or reuse. Declarations and Initialization Prefer:\nconst xiiUInt32 uiWidth = descriptor.m_uiWidth; const xiiUInt32 uiHeight = descriptor.m_uiHeight; if (uiWidth == 0U || uiHeight == 0U) return XII_FAILURE; Avoid:\nxiiUInt32 uiWidth, uiHeight; uiWidth = descriptor.m_uiWidth; uiHeight = descriptor.m_uiHeight; Separate declarations make initialization and variable lifetime easier to inspect.\nMultiple declarations in a single statement SHOULD be avoided when their variables have independent meanings or initialization requirements.\nAssignments and Conditions Assignments within conditions SHOULD be avoided when separating the operations improves clarity.\nPrefer:\nxiiResource* pResource = FindResource(sName); if (pResource == nullptr) return XII_FAILURE; ProcessResource(*pResource); Avoid:\nif (xiiResource* pResource = FindResource(sName)) ProcessResource(*pResource); else return XII_FAILURE; The second form is valid C++, but the first makes lookup, validation, and subsequent processing distinct operations.\nC++17-style conditional initializers MAY be used when their scope restriction provides a clear benefit and the condition remains straightforward.\nComplex Expressions Complex conditions SHOULD be structured so that their intent is immediately understandable.\nPrefer:\nconst bool bRequiresUpdate = bDirty || bForceUpdate; const bool bCanUpdate = bInitialized \u0026amp;\u0026amp; !bPendingDestruction; if (bRequiresUpdate \u0026amp;\u0026amp; bCanUpdate) UpdateResource(); Avoid:\nif ((bDirty || bForceUpdate) \u0026amp;\u0026amp; bInitialized \u0026amp;\u0026amp; !bPendingDestruction) UpdateResource(); Named intermediate predicates are appropriate when they clarify domain-specific meaning, are reused, or make a complex condition easier to verify.\nDo not introduce intermediate variables for every simple expression.\nSide Effects and Evaluation Order Statements with side effects MUST preserve clear and predictable evaluation semantics.\nPrefer:\nUpdateResourceState(); NotifyDependents(); RecordStatistics(); Avoid:\nUpdateResourceState(), NotifyDependents(), RecordStatistics(); Avoid expressions that modify and access shared state through multiple operands when their evaluation order is unclear or dependent on subtle language rules.\nGuiding principle:\nEach statement should express a clear operation. Prefer explicit initialization, understandable conditions, and predictable evaluation over unnecessary compactness.\n2.5 Includes and Whitespace Header dependencies MUST be explicit, organized, and consistent with XII\u0026rsquo;s module architecture.\nInclude ordering should communicate the relationship between the current translation unit, its module, and its dependencies while preserving independent compilation and minimizing unnecessary coupling.\nGeneral Requirements Use one #include directive per line. Include only headers required by the declarations or implementation. Prefer the narrowest appropriate header rather than importing a broad subsystem header unnecessarily. Do not rely on transitive includes for types, macros, or functions directly required by the current file. Public headers MUST be self-contained and compile independently when included in a valid translation unit. Public headers MUST NOT depend on a precompiled header to provide missing declarations or dependencies. Avoid circular header dependencies. Use forward declarations where appropriate and where complete type information is not required. Do not use relative paths to navigate between module include directories when the repository\u0026rsquo;s module-qualified include paths are available. Preserve the established include ordering and grouping conventions. Separate meaningful include groups with a single blank line. Do not introduce unnecessary blank lines within a cohesive include group. Header Files Header files MUST begin with the repository\u0026rsquo;s required license and copyright notice, followed by #pragma once.\nFor headers belonging to a dynamically linked module, the module\u0026rsquo;s DLL.h MUST be the first included header immediately following #pragma once.\nNo other include directive or forward declaration should appear between #pragma once and the module\u0026rsquo;s DLL.h.\nPrefer:\n#pragma once #include \u0026lt;GraphicsCore/GraphicsCoreDLL.h\u0026gt; #include \u0026lt;Foundation/Containers/DynamicArray.h\u0026gt; #include \u0026lt;Foundation/Strings/StringView.h\u0026gt; #include \u0026lt;Foundation/Types/Result.h\u0026gt; #include \u0026lt;GraphicsFoundation/Resources/Resource.h\u0026gt; The module\u0026rsquo;s DLL.h provides the export and import definitions required by its public interfaces.\nIt MUST NOT be omitted from headers that rely on module export macros merely because another included header happens to provide those definitions transitively.\nHeaders for modules that do not use a DLL export header are exempt from this requirement.\nImplementation Files C++ implementation files MUST begin with the repository\u0026rsquo;s required license and copyright notice. For implementation files that use a module precompiled header, the corresponding module PCH MUST be the first include immediately following that notice, before all other header dependencies.\nOther dependencies SHOULD then be grouped according to their architectural relationship.\nA typical implementation file follows this structure:\nModule precompiled header. Required headers from lower-level modules. Headers from the current module. Additional dependencies from other permitted modules or application-specific components. Prefer:\n#include \u0026lt;GraphicsCore/GraphicsCorePCH.h\u0026gt; #include \u0026lt;Foundation/Containers/IdTable.h\u0026gt; #include \u0026lt;Foundation/Threading/Mutex.h\u0026gt; #include \u0026lt;GraphicsFoundation/Resources/Resource.h\u0026gt; #include \u0026lt;GraphicsCore/Pipeline/RenderGraphManager.h\u0026gt; #include \u0026lt;GraphicsCore/Pipeline/RenderGraphResourceCache.h\u0026gt; The ordering of include groups MUST NOT be interpreted as permission to introduce architectural dependencies that violate the module dependency graph.\nWhere project-specific conventions establish a different ordering, those conventions take precedence unless they conflict with the repository\u0026rsquo;s formatting or build requirements.\nForward Declarations Forward declarations SHOULD be used when they meaningfully reduce compilation dependencies without compromising correctness or interface clarity.\nThey are generally appropriate for types referenced exclusively through pointers or references when no complete definition is required.\nPrefer:\n#pragma once #include \u0026lt;GraphicsCore/GraphicsCoreDLL.h\u0026gt; class xiiResourceManager; class XII_GRAPHICSCORE_DLL xiiResourceLoader { public: void SetResourceManager(xiiResourceManager* pManager); private: xiiResourceManager* m_pManager = nullptr; }; A forward declaration MUST NOT replace an include when a complete type is required, such as for a by-value data member, inheritance, inline member access, or another construct requiring the full definition.\nForward declarations SHOULD NOT be introduced solely to eliminate includes when doing so obscures important interface dependencies.\nDependency Hygiene Header dependencies influence compilation time, coupling, and the scope of rebuilds.\nAvoid including implementation-only dependencies in public headers. Move implementation-specific includes into .cpp files where practical. Do not include headers solely because they were historically present. Remove unused includes when modifying the surrounding code, provided doing so does not introduce unrelated changes. Do not introduce monolithic convenience headers into low-level modules when narrowly scoped dependencies are available. Changes to public headers SHOULD consider their impact on downstream compilation and module boundaries. Guiding principle:\nEvery include should represent an intentional dependency. Headers must stand independently, module boundaries must remain explicit, and include ordering must follow the repository\u0026rsquo;s established conventions.\n2.6 Section Separators Section separators MAY be used to organize substantial implementation files and distinguish meaningful groups of functionality.\nTheir purpose is navigation and structural clarity, not decoration.\nGeneral Requirements Use the repository\u0026rsquo;s established separator style where section dividers are appropriate. Prefer section separators between logically distinct implementation groups. Do not insert separators between every small function or helper. Avoid excessive or deeply nested visual section hierarchies. Section organization SHOULD reflect the responsibilities of the implementation rather than arbitrary file length. Do not use section separators to compensate for unrelated responsibilities that should be separated into different components or translation units. Prefer:\n////////////////////////////////////////////////////////////////////////// xiiResourceRegistry::xiiResourceRegistry() = default; xiiResourceRegistry::~xiiResourceRegistry() { Clear(); } ////////////////////////////////////////////////////////////////////////// void xiiResourceRegistry::Clear() { m_Entries.Clear(); } Separators can improve navigation when distinguishing construction and destruction, resource management, serialization, or other substantial implementation groups.\nFor short implementation files containing a small number of closely related functions, additional separators are generally unnecessary.\nStructural Organization When section separators are used, related implementations SHOULD remain grouped consistently.\nExamples of meaningful groups include:\nConstruction, initialization, and destruction. Public interface implementations. Resource creation and release. State management and synchronization. Serialization and deserialization. Internal algorithms and helper functions. Platform-specific implementations. Section dividers MUST NOT replace clear function names, cohesive class design, or appropriate source-file organization.\nAn excessively large implementation file should be evaluated for responsibility boundaries rather than divided indefinitely with decorative comments.\nGuiding principle:\nUse visual separators to clarify meaningful implementation boundaries, not to disguise unnecessary complexity or fragmented design.\n2.7 File Endings and Comments Source files MUST maintain consistent file structure, meaningful documentation, and a clean version-control history.\nComments should explain intent, constraints, and non-obvious decisions rather than repeat information already apparent from the code.\nFile Requirements Source files MUST end with a single newline. Trailing whitespace and unintended mixed line endings MUST NOT be introduced. Required copyright and license notices MUST follow the repository\u0026rsquo;s established form. Existing license notices MUST NOT be removed or modified without authorization. Contributors MUST NOT copy personal or third-party copyright notices into unrelated files without an applicable legal or licensing basis. Required static-link registration macros MUST be preserved where applicable. See §17. Generated source files MUST retain any required generator metadata, registration declarations, or structural markers. Commenting Requirements Comments SHOULD explain information that cannot be communicated adequately through names, types, interfaces, or straightforward implementation structure.\nAppropriate comments include:\nNon-obvious algorithmic decisions. Platform-specific behavior or limitations. Ownership, lifetime, or synchronization constraints. Requirements imposed by external APIs or binary formats. Performance-sensitive assumptions supported by measurements. Intentional deviations from otherwise established conventions. Compatibility requirements that are not apparent from the surrounding code. Prefer:\n// Resource destruction is deferred until submitted GPU work // no longer references the underlying allocation. QueueResourceDestruction(hResource); Avoid:\n// Destroy the resource. DestroyResource(hResource); Comments that merely restate the next operation add noise without improving comprehension.\nObsolete Code and Temporary Changes Do not retain commented-out implementations as a substitute for version control. Obsolete code MUST be removed rather than indefinitely disabled. #if 0 blocks SHOULD NOT be used to preserve abandoned implementations. Temporary debugging statements, experimental code, and instrumentation MUST be removed before submission unless intentionally retained as supported functionality. TODO and FIXME comments SHOULD identify a concrete issue or required action rather than serve as vague placeholders. Workarounds SHOULD explain the underlying constraint and, where appropriate, reference the associated issue or dependency. Avoid:\n#if 0 void ProcessResources() { // Previous implementation retained indefinitely. } #endif Version control already preserves historical implementations. Keeping disabled code in active source files increases maintenance burden and obscures the current implementation.\nComment Accuracy Comments are part of the maintained source contract.\nComments MUST NOT contradict the implementation. When behavior changes, associated comments and documentation MUST be updated. Avoid speculative comments that describe intended future behavior as though it already exists. Do not document guarantees that the implementation does not enforce. Prefer removing an inaccurate comment over preserving it for historical reasons. Guiding principle:\nSource files should contain only the active implementation and the documentation necessary to understand it. Comments must preserve intent, explain constraints, and remain accurate as the code evolves.\n2.8 Header and Implementation File Organization XII follows a consistent source-file organization in which implementation files are placed within an Implementation directory alongside their corresponding public headers.\nThis convention separates interface declarations from implementation details while preserving the logical grouping of related functionality.\nGeneral Requirements Public header files (.h) MUST reside in the directory corresponding to their module and subsystem. C++ implementation files (.cpp) MUST reside in an Implementation subdirectory within the same directory as their corresponding headers. The Implementation directory MUST use the exact capitalization established by the repository. Related headers and implementation files SHOULD share the same base filename to make their relationship immediately apparent. Implementation files MUST NOT be placed directly alongside public headers unless an established repository convention or technical requirement explicitly permits it. Private implementation headers SHOULD reside in the Implementation directory when they are not intended for consumption outside the subsystem. Do not introduce unnecessary directory hierarchies or separate implementation directories when an existing subsystem structure is sufficient. Directory Structure Prefer:\nGraphicsCore/ └── Pipeline/ ├── RenderGraph.h ├── RenderGraphManager.h └── Implementation/ ├── RenderGraph.cpp └── RenderGraphManager.cpp Avoid:\nGraphicsCore/ └── Pipeline/ ├── RenderGraph.h ├── RenderGraph.cpp ├── RenderGraphManager.h └── RenderGraphManager.cpp The preferred structure keeps public interfaces immediately discoverable while grouping their implementations in a predictable location.\nImplementation-Specific Headers Headers that define private implementation details SHOULD remain within the Implementation directory when they are not part of the module\u0026rsquo;s public interface.\nExamples include internal helper classes, private data structures, and implementation-specific utilities.\nSuch headers MUST NOT be treated as public API merely because they are accessible through the repository\u0026rsquo;s include paths.\nIf a private header becomes necessary for multiple independent subsystems, its responsibility and appropriate module placement SHOULD be reconsidered rather than exposing implementation details by convenience.\nExceptions Exceptions MAY be permitted for generated sources, platform-specific implementations, third-party code, or files subject to established build-system conventions.\nAny deviation SHOULD follow an existing repository pattern and have a clear technical justification.\nGuiding principle:\nKeep public interfaces visible and their implementations nearby. A predictable directory structure should make code easy to locate, navigate, and maintain without exposing unnecessary implementation details.\n3. Naming Conventions Consistent naming is essential for readability, maintainability, and discoverability across XII\u0026rsquo;s modules. Identifiers should communicate their purpose, type semantics, ownership context, and relationship to the surrounding system without requiring unnecessary inspection of their declarations or implementations.\nXII uses a combination of PascalCase, descriptive identifiers, and type-dependent prefixes to maintain consistency across its C++ codebase.\nPreserved baseline rule: Type-dependent prefixes are mandatory for member variables only when an applicable type is known. Local variables and function parameters may use type-dependent prefixes, but their use remains at the programmer\u0026rsquo;s discretion.\nExamples throughout this document generally use type-dependent prefixes for local variables and parameters to remain consistent with common XII conventions. These examples MUST NOT be interpreted as making such prefixes mandatory outside member variables.\n3.1 Type and Symbol Names Names MUST be descriptive, consistent with established engine terminology, and appropriate to the responsibility of the symbol.\nSymbol Convention Example Engine classes and structs xii + PascalCase xiiResourceManager Enum wrapper types xii + PascalCase xiiResourceState Enum values PascalCase Unloaded, Loaded, Invalid Functions and methods PascalCase Initialize(), CreateResource() Accessors Get, Is, Has, Can, Set as appropriate GetCount(), IsInitialized() Local variables Descriptive; type prefix optional uiResourceCount, resourceCount Function parameters Descriptive; type prefix optional sPath, path Private/protected instance fields m_ + applicable type prefix m_uiCount, m_pResource Private/protected mutable static fields s_ + applicable type prefix s_uiInstanceCount Private/protected static constants PascalCase, preferably constexpr MaxResourceCount Compile-time constants PascalCase, consistent with scope DefaultAlignment Macros and compile switches Established XII_ convention XII_ASSERT_DEV, XII_ENABLED Template type parameters Semantic PascalCase ValueType, AllocatorType Template non-type parameters Descriptive, following their semantic role Capacity, Alignment Header files Name of primary type or responsibility ResourceManager.h Implementation files Corresponding header base name ResourceManager.cpp Namespaces Established subsystem convention xiiInternal General Requirements Public symbols MUST use terminology consistent with the subsystem that owns them. Names SHOULD describe responsibilities, behavior, or meaning rather than incidental implementation details. Avoid abbreviations that require private knowledge of the implementation. Prefer established engine terminology over inventing alternative names for existing concepts. Do not encode unnecessary implementation details into identifiers when those details may change. Distinguish operations with different semantics through meaningful names rather than arbitrary suffixes. Do not rename stable public symbols solely for stylistic consistency when doing so would create unnecessary compatibility changes. C++ keywords, reserved identifiers, and implementation-reserved naming patterns MUST NOT be used for user-defined symbols. Prefer:\nxiiResult InitializeResources(); xiiUInt32 GetResourceCount() const; bool IsResourceReady(xiiResourceHandle hResource) const; Avoid:\nxiiResult InitRes(); xiiUInt32 GetRC() const; bool Check(xiiResourceHandle hResource) const; Names should be understandable without relying on undocumented abbreviations or examining the implementation.\n3.2 Type-Dependent Member Prefixes Type-dependent prefixes are mandatory for member variables when the member\u0026rsquo;s type belongs to a recognized category.\nThese prefixes communicate the semantic type of the stored value and are applied after the applicable member or static marker.\nType family Prefix Examples Signed integers, including signed atomic integers and ptrdiff_t i m_iPriority, s_iLogLevel Unsigned integers, including size_t ui m_uiFrameIndex, m_uiCount Floating-point types (float, double) f m_fDeltaTime, m_fThreshold Boolean values, including atomic booleans b m_bInitialized, m_bActive Handle types h m_hResource, m_hTexture Raw and smart pointers p m_pDevice, m_pResource NUL-terminated C strings sz m_szDisplayName Non-NUL-terminated character pointers p m_pTextBegin XII string objects and string views s m_sName, m_sPath Vector and SIMD vector types v m_vPosition, m_vVelocity Quaternion types q m_qRotation Matrix and SIMD matrix types m m_mTransform Other types No additional type prefix m_Descriptor, m_Blackboard Prefix Requirements Member variables MUST use their applicable type-dependent prefixes. Prefixes MUST represent the semantic type of the value rather than a convenient or misleading interpretation. Type aliases and wrappers SHOULD follow the semantic category they represent when that category is unambiguous. Do not assign pointer prefixes to integer values merely because those values represent memory addresses. Handles MUST use the h prefix rather than the p prefix, even when they refer to independently allocated resources. Smart pointers such as xiiUniquePtr and xiiSharedPtr use the p prefix because they represent pointer-based access and ownership. Types without an established prefix MUST NOT be assigned arbitrary prefixes merely to maintain a visual pattern. Prefixes SHOULD remain consistent when a member\u0026rsquo;s type changes. A type change that alters the appropriate prefix should trigger a corresponding identifier review. Prefer:\nclass xiiSimulationState { private: xiiUInt64 m_uiSimulationStep = 0U; xiiInt32 m_iPriority = 0; bool m_bActive = false; float m_fDeltaTime = 0.0f; xiiHashedString m_sName; xiiResourceHandle m_hResource; xiiUniquePtr\u0026lt;xiiResourceData\u0026gt; m_pResourceData; xiiVec3 m_vPosition; xiiQuat m_qRotation; xiiMat4 m_mTransform; xiiDynamicArray\u0026lt;xiiUInt32\u0026gt; m_ActiveIndices; }; The spelling m_mTransform is intentional:\nThe first m_ identifies an instance member. The second m identifies a matrix. Likewise, m_pResourceData identifies a member containing a pointer-like object, while m_hResource identifies a resource handle.\nSemantic Types and Aliases A prefix must not misrepresent the underlying abstraction.\nFor example:\nxiiUInt64 m_uiGpuAddress = 0U; xiiResourceHandle m_hResource; xiiUniquePtr\u0026lt;Data\u0026gt; m_pData; A GPU address represented as an unsigned integer uses ui, even if the value refers to a location in GPU memory.\nAn opaque resource handle uses h, regardless of how the handle is implemented internally.\nThe naming convention must remain faithful to the type\u0026rsquo;s public semantics rather than its private representation.\n3.3 Public Fields The existing XII naming policy does not require the m_ prefix for public non-static data members.\nHowever, many existing public engine structures use m_ consistently, particularly configuration objects, descriptors, reflected structures, and serialized data.\nFor new public data structures, contributors SHOULD follow the naming convention established by the surrounding module.\nGeneral Requirements Public fields SHOULD use a consistent naming style within the same structure. New configuration and descriptor structures SHOULD follow the established conventions of neighboring public data types. Public field names MUST communicate their intended meaning and, where relevant, units or state. Do not mix prefixed and unprefixed public fields arbitrarily within a new structure. Existing public fields MUST NOT be renamed solely for aesthetic consistency without evaluating compatibility implications. Changes to reflected or serialized public fields MUST consider their effect on persistent data, tools, scripts, plugins, and external consumers. Prefer:\nstruct xiiSimulationSettings { bool m_bEnableSimulation = true; xiiUInt32 m_uiMaxIterations = 128U; float m_fFixedTimeStep = 0.016f; }; This structure follows a consistent field convention and communicates each value\u0026rsquo;s semantics through its name.\nPublic fields may be accessed directly when the structure intentionally represents data rather than an encapsulated behavioral abstraction.\nDo not introduce getters and setters for every field of a simple configuration or value structure unless doing so protects meaningful invariants or provides necessary behavior.\n3.4 Private, Protected, and Static Fields Private and protected instance fields MUST use the m_ prefix followed by the appropriate type-dependent prefix, where applicable.\nPrivate and protected mutable static fields MUST use the s_ prefix followed by the appropriate type-dependent prefix.\nCompile-time constants should use descriptive PascalCase names.\nPrefer:\nclass xiiCounter { public: XII_ALWAYS_INLINE xiiUInt32 GetCount() const { return m_uiCount; } private: static constexpr xiiUInt32 MaxCount = 1024U; static xiiUInt32 s_uiLiveCounters; xiiUInt32 m_uiCount = 0U; }; General Requirements Instance fields MUST be distinguishable from local variables and parameters through the m_ prefix. Private and protected mutable static fields MUST use s_. Private and protected static constants SHOULD use PascalCase without the mutable-static prefix. Prefer constexpr for compile-time constants when supported by the required semantics. Avoid mutable static state when ordinary object ownership or explicitly managed subsystem state provides a clearer design. Static data MUST have well-defined initialization, synchronization, and destruction behavior. Public mutable static fields SHOULD be avoided when they expose internal state or permit uncontrolled modification. The existing naming policy exempts public static fields from the private-static naming requirement.\nNevertheless, public mutable state should not be introduced casually. Prefer named constants or controlled operations when invariants or synchronization requirements must be preserved.\n3.5 Fixed-Size Arrays The existing XII naming policy permits a relaxed member-prefix convention for fixed-size arrays.\nBoth of the following declarations are permitted:\nbool m_bActiveFlags[3]; bool mActiveFlags[3]; For new code, the conventional typed-member form is RECOMMENDED because it remains consistent with other member declarations.\nGeneral Requirements Fixed-size arrays MAY use the relaxed member-prefix convention established by the original policy. New fixed-size array members SHOULD use the conventional m_ and applicable type prefix. A class or structure SHOULD use one consistent naming style for its fixed-size array members. Existing array members MUST NOT be renamed solely to eliminate a permitted naming exception. Array names SHOULD describe the meaning of their elements rather than merely identify the storage as an array. Prefer for new code:\nclass xiiInputState { private: bool m_bButtonPressed[8] = {}; xiiUInt32 m_uiEventCounts[8] = {}; }; A fixed-size array exception does not remove the requirement for meaningful names or consistent member organization.\n3.6 Boolean and Query Names Boolean identifiers and query methods SHOULD communicate conditions that can be read naturally in control-flow expressions.\nA boolean name should make its true and false meanings immediately apparent.\nGeneral Requirements Boolean member fields MUST use the b type prefix where applicable. Prefer affirmative condition names over unnecessary negations. Avoid ambiguous terms such as Flag, Status, State, or Check when a more specific condition can be expressed. Boolean query methods SHOULD use Is, Has, or Can according to their semantics. Queries SHOULD NOT modify observable object state unless their contract explicitly communicates that behavior. Do not use Get for boolean queries when Is, Has, or Can communicates the intended meaning more precisely. Prefer:\nif (m_bSimulationActive \u0026amp;\u0026amp; pWorld != nullptr) { AdvanceSimulation(); } Avoid:\nif (!m_bSimulationDisabled \u0026amp;\u0026amp; pWorld != nullptr) { AdvanceSimulation(); } Prefer query names that communicate distinct meanings:\nPrefix Intended meaning Example Is Current state or property IsInitialized() Has Possession or availability HasPendingTasks() Can Capability or permission CanExecute() Get Retrieve a value or object GetResourceCount() Set Assign or configure state SetPriority() These conventions are semantic guidelines rather than a requirement to mechanically rename established public APIs.\nGuiding principle:\nA boolean name should describe what is true, and a query method should communicate what it evaluates.\n3.7 Names Must Distinguish Identity, Units, and Scope Names MUST distinguish concepts that share similar underlying representations but have different meanings.\nThis is especially important in systems involving resource handles, identifiers, time measurements, memory offsets, simulation state, and asynchronous execution.\nGeneral Requirements Use distinct names for logically different identifiers, even when their underlying types are identical. Include units in identifiers when values could otherwise be misinterpreted. Distinguish indices, counts, offsets, capacities, and sizes according to their actual semantics. Distinguish CPU submission, GPU completion, and application-level frame progression when those concepts are independently tracked. Avoid generic names when the surrounding scope does not make their meaning unambiguous. Prefer domain-specific terminology over implementation-dependent descriptions. Names MUST NOT suggest guarantees that the underlying value or operation does not provide. Prefer:\nxiiResourceHandle hResource; xiiUInt64 uiSimulationStep; xiiUInt64 uiCompletedStep; xiiUInt32 uiBufferSizeBytes; xiiUInt32 uiElementCount; xiiUInt32 uiElementOffset; float fElapsedTimeSeconds; Avoid:\nxiiResourceHandle hObject; xiiUInt64 uiIndex; xiiUInt64 uiDone; xiiUInt32 uiSize; xiiUInt32 uiCount; xiiUInt32 uiOffset; float fTime; The shorter names may be acceptable within a tightly constrained local scope where their meaning is unambiguous. However, public interfaces and long-lived member fields should use sufficiently descriptive names to remain understandable independently of nearby implementation details.\nUnits and Measurements Unit-bearing identifiers SHOULD use explicit unit suffixes when the unit is not already guaranteed by a strongly typed abstraction.\nExamples include:\nuiBufferSizeBytes uiTimeoutMilliseconds fElapsedTimeSeconds fDistanceMeters uiFrequencyHertz Do not mix values expressed in different units without explicit conversion.\nWhere a dedicated type already encodes units, repeating the unit in every identifier may be unnecessary.\nIdentity and Ordering Identifiers should distinguish identity from position or ordering.\nFor example:\nxiiUInt32 uiResourceIndex; xiiUInt32 uiExecutionOrder; xiiUInt32 uiPriority; These names describe different concepts:\nAn index identifies a position in a collection. An execution order describes sequencing. A priority expresses precedence according to a defined scheduling policy. Do not call an execution index a priority unless the system actually implements priority semantics.\nGuiding principle:\nNames should communicate what a value represents, not merely how it is stored. Identity, units, ordering, and scope must remain distinguishable.\n3.8 Abbreviations, Acronyms, and Spelling Abbreviations and acronyms MUST remain consistent with established XII terminology and commonly understood technical conventions.\nNames should favor clarity over brevity, particularly in public APIs and cross-module interfaces.\nGeneral Requirements Prefer descriptive identifiers over abbreviations that require implementation-specific knowledge. Preserve established engine terminology and widely recognized technical acronyms. Do not invent multiple abbreviations for the same concept within a subsystem. Avoid introducing synonyms for established terms unless they represent meaningfully different concepts. Public APIs SHOULD use sufficiently descriptive names to remain understandable without implementation context. Identifiers, comments, documentation, assertions, and diagnostic messages MUST use correct spelling and terminology. Acronym capitalization SHOULD follow the existing conventions of the surrounding API. Do not rename stable public symbols solely to expand an established abbreviation without considering compatibility consequences. Prefer:\nxiiUInt64 GetCompletedFrameIndex() const; xiiUInt32 GetActiveResourceCount() const; bool IsResourceResident() const; Avoid:\nxiiUInt64 GetCFI() const; xiiUInt32 GetARC() const; bool IsResRes() const; Established Acronyms Common technical abbreviations are acceptable when they are widely recognized or already established within XII.\nExamples include:\nAcronym Meaning CPU Central Processing Unit GPU Graphics Processing Unit GAL Graphics Abstraction Layer API Application Programming Interface ID Identifier UI User Interface SRV Shader Resource View UAV Unordered Access View RT Render Target or Ray Tracing, depending on context SIMD Single Instruction, Multiple Data ECS Entity Component System An acronym SHOULD NOT be used when its meaning is ambiguous within the relevant subsystem.\nFor example, RT may refer to a render target or ray tracing. Where both concepts appear in the same context, prefer a more explicit name.\nTerminology Consistency A subsystem SHOULD establish consistent terminology for its primary concepts.\nFor example, if an API uses the term ResourceHandle, related interfaces should not arbitrarily alternate between ResourceId, ResourceReference, and ResourceToken unless those names represent distinct abstractions.\nLikewise, distinguish registration, creation, acquisition, initialization, and loading when their operations have different lifecycle semantics.\nSpelling and terminology are particularly important for public interfaces because names become part of the contract that developers rely on.\nGuiding principle:\nNames are part of the interface. Favor clarity, preserve established terminology, and use abbreviations only when their meaning is sufficiently well understood.\n4. File Organization and Headers Source files and public interfaces MUST be organized to preserve clear module boundaries, minimize compilation dependencies, and support independent development across XII\u0026rsquo;s subsystems.\nThis section extends the source layout requirements established in §2, focusing on header design, dependency management, translation-unit boundaries, template organization, and binary interfaces.\nFormatting, include ordering, license placement, and the Implementation directory convention are governed by §§2.1–2.8 and are not repeated here.\n4.1 One Clear Primary Responsibility per File Each source file SHOULD have a coherent primary responsibility corresponding to a class, subsystem operation, or closely related group of functions.\nPrefer organizing files around meaningful responsibilities rather than arbitrary implementation size. Keep implementation-specific helpers near the component that owns them. Do not move unrelated types into generic Utilities, Common, or Helpers files merely because their implementations are small. Extract reusable functionality into independent files only when it has a clear responsibility and demonstrated consumers. Avoid distributing a single cohesive implementation across multiple translation units without a meaningful architectural reason. Avoid accumulating unrelated functionality in an existing file solely because it is convenient. File names SHOULD identify their principal type, interface, or responsibility. Public headers and corresponding implementation files follow the directory organization defined in §2.8.\nCore/ └── ResourceManager/ ├── ResourceManager.h ├── ResourceManagerTypes.h └── Implementation/ ├── ResourceManager.cpp └── ResourceManagerTypes.cpp Related types may share a file when their responsibilities are inseparable and separating them would introduce unnecessary dependencies or fragmentation.\nGuiding principle:\nOrganize files around cohesive responsibilities, not arbitrary size limits or superficial classification.\n4.2 Header Self-Sufficiency and Complete Types Public headers MUST be independently compilable and must declare their dependencies explicitly.\nA header must not rely on another header, a precompiled header, or a particular inclusion order to provide required declarations.\nThe include organization and module DLL.h placement requirements are defined in §2.5.\nGeneral Requirements Public headers MUST contain the declarations and dependencies necessary for their independent compilation. A header MUST NOT rely on transitive includes for symbols it directly requires. Complete type definitions MUST be available wherever the C++ language requires them. Forward declarations SHOULD be used where legal and beneficial to reduce unnecessary dependencies. Avoid including implementation-specific headers in public interfaces unless their definitions are genuinely required. Public headers SHOULD minimize dependencies on unrelated modules. Header self-sufficiency SHOULD be validated through independent compilation or automated header checks. Prefer:\n// ResourceLoader.h #pragma once #include \u0026lt;Core/CoreDLL.h\u0026gt; #include \u0026lt;Foundation/Strings/StringView.h\u0026gt; #include \u0026lt;Foundation/Types/Result.h\u0026gt; class xiiResourceManager; class XII_CORE_DLL xiiResourceLoader { public: xiiResult LoadResource(xiiStringView sPath); void SetResourceManager(xiiResourceManager* pManager); private: xiiResourceManager* m_pManager = nullptr; }; The forward declaration is sufficient because the header only stores a non-owning pointer to xiiResourceManager.\nThe corresponding implementation file includes the complete definition when it accesses the manager\u0026rsquo;s members.\nIncomplete Types and Ownership Forward declarations are not universally interchangeable with includes.\nComplete types are generally required for:\nNon-static data members stored by value. Base classes. Inline functions that access type members. Operations requiring an object\u0026rsquo;s size or alignment. Template instantiations whose definitions require complete types. Destruction or allocation operations requiring a complete type. Smart pointers may support declarations involving incomplete types, but their destruction and instantiation requirements must be respected.\nPrefer:\n// SimulationController.h #pragma once #include \u0026lt;Core/CoreDLL.h\u0026gt; #include \u0026lt;Foundation/Types/UniquePtr.h\u0026gt; class xiiSimulationData; class XII_CORE_DLL xiiSimulationController { public: xiiSimulationController(); ~xiiSimulationController(); private: xiiUniquePtr\u0026lt;xiiSimulationData\u0026gt; m_pData; }; // Implementation/SimulationController.cpp #include \u0026lt;Core/CorePCH.h\u0026gt; #include \u0026lt;Core/Simulation/SimulationController.h\u0026gt; #include \u0026lt;Core/Simulation/SimulationData.h\u0026gt; xiiSimulationController::xiiSimulationController() = default; xiiSimulationController::~xiiSimulationController() = default; This pattern is valid when the smart pointer\u0026rsquo;s implementation supports incomplete types and the out-of-line destructor is instantiated with the complete definition available.\nDo not assume that all ownership wrappers have identical incomplete-type requirements.\nGuiding principle:\nHeaders must provide every dependency required by their contracts while avoiding dependencies needed only by their implementations.\n4.3 Header Inclusion and One Definition Rule XII uses #pragma once as its established header inclusion mechanism, as specified in §2.5.\nIn addition to preventing repeated inclusion, header design MUST respect C++ linkage and the One Definition Rule (ODR).\nGeneral Requirements Header definitions MUST be safe for inclusion in multiple translation units. Ordinary non-inline functions and variables with external linkage MUST NOT be defined in headers unless the language explicitly permits the intended definition pattern. Use inline or inline constexpr where appropriate for definitions intentionally shared across translation units. Avoid namespace-scope mutable state defined in headers. Do not rely on #pragma once to prevent ODR violations across separate translation units. Header-defined templates, inline functions, and constants MUST have consistent definitions across all translation units. Conditional compilation MUST NOT produce incompatible definitions of the same externally linked type or symbol. Avoid:\n#pragma once xiiUInt32 GetDefaultCapacity() { return 1024U; } Including this header in multiple translation units can produce multiple definitions of the same externally linked function.\nPrefer:\n#pragma once XII_ALWAYS_INLINE xiiUInt32 GetDefaultCapacity() { return 1024U; } Alternatively, declare the function in the header and provide its definition in the corresponding .cpp file.\nFor non-trivial functions, the latter is generally preferred.\nGuiding principle:\nHeader inclusion protection prevents repeated inclusion within a translation unit; correct linkage and definition placement prevent conflicts across translation units.\n4.4 Public Interface Exposure and Dependency Boundaries Public headers form part of a module\u0026rsquo;s contract and influence every consumer that includes them.\nChanges to public headers must therefore consider dependency direction, compilation costs, portability, and interface stability.\nGeneral Requirements Public interfaces MUST NOT expose private implementation details without a documented architectural reason. Backend-specific declarations SHOULD remain within their corresponding backend modules. Platform SDK and third-party types SHOULD NOT appear in portable public interfaces unless native interoperability is an explicit responsibility. Implementation-only dependencies MUST remain outside public headers where practical. Avoid exposing concrete storage containers when the contract requires only controlled operations or abstract resource access. Do not include large subsystem headers solely to obtain a small declaration when a narrower dependency is available. Public headers MUST respect the module dependency architecture. Cyclic dependencies MUST be resolved through appropriate interface design rather than dependence on include order. Resolving Cyclic Dependencies Circular dependencies frequently indicate unclear ownership or excessively coupled interfaces.\nWhen resolving them, consider:\nReplacing unnecessary includes with forward declarations. Moving implementation-specific dependencies into .cpp files. Extracting genuinely shared types into an appropriate lower-level header. Separating unrelated responsibilities into distinct components. Revising ownership or interface contracts when two components depend on each other\u0026rsquo;s implementation details. Do not introduce generic interface layers solely to eliminate every cycle without evaluating whether the additional abstraction improves the architecture.\nPublic Versus Private Dependencies A dependency belongs in a public header when it is necessary to express or compile that header\u0026rsquo;s public contract.\nDependencies required only for internal algorithms, storage management, platform operations, or implementation-specific functionality should remain private.\nThis separation helps reduce rebuild propagation and permits implementations to evolve without unnecessarily affecting consumers.\nGuiding principle:\nA public header should reveal what a component provides, not how the component happens to implement it.\n4.5 Internal Linkage and Translation-Unit Helpers Symbols used exclusively within a single implementation file SHOULD have internal linkage or otherwise remain inaccessible outside that translation unit.\nThis prevents accidental dependencies on implementation details and reduces the risk of symbol collisions.\nGeneral Requirements Prefer unnamed namespaces for non-member helper functions, constants, and implementation-specific types that are local to a translation unit. File-local static functions and variables MAY be used when consistent with neighboring implementations. Do not export helper symbols without a demonstrated external consumer. Do not place translation-unit implementation details in public namespaces unnecessarily. Helpers SHOULD be located near the operations that use them when doing so improves readability. Avoid introducing globally accessible utility functions for logic used by only one component. Helper names MUST remain descriptive even when their scope is limited. Prefer:\nnamespace { bool IsValidCapacity(xiiUInt32 uiCapacity) { return uiCapacity \u0026gt; 0U; } constexpr xiiUInt32 DefaultCapacity = 1024U; } An unnamed namespace limits these declarations to the translation unit.\nFor class-specific helpers that require private member access or participate in class invariants, prefer private member functions when that more accurately expresses their responsibility.\nAvoid Unnecessary External Linkage Avoid:\nbool ValidateInternalResourceState(const xiiResourceState\u0026amp; state) { return state.m_bInitialized; } If this function is used only in one translation unit, external linkage provides no benefit.\nPrefer giving it internal linkage unless it intentionally forms part of a shared interface.\nGuiding principle:\nKeep implementation details local to the smallest scope that requires them. External visibility should be intentional.\n4.6 Templates, Inline Functions, and Implementation Files Templates and inline functions require particular attention because their definitions may need to be visible to multiple translation units.\nTheir placement MUST satisfy C++ instantiation and linkage requirements without unnecessarily increasing compilation dependencies.\nTemplate Requirements Template definitions MUST be visible at their required instantiation points unless explicit instantiation is used. Template implementations SHOULD follow the existing module conventions for headers and _inl.h files. Do not place template definitions exclusively in .cpp files unless all required specializations are explicitly instantiated and the supported instantiation contract is documented. Avoid unnecessarily large template implementations in public headers when the code can be organized into a dedicated implementation include. Template interfaces SHOULD expose only the constraints and operations required by their consumers. Do not introduce template abstractions solely to avoid a small amount of ordinary implementation duplication. Inline Functions Small accessors and simple operations MAY be defined inline when consistent with surrounding code. Substantial non-template implementations SHOULD remain in .cpp files. Do not assume that the inline keyword guarantees machine-code inlining. Do not move implementations into headers solely in pursuit of speculative performance improvements. Header-defined functions MUST respect linkage and ODR requirements. Performance-motivated inlining decisions SHOULD follow the evidence-based optimization requirements in §1.5. Prefer:\nclass xiiSimulationClock { public: XII_ALWAYS_INLINE xiiUInt64 GetStepCount() const { return m_uiStepCount; } void Advance(xiiTime deltaTime); private: xiiUInt64 m_uiStepCount = 0U; }; The trivial accessor is suitable for a header definition, while Advance() may contain substantial logic and belongs in the corresponding implementation file.\nTemplate Implementation Includes Where the repository uses _inl.h files, these SHOULD remain associated with the corresponding template declarations and follow the established include structure.\nDo not introduce additional source-file extensions or template organization patterns without a clear benefit.\nGuiding principle:\nPlace definitions where C++ requires them, but keep implementation complexity out of public headers whenever practical.\n4.7 Export Boundaries and Binary Interfaces Modules intended for dynamic linking MUST use the appropriate XII DLL export and import declarations.\nPublic symbol visibility is part of the module boundary and must be managed deliberately.\nThe placement of each module\u0026rsquo;s DLL.h is defined in §2.5.\nGeneral Requirements Exported classes, functions, and variables MUST use the appropriate module export macro. Symbols MUST NOT be exported solely for convenience when they are internal implementation details. Avoid unnecessary exposure of internal types, storage layouts, or ownership mechanisms through exported interfaces. Cross-module ownership and destruction responsibilities MUST be explicit. Public binary interfaces MUST consider calling conventions, type layout, alignment, and supported compiler or platform requirements. Changes to exported interfaces MUST be reviewed for compatibility with their intended consumers. Plugin and dynamically loaded module boundaries MUST account for initialization, shutdown, object lifetime, and module unloading. Do not assume that successful compilation guarantees binary compatibility. Example:\nclass XII_GRAPHICSCORE_DLL xiiRenderGraphManager { public: xiiResult Initialize(); void Shutdown(); }; A public class may require the module export macro when its methods or type are intended to be consumed across dynamic library boundaries.\nA forward declaration alone does not necessarily require an export annotation unless its particular usage or compiler contract requires one.\nABI Considerations Exporting a concrete C++ class may expose aspects of its binary representation to dependent modules.\nPotential compatibility concerns include:\nData member layout and alignment. Virtual function table organization. Inheritance structure. Constructor and destructor behavior. Compiler and standard-library compatibility. Template and container implementation details. Allocation and deallocation across module boundaries. Exception and runtime-library configuration. Where independently versioned binary compatibility is a requirement, opaque handles, controlled interfaces, or ABI-stable façade patterns SHOULD be considered.\nThese mechanisms MUST NOT be introduced automatically for every internal engine interface. Their complexity should be justified by actual deployment and compatibility requirements.\nExported Ownership Contracts When resources are created in one module and released in another, their ownership and destruction mechanism must be explicit.\nAvoid interfaces that permit callers to release module-owned allocations through an incompatible allocator or runtime.\nWhere appropriate, provide destruction operations through the same subsystem responsible for creation.\nGuiding principle:\nExport only intentional contracts. Symbol visibility, ownership, and binary compatibility are architectural decisions, not merely compiler annotations.\n4.8 Precompiled Headers and Compilation Independence Precompiled headers are a build optimization and MUST NOT be treated as implicit dependency declarations.\nThe implementation-file ordering requirements, including placement of the repository\u0026rsquo;s license notice before the module PCH, are defined in §2.5.\nGeneral Requirements Implementation files belonging to PCH-enabled modules MUST use the corresponding module PCH according to the repository\u0026rsquo;s build configuration. Public headers MUST NOT depend on a PCH for required declarations. Implementation files MUST include their associated public declaration and other directly required headers. Removing or disabling PCH support SHOULD NOT reveal missing dependencies in otherwise valid source files. A PCH MUST NOT be used to conceal circular dependencies or invalid include relationships. Avoid adding narrowly used dependencies to a module PCH solely to eliminate local include directives. Changes to shared PCH content SHOULD consider compilation impact across the entire module. Prefer:\n// Copyright and license notice. #include \u0026lt;Core/CorePCH.h\u0026gt; #include \u0026lt;Foundation/Containers/DynamicArray.h\u0026gt; #include \u0026lt;Core/Simulation/SimulationController.h\u0026gt; #include \u0026lt;Core/Simulation/SimulationData.h\u0026gt; The PCH accelerates compilation, while the remaining includes document the translation unit\u0026rsquo;s actual dependencies.\nCompilation Validation When modifying shared headers, PCH configuration, or module dependencies, contributors SHOULD consider validating builds with PCH disabled where the build system supports that configuration.\nThis helps identify accidental dependencies that ordinary PCH-enabled builds might conceal.\nGuiding principle:\nA precompiled header may accelerate compilation, but it must never determine whether otherwise valid source code can compile correctly.\n4.9 Public Header Stability and Compilation Cost Public headers are frequently included throughout the engine and can have a disproportionate effect on build times and dependency propagation.\nChanges to widely included headers SHOULD consider both architectural impact and compilation cost.\nGeneral Requirements Keep widely consumed headers focused on the declarations required by their consumers. Avoid introducing heavyweight dependencies into foundational headers without a demonstrated need. Prefer implementation-side changes when a public contract does not need to change. Do not introduce large implementation bodies into public headers solely to reduce the number of source files. Avoid unnecessary macro definitions in shared headers. Shared macros MUST use appropriate naming and scope conventions to minimize collisions. Changes to common type definitions SHOULD consider their effects on downstream compilation, reflection, serialization, and binary compatibility. Build-time improvements SHOULD be measured rather than assumed. Forward declarations, explicit dependencies, and implementation separation can reduce compilation costs, but they should not compromise interface clarity or correctness.\nGuiding principle:\nShared headers are high-impact dependencies. Keep their contracts focused, their dependencies intentional, and their implementations appropriately isolated.\n5. Types, Classes, and Interfaces Types and interfaces MUST communicate their intended responsibilities, ownership semantics, invariants, and permitted operations.\nXII favors cohesive types, explicit contracts, predictable object lifetimes, and controlled state transitions. Object-oriented abstractions should serve concrete architectural purposes rather than introduce unnecessary indirection or complexity.\nThe ownership and lifetime requirements established in §1.2, interface design principles in §1.3, and naming conventions in §3 apply throughout this section.\n5.1 struct Versus class Use struct for transparent data types whose members intentionally form a public data contract. Use class for types that encapsulate behavior, enforce invariants, manage resources, or control access to internal state.\nThis distinction communicates design intent. In C++, struct and class have equivalent capabilities and object representation rules; their primary language-level differences are default member accessibility and default inheritance accessibility.\nGeneral Requirements Use struct for plain data records, descriptors, configuration objects, and related value types whose members are intentionally accessible. Use class when internal state requires controlled mutation or when the type establishes meaningful behavioral invariants. Do not introduce getters and setters for every field of a transparent data structure without a concrete reason. Do not expose mutable fields merely to avoid implementing operations that preserve invariants. A struct MAY contain constructors, helper functions, and validation methods when these complement its role as a transparent data type. A class MAY contain public data when required by an established contract, but such exposure SHOULD be intentional. Do not select struct or class based on assumptions about performance, allocation, or memory layout. If a data structure develops invariants that cannot be reliably maintained through public mutation, reconsider its representation and interface. Prefer:\nstruct xiiSimulationSettings { xiiUInt32 m_uiMaxIterations = 128U; float m_fFixedTimeStep = 0.016f; bool m_bEnableGravity = true; }; class xiiSimulationController { public: xiiResult Initialize(const xiiSimulationSettings\u0026amp; settings); void Shutdown(); bool IsInitialized() const; private: bool m_bInitialized = false; }; The settings structure represents a transparent configuration contract, while the controller encapsulates lifecycle behavior and the state transitions required to maintain it.\nGuiding principle:\nUse struct to represent data and class to protect behavior and invariants. Choose based on semantics, not perceived complexity or performance.\n5.2 Valid Initial States and Object Invariants Objects MUST enter a well-defined state when constructed. Their initial state should be valid for the operations permitted by their documented contract.\nA valid initial state does not necessarily mean a fully initialized or operational state. Some engine components require an explicit initialization phase, but their behavior before initialization must remain well-defined.\nGeneral Requirements Members SHOULD receive meaningful default values through default member initializers or constructors. Do not assume that callers will initialize every field of a public configuration structure manually. Members MUST NOT be read before they have been initialized. Default construction MUST establish a documented state. Partially initialized objects MUST NOT expose operations that require completed initialization without appropriate precondition enforcement. Sentinel values MUST have defined meanings and must not be confused with ordinary valid values. Prefer explicit state representations when several distinct lifecycle states must be represented. Avoid introducing additional state variables when the state can be derived reliably and efficiently from existing information. Initialization failures MUST leave objects in a documented state that permits safe cleanup, recovery, or destruction. Prefer:\nstruct xiiTaskSchedulerSettings { xiiUInt32 m_uiWorkerCount = 1U; xiiUInt32 m_uiQueueCapacity = 1024U; bool m_bEnableTracing = false; }; Avoid:\nstruct xiiTaskSchedulerSettings { xiiUInt32 m_uiWorkerCount; xiiUInt32 m_uiQueueCapacity; bool m_bEnableTracing; }; The avoided implementation permits uninitialized members when a settings object is default-initialized without value initialization.\nInitialization Failure When construction cannot reasonably report an expected runtime failure, prefer a separate initialization operation or a factory that exposes the failure explicitly.\nPrefer:\nclass xiiDataStore { public: xiiResult Open(xiiStringView sPath); void Close(); bool IsOpen() const; private: bool m_bOpen = false; }; The Open() operation may fail because of external conditions, such as an inaccessible file or invalid data.\nSuch failures should be represented through xiiResult or an appropriate richer error mechanism, consistent with §1.1.\nAn object that requires explicit initialization MUST prevent invalid operations before initialization and maintain safe destruction semantics.\nGuiding principle:\nEvery object must have a valid, predictable state throughout its lifetime, including before initialization, after failure, and during destruction.\n5.3 Constructors and Initialization Constructors MUST establish the invariants required for safe object use and destruction.\nPrefer straightforward initialization over constructors that perform substantial work, introduce hidden dependencies, or obscure failure behavior.\nGeneral Requirements Prefer default member initializers for stable, context-independent defaults. Use constructor member initializer lists for values supplied through constructor parameters. References and const members MUST be initialized during construction. Members SHOULD be initialized in their declaration order to avoid misleading initialization dependencies. Constructors accepting a single argument SHOULD be marked explicit unless implicit conversion is intentional and documented. Prefer = default for constructors that require no custom behavior. Use = delete to explicitly prohibit construction or conversion operations where appropriate. Do not call virtual functions from constructors or destructors expecting derived-class dispatch. Constructors MUST NOT publish an object for external use before its required invariants are established. Avoid performing recoverable operations in constructors when those operations may legitimately fail and require error reporting. Constructor behavior SHOULD remain predictable and avoid unnecessary global state changes. Prefer:\nclass xiiFrameRing { public: explicit xiiFrameRing(xiiUInt32 uiCapacity) : m_uiCapacity(uiCapacity) { XII_ASSERT_DEV(uiCapacity \u0026gt; 0U, \u0026#34;Frame ring capacity must be greater than zero.\u0026#34;); } private: xiiUInt32 m_uiCapacity; }; Here, a positive capacity is an explicit construction precondition. The assertion detects a programmer contract violation; it does not replace validation of untrusted input at the boundary where that input enters the system.\nInitialization Order C++ initializes members in declaration order, regardless of the order in which they appear in the constructor initializer list.\nPrefer:\nclass xiiBufferRange { public: xiiBufferRange(xiiUInt32 uiOffset, xiiUInt32 uiCount) : m_uiOffset(uiOffset), m_uiCount(uiCount) { } private: xiiUInt32 m_uiOffset; xiiUInt32 m_uiCount; }; Initializer lists SHOULD follow declaration order to make actual initialization behavior apparent.\nRecoverable Construction Failures XII does not use exceptions for ordinary error handling.\nConstructors should therefore avoid operations that require recoverable failure reporting when a separate initialization method or factory provides a clearer contract.\nPrefer:\nclass xiiResourceDatabase { public: xiiResourceDatabase() = default; xiiResult Initialize(xiiStringView sDatabasePath); void Shutdown(); }; A factory may also be appropriate when partially initialized objects must never be exposed to callers.\nThe choice between explicit initialization and factory construction should reflect actual ownership and lifecycle requirements, not an arbitrary preference for one pattern.\nGuiding principle:\nConstructors establish invariants. Recoverable initialization failures belong in operations that can communicate them explicitly.\n5.4 Destructors and RAII Resource Acquisition Is Initialization (RAII) SHOULD be used whenever resource ownership can be represented through object lifetime.\nResources acquired by an object should be released deterministically when that ownership ends.\nGeneral Requirements Destructors MUST preserve safe cleanup semantics. Destructors MUST NOT propagate exceptions across engine boundaries. Destruction SHOULD be deterministic and avoid unnecessary hidden work. Resources MUST be released using the mechanism appropriate to their ownership contract. RAII guards SHOULD represent one clear responsibility, such as locking a mutex, managing temporary state, or owning a resource. Explicit cleanup operations MUST account for subsequent destruction. Destructors SHOULD NOT perform unbounded waiting or unexpected expensive operations unless the type\u0026rsquo;s contract explicitly requires them. Resource cleanup MUST respect outstanding asynchronous operations and external lifetime dependencies. Destructors MUST NOT depend on virtual dispatch to derived-class implementations. Explicit Cleanup and Destruction When a type provides both an explicit cleanup operation and destructor-based cleanup, repeated cleanup must not violate invariants.\nPrefer:\nclass xiiScopedOperation { public: ~xiiScopedOperation() { End(); } void End() { if (!m_bActive) return; ReleaseResources(); m_bActive = false; } private: void ReleaseResources(); bool m_bActive = true; }; The End() operation is idempotent: invoking it more than once does not repeat resource release.\nThis is particularly useful for scoped operations that may finish explicitly before their owning object is destroyed.\nHowever, idempotent cleanup should not be introduced universally. If repeated cleanup represents a programming error, an assertion or explicit lifecycle contract may be more appropriate.\nAsynchronous Resource Destruction Logical ownership and physical destruction may occur at different times.\nFor example, a GPU resource may become unavailable to new callers while its underlying allocation remains alive until submitted GPU work has completed.\nLikewise, an asynchronous task may require captured state to remain valid until execution finishes.\nDestructors and cleanup operations MUST respect these dependencies.\nGuiding principle:\nResource ownership should determine cleanup responsibility, while the resource\u0026rsquo;s actual lifetime must satisfy every outstanding dependency.\n5.5 Copy and Move Semantics Copy and move operations MUST reflect the semantic identity, ownership, and lifetime requirements of a type.\nDo not make types copyable or movable merely because C++ permits the operations.\nValue Types Value-like types SHOULD support copying when duplication is meaningful and reasonably efficient.\nExamples include mathematical vectors, simple descriptors, configuration records, and small independent data objects.\nCopying such objects should produce independent values with equivalent observable state.\nUnique Ownership Types that own resources exclusively MUST NOT permit copying unless a meaningful deep-copy operation is intentionally provided.\nMoving may be appropriate when ownership can be transferred without violating invariants.\nA move operation MUST leave both the destination and moved-from object in documented, safely destructible states.\nIdentity-Bearing Types Managers, synchronization objects, resource registries, device contexts, and other identity-bearing types are generally noncopyable.\nMoving such objects may also be unsafe when other components retain pointers, references, registrations, callbacks, or addresses referring to the original object.\nPrefer:\nclass xiiTaskScheduler { XII_DISALLOW_COPY_AND_ASSIGN(xiiTaskScheduler); public: xiiTaskScheduler() = default; ~xiiTaskScheduler(); xiiTaskScheduler(xiiTaskScheduler\u0026amp;\u0026amp;) = delete; xiiTaskScheduler\u0026amp; operator=(xiiTaskScheduler\u0026amp;\u0026amp;) = delete; }; The move operations are explicitly prohibited because moving a scheduler may invalidate synchronization or registration state associated with its identity.\nGeneral Requirements Copy and move operations MUST preserve documented invariants. Exclusive ownership MUST NOT be duplicated accidentally. Moving a type MUST NOT invalidate externally registered addresses or references unless the contract explicitly permits it. Prefer compiler-generated copy and move operations when their behavior matches the intended semantics. Custom special member functions SHOULD be introduced only when ownership, resource management, or invariants require them. When declaring custom destructors or copy operations, review whether move operations must be explicitly defined or deleted. Do not infer a type\u0026rsquo;s move behavior solely from the name of an engine macro. The actual definitions of copy-disabling macros MUST be considered before relying on their effects. Prefer the Rule of Zero for types whose members already manage their own resources correctly.\nFor ownership-bearing types that require custom special member functions, define or prohibit the relevant operations deliberately.\nGuiding principle:\nCopying duplicates a value; moving transfers state or ownership. Both operations must be valid according to the type\u0026rsquo;s semantics, not merely its syntax.\n5.6 Inheritance and Virtual Dispatch Inheritance SHOULD represent a genuine substitutability relationship, an established extension contract, or a deliberate form of interface polymorphism.\nPrefer composition when the objective is merely to reuse implementation details.\nGeneral Requirements Public inheritance MUST preserve the behavioral contract of the base type. Derived implementations MUST NOT weaken required preconditions or violate established postconditions. Overridden virtual functions MUST use override. Use final only when preventing further inheritance or overriding is an intentional design requirement. Polymorphic base classes MUST have an appropriate virtual destructor when deletion through a base pointer is permitted. Do not introduce virtual methods solely to make a type hypothetically extensible. Avoid deep inheritance hierarchies that obscure ownership, initialization, or behavioral responsibilities. Do not use inheritance merely to share member variables or common helper implementations. Virtual dispatch overhead SHOULD be evaluated through profiling when it occurs in demonstrated hot paths, as described in §1.5. Prefer:\nclass xiiSimulationBackend { public: virtual ~xiiSimulationBackend() = default; virtual xiiResult Initialize() = 0; virtual void Shutdown() = 0; virtual void StepSimulation(float fDeltaTime) = 0; }; class xiiCpuSimulationBackend final : public xiiSimulationBackend { public: xiiResult Initialize() override; void Shutdown() override; void StepSimulation(float fDeltaTime) override; }; The derived type implements an established behavioral interface rather than inheriting solely to reuse implementation.\nVirtual Destruction A base class intended for polymorphic deletion MUST provide a public virtual destructor or another explicitly enforced destruction mechanism.\nA protected non-virtual destructor may be appropriate for interfaces that prohibit deletion through the base type and delegate destruction to a controlled owner.\nDo not add virtual destructors to every class automatically. Polymorphism and destruction semantics must justify their use.\nAvoiding Artificial Polymorphism Do not introduce base classes, virtual functions, or runtime type registries when the set of implementations is fixed and ordinary composition or direct calls provide a simpler design.\nConversely, do not replace a cohesive polymorphic interface with manual type identifiers and conditional dispatch without a demonstrated benefit.\nGuiding principle:\nInheritance defines behavioral relationships. Use it to express substitutability, not merely to share code or anticipate hypothetical extensions.\n5.7 Interface Granularity and Capability Exposure Interfaces MUST expose meaningful capabilities while minimizing unnecessary knowledge of internal state.\nThe general principles of small, cohesive interfaces are established in §1.3. This section focuses on their application to C++ type design.\nGeneral Requirements Public methods SHOULD represent meaningful operations rather than direct access to implementation details. Avoid exposing internal containers, registration tables, synchronization objects, or mutable storage without a documented requirement. Do not return unrestricted mutable references to manager-owned resources when doing so bypasses invariants. Prefer controlled operations when resource mutation requires validation, synchronization, or lifecycle management. Do not introduce multiple layers of accessors that merely forward calls without establishing meaningful contracts. Avoid expanding interfaces to support hypothetical consumers. Interface methods SHOULD have clearly defined preconditions, postconditions, and failure behavior. Read-only access SHOULD be preferred when mutation is unnecessary. Borrowed access MUST have valid and understandable lifetime guarantees. Prefer:\nclass xiiTaskRegistry { public: xiiResult RegisterTask(xiiTaskHandle hTask); xiiResult UnregisterTask(xiiTaskHandle hTask); bool IsRegistered(xiiTaskHandle hTask) const; }; Avoid:\nclass xiiTaskRegistry { public: xiiDynamicArray\u0026lt;TaskEntry\u0026gt;\u0026amp; GetMutableEntries(); }; The avoided interface exposes internal storage and permits callers to bypass registration rules, potentially invalidating indices, handles, or synchronization assumptions.\nCapability-Oriented Interfaces Interfaces should communicate what consumers are permitted to do, not merely what data the implementation contains.\nHowever, exposing an immutable view or container may be appropriate when iteration itself is a documented capability and its lifetime and invalidation semantics are well-defined.\nControlled interfaces should not become unnecessarily elaborate wrappers around operations that are already safe and self-explanatory.\nGuiding principle:\nExpose the operations consumers need while retaining control of the invariants consumers must not violate.\n5.8 Encapsulation and Invariant Preservation Encapsulation is meaningful only when it protects an actual contract.\nMarking a field private does not guarantee encapsulation if callers can obtain unrestricted mutable access to the same state.\nGeneral Requirements State transitions MUST preserve the invariants owned by the component. Mutable access to internal state SHOULD be restricted when arbitrary modification could violate those invariants. Methods SHOULD express valid transitions rather than requiring callers to coordinate internal fields manually. Internal synchronization requirements MUST NOT be bypassed through unguarded public references or pointers. Avoid exposing implementation storage solely to simplify a caller\u0026rsquo;s code. Do not introduce accessors that provide no meaningful encapsulation benefit. Invariants MUST remain valid after operations that report failure, subject to their documented failure guarantees. Components SHOULD centralize validation and transition logic rather than duplicate it across unrelated callers. Prefer:\nclass xiiSimulationSession { public: xiiResult Begin() { if (m_bActive) return XII_FAILURE; m_bActive = true; return XII_SUCCESS; } void End() { m_bActive = false; } XII_ALWAYS_INLINE bool IsActive() const { return m_bActive; } private: bool m_bActive = false; }; Avoid:\nclass xiiSimulationSession { public: XII_ALWAYS_INLINE bool\u0026amp; GetActiveState() { return m_bActive; } private: bool m_bActive = false; }; The avoided implementation allows external code to modify state without passing through the operations responsible for enforcing valid transitions.\nThe preferred example is intentionally simple; Begin() and End() are justified when they represent meaningful lifecycle transitions. A plain boolean field in a transparent configuration structure would not require this abstraction.\nGuiding principle:\nEncapsulation is the enforcement of meaningful contracts, not the mechanical replacement of fields with getters and setters.\n5.9 Separate Ownership from Execution Policy Object ownership and execution policy are distinct architectural responsibilities.\nA component may own, register, or manage the lifetime of an object without determining when or how that object executes.\nThese responsibilities SHOULD remain separate unless their coupling is an explicit part of the subsystem\u0026rsquo;s contract.\nGeneral Requirements Registration MUST NOT implicitly imply execution unless documented by the owning subsystem. Ownership MUST NOT automatically imply scheduling, activation, or per-frame participation. Execution order, priority, frequency, and synchronization SHOULD be represented independently when they are distinct concepts. Avoid embedding global scheduling assumptions into otherwise reusable resource or object managers. Components SHOULD be reusable across execution contexts when their responsibilities permit it. Do not introduce a separate scheduler or execution abstraction when ownership and execution are inherently inseparable and no meaningful benefit would result. Changes to execution policy SHOULD NOT require restructuring ownership contracts unnecessarily. Example A simulation registry may own simulation instances while a separate scheduler determines which instances participate in a particular update.\nclass xiiSimulationRegistry { public: xiiSimulationHandle CreateSimulation(); xiiResult DestroySimulation(xiiSimulationHandle hSimulation); }; class xiiSimulationScheduler { public: xiiResult ScheduleSimulation(xiiSimulationHandle hSimulation); xiiResult UnscheduleSimulation(xiiSimulationHandle hSimulation); void ExecuteScheduled(float fDeltaTime); }; The registry owns simulation lifetime, while the scheduler controls execution participation.\nThis separation is appropriate when simulations may be created independently of scheduling or participate in different execution policies.\nFor smaller systems where ownership and execution are necessarily coupled, a single cohesive component may be preferable.\nGuiding principle:\nOwnership determines who manages an object. Execution policy determines when and how it participates in work. Do not conflate these responsibilities without a concrete reason.\n5.10 Favor Composition Over Unnecessary Inheritance Composition SHOULD be preferred when a type needs to use or own another component rather than satisfy its behavioral contract.\nComposition permits responsibilities to evolve independently and generally makes ownership and lifetime relationships easier to understand.\nGeneral Requirements Prefer composition for owned utilities, allocators, profilers, caches, synchronization objects, and supporting services. Use inheritance when substitutability or an established extension interface provides a meaningful benefit. Avoid inheriting from a type solely to access its implementation details or reuse a few methods. Do not introduce deep inheritance hierarchies to share common data members. Composed objects MUST have clearly defined ownership and lifetime relationships. Prefer direct composition over unnecessary service locators, global registries, or dependency injection layers. Dependencies SHOULD be provided explicitly when doing so clarifies ownership and testability. Do not fragment a cohesive type into numerous single-purpose objects without a demonstrated architectural benefit. Prefer:\nclass xiiSimulationSystem { public: xiiResult Initialize(); void Shutdown(); private: xiiTaskScheduler m_TaskScheduler; xiiSimulationClock m_SimulationClock; }; Avoid:\nclass xiiSimulationSystem : public xiiTaskScheduler, public xiiSimulationClock { public: xiiResult Initialize(); void Shutdown(); }; The simulation system uses scheduling and timing capabilities; it is not itself necessarily a scheduler or a clock.\nComposition communicates that relationship without exposing the implementation details of its supporting components through inheritance.\nDependency Lifetime Composition also clarifies destruction order.\nC++ destroys member objects in the reverse order of their declaration. Dependencies between composed members MUST respect this behavior.\nWhen one member depends on another during destruction, their declaration order and cleanup contracts must ensure that the dependency remains alive for the required duration.\nGuiding principle:\nPrefer composition to express what a component has or uses. Reserve inheritance for what a component genuinely is or promises to implement.\n6. Initialization, Ownership, and Lifetime Resource management in XII MUST be deterministic, explicit, and consistent with the lifetime guarantees of the underlying subsystem.\nThis applies to ordinary heap allocations, engine-managed resources, platform objects, asynchronous operations, GPU resources, plugin registrations, and any object whose lifetime extends beyond a single scope.\nThe general ownership principles established in §1.2 and the type design requirements in §5 apply throughout this section. This section defines the practical requirements for allocation, initialization, borrowing, stable identity, resource invalidation, and coordinated destruction.\n6.1 The Ownership Rule Every dynamically allocated object and long-lived resource MUST have an identifiable owner or an explicitly defined shared ownership policy.\nOwnership determines which component is responsible for releasing a resource. Borrowed access does not transfer or extend that responsibility.\nGeneral Requirements Every resource MUST have a clearly defined ownership and destruction mechanism. Prefer automatic storage and value semantics when dynamic allocation or independent identity is unnecessary. Use xiiUniquePtr when exclusive heap ownership is appropriate. Use xiiSharedPtr only when multiple independent owners genuinely require shared lifetime management. Raw pointers, references, spans, and views MUST be treated as non-owning unless an API explicitly documents an ownership-transfer contract. Ownership transfers MUST be visible through the API and supported by the relevant type semantics. Do not introduce shared ownership merely to compensate for unclear lifetime relationships. Do not maintain competing ownership mechanisms for the same resource. Resource-owning objects MUST remain safely destructible after partial initialization or recoverable failure. Ownership and thread safety MUST be evaluated independently. Prefer:\nstruct xiiSimulationEntry { xiiUniquePtr\u0026lt;xiiSimulation\u0026gt; m_pSimulation; xiiSimulationState m_State; }; The entry has exclusive ownership of its simulation object, while the associated state follows the entry\u0026rsquo;s lifetime.\nAvoid:\nstruct xiiSimulationEntry { xiiSimulation* m_pSimulation = nullptr; // Ownership is unspecified. // The caller may not know who destroys the simulation. }; An owning raw pointer may be required when interacting with a specialized allocator or legacy interface, but its ownership contract MUST be explicit and consistently enforced.\nShared Ownership Shared ownership introduces additional costs and lifetime complexity.\nThese may include:\nReference-counting operations. Atomic synchronization or contention. Delayed and less predictable destruction. Ownership cycles. Difficulty coordinating resource release with subsystem shutdown. Resources remaining alive beyond plugin or device lifetime. Shared ownership SHOULD NOT be the default solution to lifetime uncertainty.\nWhen an object has one authoritative manager, prefer manager-owned storage and non-owning, validated access where appropriate.\nGuiding principle:\nEvery resource must have an accountable owner. Borrowing grants access, not ownership, and shared ownership must reflect an actual lifetime requirement.\n6.2 Allocation and Allocator Compatibility XII\u0026rsquo;s allocation mechanisms MUST be used consistently with the allocator responsible for the resource.\nThe mechanism used to destroy an object must be compatible with the mechanism used to construct it.\nGeneral Requirements Use XII allocation facilities when required by the owning subsystem. Allocations made using XII_NEW MUST be released using the corresponding compatible XII destruction mechanism. Do not arbitrarily mix XII_NEW and XII_DELETE with unrelated C++ new and delete operations. Use XII_DEFAULT_NEW and XII_DEFAULT_DELETE to use the default allocator when no other allocator is required by the owning subsystem. Objects created through custom allocators MUST retain or otherwise have access to the allocator required for destruction. Containers SHOULD use the allocator associated with their owning subsystem when allocator selection is part of that subsystem\u0026rsquo;s contract. Avoid introducing independent allocations when values can be stored directly without compromising lifetime or address stability. Allocator ownership and lifetime MUST be compatible with the lifetime of every allocation obtained from it. Memory MUST NOT be released through an allocator that has already been destroyed or invalidated. Cross-module allocations MUST respect the allocation and destruction contract of the module boundary. Example:\nxiiUniquePtr\u0026lt;xiiSimulationData\u0026gt; pData = XII_DEFAULT_NEW(xiiSimulationData); The example assumes the XII ownership wrapper and allocation macro support the corresponding allocator-aware construction and destruction semantics.\nExplicit Allocator Dependencies When an object requires a particular allocator, prefer expressing that dependency through its constructor or initialization contract.\nPrefer:\nclass xiiSimulationStorage { public: explicit xiiSimulationStorage(xiiAllocator* pAllocator) : m_Entries(pAllocator) { XII_ASSERT_DEV(pAllocator != nullptr, \u0026#34;A valid allocator is required.\u0026#34;); } private: xiiDynamicArray\u0026lt;xiiUInt32\u0026gt; m_Entries; }; Avoid:\nclass xiiSimulationStorage { public: xiiSimulationStorage() : m_Entries(GetGlobalSimulationAllocator()) { } private: xiiDynamicArray\u0026lt;xiiUInt32\u0026gt; m_Entries; }; The avoided implementation introduces a hidden dependency on global allocator state.\nA global or subsystem allocator may be appropriate when it is part of an established engine-wide allocation policy. However, allocator dependencies should not be hidden when explicit selection is necessary for correctness, testing, or lifetime management.\nAllocator Lifetime An allocator must remain valid until all allocations that depend on it have been released.\nThis requirement is particularly important for:\nFrame and temporary allocators. Pool and arena allocators. Plugin-specific allocators. Module-local heaps. Device-scoped resource allocators. Memory shared across dynamically loaded modules. Resetting an arena or destroying a pool invalidates allocations owned by that allocator according to its documented semantics.\nGuiding principle:\nAllocation and destruction are a single ownership contract. The allocator, allocation, and owning object must have compatible lifetimes.\n6.3 Borrowing Is Not Ownership A borrowed pointer, reference, or view provides temporary access to an object according to the owner\u0026rsquo;s lifetime contract.\nBorrowing MUST NOT be interpreted as ownership transfer or a guarantee that the referenced object will remain alive indefinitely.\nGeneral Requirements Borrowed access MUST remain within the validity period guaranteed by the owner. A borrowed pointer MUST NOT be destroyed by the borrower unless ownership transfer is explicitly documented. Borrowed access MUST NOT be retained beyond owner destruction or resource invalidation. Borrowed access MUST NOT be assumed thread-safe. Do not capture borrowed pointers in asynchronous operations without guaranteeing the referenced object\u0026rsquo;s lifetime. Do not expose borrowed references when their validity cannot be adequately described or enforced. Prefer references when null is not a meaningful state and the lifetime contract permits reference semantics. Prefer pointers when optional access, nullability, or reseating is meaningful. Example:\nxiiGALDevice* pDevice = m_pDevice.Borrow(); if (pDevice == nullptr) return XII_FAILURE; InitializeDeviceResources(*pDevice); The pointer is valid only while the owning object guarantees the device\u0026rsquo;s lifetime.\nThe Borrow() operation does not extend that lifetime.\nBorrowing Across Asynchronous Boundaries Borrowed access becomes particularly dangerous when execution is deferred.\nAvoid:\nvoid SubmitTask() { xiiString sMessage = \u0026#34;Simulation complete\u0026#34;; QueueTask([\u0026amp;sMessage]() { LogMessage(sMessage); }); } The callback may execute after sMessage has been destroyed.\nPrefer:\nvoid SubmitTask() { xiiString sMessage = \u0026#34;Simulation complete\u0026#34;; QueueTask([sMessage = std::move(sMessage)]() { LogMessage(sMessage); }); } The callback owns its captured string, assuming the task system stores and executes the callable according to the required ownership contract.\nCopying or moving captured data is not always necessary. Borrowing remains appropriate when the asynchronous operation\u0026rsquo;s completion is guaranteed before the referenced storage becomes invalid.\nGuiding principle:\nBorrowed access is valid only for the lifetime and synchronization guarantees explicitly provided by its owner.\n6.4 Stable Identity Is Not Stable Address Object identity and memory address are distinct concepts.\nA handle identifies a logical object through an authoritative subsystem, while a pointer identifies a particular memory location.\nNeither mechanism automatically guarantees the continued lifetime of the referenced object.\nGeneral Requirements Use handles when objects require independently managed identity, validation, or generation tracking. Use pointers and references when direct access is appropriate and their lifetime is guaranteed. Do not assume that a container preserves element addresses unless its contract explicitly guarantees that behavior. Container growth, insertion, removal, compaction, or relocation MUST be considered when storing pointers or references to elements. Generation-checked handles SHOULD be used when reusable registry slots could otherwise allow stale identifiers to reference unrelated objects. Handle validity MUST be checked when the calling contract does not guarantee validity. Stable allocation addresses MUST NOT be confused with permanent resource validity. Handles MUST NOT be treated as ownership tokens unless the subsystem explicitly defines them that way. Avoid:\nxiiDynamicArray\u0026lt;Entry\u0026gt; m_Entries; xiiIdTable\u0026lt;GraphId, Entry*, xiiLocalAllocatorWrapper\u0026gt; m_EntryTable; // Pointers into m_Entries may be invalidated when // the container reallocates or relocates its elements. Prefer when stable allocation addresses are required:\nxiiDynamicArray\u0026lt;xiiUniquePtr\u0026lt;Entry\u0026gt;\u0026gt; m_Entries; // Moving or growing the pointer container does not // relocate the independently allocated Entry objects. This representation preserves the addresses of surviving allocated entries during relocation of the container\u0026rsquo;s pointer elements.\nIt does not preserve references after the corresponding entry is destroyed.\nHandles and Generations A generation-checked handle generally combines a resource slot or index with information that distinguishes different lifetimes of that slot.\nWhen a resource is destroyed, its previous generation becomes invalid according to the registry\u0026rsquo;s rules.\nThis prevents certain stale-handle errors, including accidental access to a newly allocated object that reuses an earlier slot.\nHowever, generation tracking does not automatically provide:\nThread-safe lookup. Atomic lifetime acquisition. Protection against concurrent destruction after lookup. Protection against generation wraparound. Ownership of the referenced resource. These guarantees require separate mechanisms.\nGuiding principle:\nHandles preserve logical identity through validation. Pointers provide direct access to memory. Neither alone establishes a lifetime or concurrency guarantee.\n6.5 Lifetime-Safe Lookup and Concurrent Access A successful resource lookup does not necessarily guarantee that the resource remains alive after the lookup completes.\nThis is especially important when registration, lookup, and destruction may occur concurrently.\nGeneral Requirements Resource lookup MUST respect the synchronization contract of the owning registry. A lookup mutex MUST NOT be assumed to protect a resource after the lock has been released. Returned pointers and references MUST remain valid for the documented duration of access. Resource destruction MUST NOT race with active access that requires the resource to remain alive. Lookup and lifetime protection MUST be coordinated when concurrent destruction is permitted. Do not return unrestricted mutable access to internally synchronized resources unless callers receive an enforceable synchronization contract. Thread-affine registries MUST document their permitted access and mutation contexts. Avoid introducing shared ownership solely for lookup convenience when scoped access or execution-boundary guarantees are sufficient. Avoid:\nEntry* GetEntry(xiiResourceHandle hResource) { XII_LOCK(m_Mutex); Entry* pEntry = nullptr; m_ResourceTable.TryGetValue(hResource, pEntry); return pEntry; } The mutex protects the lookup operation, but it does not necessarily protect the returned pointer.\nAnother thread may destroy the entry immediately after the function releases the mutex.\nValid Lifetime Strategies The appropriate strategy depends on the subsystem\u0026rsquo;s concurrency and execution model.\nCommon approaches include:\nScoped locking: Hold the required lock throughout the operation that accesses the resource. Scoped access guards: Return an object that maintains the necessary lifetime and synchronization protection. Shared ownership: Retain a strong ownership reference for the duration of access when shared lifetime semantics are appropriate. Execution-boundary mutation: Permit registration and destruction only at defined synchronization points. Deferred reclamation: Delay physical destruction until all active readers or operations have completed. These strategies are alternatives rather than mechanisms that must all be implemented.\nThe simplest approach satisfying the actual concurrency requirements SHOULD be preferred.\nScoped Access When resource access must remain synchronized, prefer an API that performs the operation within the required lifetime boundary.\nFor example:\nxiiResult UpdateResource(xiiResourceHandle hResource) { XII_LOCK(m_Mutex); Entry* pEntry = nullptr; if (!m_ResourceTable.TryGetValue(hResource, pEntry)) return XII_FAILURE; return pEntry-\u0026gt;Update(); } This pattern is appropriate when the mutex protects the entire operation and Update() does not violate the lock\u0026rsquo;s reentrancy, ordering, or contention requirements.\nLong-running operations should not automatically execute while holding a global registry mutex.\nWhere that would cause unacceptable contention, use a more appropriate scoped lifetime mechanism.\nGuiding principle:\nA lookup establishes that an object existed at a particular moment. Safe access requires that its lifetime and synchronization guarantees remain valid throughout the operation.\n6.6 Parent-Child and Subsystem Lifetimes Resources frequently depend on other objects or subsystems for their operation and destruction.\nThese dependencies MUST be reflected in initialization and shutdown ordering.\nGeneral Requirements Dependent resources MUST be released before the infrastructure required for their destruction becomes unavailable. Initialization order SHOULD follow dependency direction. Shutdown order SHOULD reverse dependency order where appropriate. Explicit shutdown operations MUST prevent new work from acquiring resources that are being destroyed. Objects MUST NOT access dependencies after those dependencies have been destroyed or invalidated. Plugin-owned callbacks, delegates, and registrations MUST be removed before unloading the plugin code they reference. Background tasks MUST complete, cancel, or otherwise release their dependencies before the owning subsystem is destroyed. Shutdown MUST account for partially initialized subsystems. Avoid relying on unspecified static initialization or destruction order across translation units or dynamically loaded modules. Dependency Ordering For example, a rendering subsystem may have the following dependency hierarchy:\nGAL Device └── Device Resource Cache └── Render Graph Resources └── Frame Execution State If each child depends on its parent during destruction, shutdown must release the dependent objects before the required parent infrastructure.\nA typical shutdown sequence would therefore release:\nActive frame execution state. Graph-specific resources. Device-scoped caches. The underlying GAL device. The exact sequence depends on the actual ownership and synchronization contracts.\nIt must not be inferred solely from the visual hierarchy.\nPlugin Lifetime Dynamically loaded plugins introduce an additional dependency: executable code may disappear when the module is unloaded.\nBefore unloading a plugin, the engine MUST ensure that no active operation retains a callback, virtual dispatch target, function pointer, or other executable reference into the unloaded module.\nThis includes asynchronous tasks and deferred callbacks that may execute after the plugin\u0026rsquo;s shutdown function returns.\nGuiding principle:\nDependencies must outlive every operation that relies on them. Initialization establishes those dependencies; shutdown must dismantle them safely.\n6.7 Resource Invalidation and Destruction Resource invalidation MUST distinguish between invalidating a caller\u0026rsquo;s reference, removing a resource from a registry, and physically destroying its underlying allocation.\nThese events may occur at different times.\nGeneral Requirements Public handles SHOULD be invalidated after successful destruction when the API supports explicit invalidation. Invalidating one handle variable MUST NOT be interpreted as invalidating copies held elsewhere. The authoritative registry MUST reject handles that are stale according to its generation and validity rules. Destruction operations MUST preserve registry consistency. Resource removal MUST prevent future lookups from acquiring access when the resource is no longer logically available. Physical destruction MUST respect outstanding references and asynchronous execution dependencies. Repeated destruction attempts MUST have documented behavior. Failed destruction operations MUST leave resource ownership and validity in a documented state. Example:\nif (DestroyResource(hResource).Succeeded()) { hResource.Invalidate(); } The caller invalidates its local handle only after the destruction operation reports success.\nThis does not revoke other copies of the handle.\nThe resource registry remains responsible for rejecting stale identifiers.\nLogical and Physical Destruction In asynchronous systems, resource destruction may involve multiple phases:\nLogical invalidation: The resource becomes unavailable for new operations. Retirement: Existing operations are allowed to complete or release their references. Physical destruction: The underlying allocation and associated objects are released. Not every subsystem requires all three phases.\nA simple CPU-owned object with no outstanding references may be destroyed immediately.\nGPU resources, asynchronously accessed objects, and concurrently readable registry entries may require deferred reclamation.\nGuiding principle:\nInvalidating a reference prevents future intended use. Safe destruction additionally requires that no outstanding operation can still access the resource.\n6.8 Non-Owning Views and Scope-Bound Data Views, spans, borrowed strings, iterators, mapped memory, and similar abstractions provide access without necessarily controlling the lifetime of the referenced storage.\nTheir validity MUST remain within the lifetime and invalidation guarantees of the underlying owner.\nGeneral Requirements Non-owning views MUST NOT outlive their backing storage. Views MUST NOT be retained across container mutations that invalidate their referenced storage. Do not return views or references into temporary objects that are destroyed before the caller can use them. Do not persist borrowed stack storage in asynchronous callbacks or long-lived objects. Copy data into owning storage when its lifetime must extend beyond the original owner\u0026rsquo;s guarantees. Mapping objects and scoped access helpers MUST respect their required unmapping or release operations. Borrowed views MUST NOT be assumed safe for concurrent mutation. Prefer explicit ownership when lifetime extension cannot be guaranteed reliably. Avoid:\nxiiStringView MakeResourceName() { xiiString sName = \u0026#34;SimulationResource\u0026#34;; return sName; } The returned view references storage owned by a local string that is destroyed when the function returns.\nPrefer:\nxiiString MakeResourceName() { return xiiString(\u0026#34;SimulationResource\u0026#34;); } The returned string owns its storage.\nContainer Invalidation Container operations may invalidate references, pointers, iterators, or views into their contents.\nCode MUST respect the invalidation guarantees of the specific container being used.\nDo not assume that all XII containers share the same relocation or iterator stability semantics.\nFor example, growing a contiguous dynamic array may relocate its elements, invalidating previously obtained pointers.\nA container providing stable element addresses may have different guarantees, but element removal still invalidates access to the removed object.\nScoped Mapping Memory obtained through a mapping operation may remain valid only until its corresponding unmap or release operation.\nSuch pointers MUST NOT be retained beyond that boundary.\nThis applies to CPU memory mappings, GPU-visible mapped ranges, temporary allocator storage, and similar scoped resources.\nGuiding principle:\nA view grants access to existing storage; it does not extend the storage\u0026rsquo;s lifetime. Persist ownership, not borrowed access, when data must survive its original scope.\n6.9 Runtime and GPU Resource Lifetimes CPU-side execution completion and GPU execution completion are different events.\nA function returning from command recording, submission, or render graph execution does not necessarily mean that the GPU has finished accessing the submitted resources.\nGPU resource lifetime MUST account for asynchronous execution.\nGeneral Requirements CPU-side submission completion MUST NOT be treated as proof of GPU execution completion. Resources referenced by submitted GPU work MUST remain valid until the GPU has completed the operations that require them. Resource retirement MUST use appropriate queue completion, fence, timeline, or equivalent synchronization guarantees. Command allocators, descriptor storage, upload buffers, temporary resources, and other GPU-visible allocations MUST respect their actual in-flight lifetimes. Frame-index progression MUST NOT be confused with GPU completion unless the frame-management contract explicitly establishes that relationship. Resource reuse MUST respect outstanding GPU dependencies. Deferred destruction mechanisms MUST preserve the resources and associated device infrastructure required for final cleanup. Device shutdown MUST account for outstanding submissions and pending resource retirement. Synchronization mechanisms MUST follow the guarantees of the active graphics backend. Submission Versus Completion Consider a typical sequence:\nCPU: Record Commands | v Submit Commands | v Execute() Returns | v Continue CPU Work GPU: Execute Submitted Commands | v Signal Completion The CPU may continue execution while the GPU is still processing previously submitted commands.\nDestroying a resource immediately after the submission function returns may therefore be unsafe.\nDeferred Resource Retirement A rendering subsystem may logically release a resource immediately while retaining its physical allocation until the relevant GPU completion condition is satisfied.\nFor example:\nvoid ReleaseResource(xiiGALResourceHandle hResource) { QueueResourceRetirement(hResource, GetCurrentSubmissionValue()); } This is a conceptual example. The retirement system must ensure that the recorded completion value covers every GPU submission that may still access the resource.\nA resource may have dependencies across multiple queues or submissions. The last submission recorded by a local subsystem is not necessarily sufficient to establish safe destruction.\nFrames in Flight Frame-buffering strategies commonly retain resources across several in-flight frames.\nHowever, a fixed number of frames in flight is not itself a completion guarantee.\nResource reuse and destruction MUST be based on the synchronization semantics of the frame system and graphics backend.\nDo not assume that advancing a frame counter by a particular amount proves that earlier GPU work has completed.\nDetailed GPU synchronization, queue completion, and retirement requirements are covered in §15.\nGuiding principle:\nCPU execution determines when work is submitted. GPU synchronization determines when its resources can be safely reused or destroyed. These lifetimes must never be conflated.\n6.10 Initialization and Shutdown State Machines Subsystem initialization and shutdown MUST follow a documented lifecycle.\nA component should not require callers to infer which operations are valid from partially initialized internal state.\nGeneral Requirements Initialization and shutdown operations MUST define their valid calling conditions. Repeated initialization MUST have documented behavior. Repeated shutdown MUST either be supported safely or treated as an explicit contract violation. Initialization failures MUST leave the component in a state where cleanup or recovery is possible. Shutdown MUST prevent new work from accessing resources that are being retired. Lifecycle state SHOULD remain minimal and correspond to actual observable requirements. Do not introduce elaborate state machines for simple components that require only initialized and uninitialized states. Explicit initialization methods SHOULD return xiiResult when initialization may fail for expected runtime reasons. Example:\nclass xiiSubsystem { public: xiiResult Initialize() { if (m_bInitialized) return XII_FAILURE; if (InitializeResources().Failed()) return XII_FAILURE; m_bInitialized = true; return XII_SUCCESS; } void Shutdown() { if (!m_bInitialized) return; ReleaseResources(); m_bInitialized = false; } XII_ALWAYS_INLINE bool IsInitialized() const { return m_bInitialized; } private: xiiResult InitializeResources(); void ReleaseResources(); bool m_bInitialized = false; }; This example assumes InitializeResources() either cleans up any partial acquisitions before returning failure or leaves them in a state safely handled by the component\u0026rsquo;s cleanup contract.\nThe implementation must not mark initialization as complete before all required invariants have been established.\nTransactional Initialization Where practical, initialization SHOULD provide an all-or-nothing observable result.\nIf initialization fails, the subsystem should either:\nRestore its previous valid state. Return to a documented uninitialized state. Enter an explicit recoverable failure state when restoration is impossible or inappropriate. Partial initialization must not leave resources permanently owned without a valid cleanup path.\nGuiding principle:\nInitialization establishes a usable state; shutdown dismantles it. Both operations must preserve ownership, invariants, and well-defined recovery behavior.\n6.11 Asynchronous Operations and Cancellation Asynchronous execution introduces lifetimes that may extend beyond the scope of the function that schedules the work.\nEvery asynchronous operation MUST establish ownership and cancellation rules for the data and resources it accesses.\nGeneral Requirements Captured objects MUST remain valid until asynchronous work completes or releases them. Cancellation MUST NOT be treated as synchronous completion unless the API explicitly guarantees it. Owners MUST coordinate outstanding tasks before destroying resources those tasks may access. Callbacks MUST NOT retain references to destroyed objects or unloaded plugin code. Task completion and resource release MUST obey the required synchronization and memory-visibility rules. Do not introduce shared ownership merely because an operation is asynchronous when scoped completion or explicit lifetime coordination provides a simpler solution. Asynchronous APIs SHOULD make ownership transfer and cancellation responsibilities visible. Shutdown paths MUST define how pending, executing, and completed tasks are handled. Cancellation Versus Completion Cancellation commonly represents a request that work should stop or avoid starting.\nIt does not necessarily guarantee that currently executing work has finished.\nA component that requests cancellation and then immediately destroys task dependencies may therefore introduce use-after-free behavior.\nSafe destruction may require waiting for task completion, transferring ownership of required data, or deferring reclamation.\nGuiding principle:\nScheduling transfers work into the future, not automatically the ownership of everything that work accesses. Asynchronous lifetime guarantees must be explicit.\n6.12 Resource Lifetime Verification Lifetime correctness SHOULD be validated through tests and development diagnostics, particularly for resource managers, concurrent registries, asynchronous execution, and device-scoped resources.\nGeneral Requirements Test initialization failure and partial cleanup paths where recoverable failures are possible. Verify that repeated cleanup does not violate the documented lifecycle contract. Test stale-handle rejection when generation-checked handles are used. Exercise container growth, removal, and relocation when references to stored elements are retained. Validate concurrent lookup and destruction when the subsystem permits those operations. Test subsystem shutdown with outstanding work when asynchronous execution is supported. Verify that plugin shutdown removes callbacks and registrations before unloading. Exercise GPU resource retirement under representative frames-in-flight and queue synchronization conditions. Use assertions and diagnostic instrumentation to detect violated ownership and lifetime contracts during development. Do not rely exclusively on assertions to prevent invalid resource access in production execution. Relevant tests SHOULD focus on the actual lifetime guarantees of the component rather than merely confirming that normal construction and destruction succeed.\nGuiding principle:\nOwnership and lifetime guarantees are correctness properties. They should be explicit in interfaces, enforced by implementations, and verified under failure, concurrency, and shutdown conditions.\n7. Functions, Parameters, and Return Values Functions in XII MUST express clear responsibilities, communicate their contracts through their interfaces, and preserve predictable behavior across supported platforms and configurations.\nFunction signatures should make dependencies, ownership, mutability, failure behavior, and observable side effects apparent wherever practical.\nThis section extends the principles established in §1.1 (Correctness), §1.2 (Ownership and Lifetime), §1.3 (Interface Design), §3 (Naming Conventions), and §5 (Types and Interfaces).\n7.1 Functions Should Perform One Coherent Operation A function SHOULD perform one coherent operation at a consistent level of abstraction.\nThis does not require every function to contain only a few statements. A substantial implementation may remain appropriate when its operations collectively fulfill one clearly defined responsibility.\nGeneral Requirements Functions MUST have a clearly identifiable purpose. Prefer separating operations with distinct failure, ownership, synchronization, or lifecycle semantics. Do not combine unrelated responsibilities merely to reduce the number of functions. Avoid extracting trivial wrappers that introduce navigation overhead without establishing a meaningful boundary. Functions SHOULD operate at a consistent level of abstraction. Prefer meaningful helper functions when they isolate reusable logic, enforce invariants, or clarify complex algorithms. Avoid excessive parameter lists when they indicate that a function performs unrelated operations or depends on too much external state. Do not split tightly coupled operations when doing so would expose invalid intermediate states or compromise correctness. Function names MUST accurately describe the work performed and its observable effects. Prefer:\nxiiResult LoadDocument(xiiStringView sPath); xiiResult ValidateDocument(); xiiResult SaveDocument(xiiStringView sPath); Each operation has a distinct responsibility and may encounter different failure conditions.\nA higher-level operation may coordinate these steps when appropriate:\nxiiResult OpenDocument(xiiStringView sPath) { XII_SUCCEED_OR_RETURN(LoadDocument(sPath)); XII_SUCCEED_OR_RETURN(ValidateDocument()); return XII_SUCCESS; } The coordinating function remains cohesive because its responsibility is opening a valid document.\nAvoid Excessive Fragmentation Do not extract every expression into a separate function merely to reduce function length.\nA helper function should provide at least one meaningful benefit:\nEncapsulation of a distinct responsibility. Reuse by multiple consumers. Enforcement of an invariant. Clarification of a complex algorithm. Isolation of platform-specific behavior. Separation of independently testable logic. Establishment of a meaningful failure or synchronization boundary. Function length alone MUST NOT determine whether an implementation should be divided.\nGuiding principle:\nA function should perform one coherent operation, but cohesion matters more than line count.\n7.2 Prefer Explicit Inputs and Dependencies Functions SHOULD receive the information required for their operation through explicit parameters, object state, or clearly documented subsystem dependencies.\nAvoid hidden mutable global state when an explicit dependency would improve correctness, reuse, or testability.\nGeneral Requirements Required inputs SHOULD be apparent from the function signature or the owning object\u0026rsquo;s documented state. Avoid retrieving mutable global state when the caller already possesses the required context. Do not introduce hidden dependencies on the active frame, current thread, global device, or singleton unless those dependencies are part of an established subsystem contract. Functions SHOULD NOT silently depend on unrelated initialization or execution state. Prefer passing the narrowest context required by the operation. Do not pass entire subsystem objects when only a small, well-defined portion of their capabilities is required, unless doing so is consistent with the architecture. Avoid introducing unnecessary parameter forwarding through multiple layers when the dependency naturally belongs to an owning component. Execution context MUST remain valid throughout the operation. Prefer:\nxiiResult Simulate(xiiSimulationWorld\u0026amp; world, const xiiSimulationSettings\u0026amp; settings, xiiTime deltaTime); Avoid:\nxiiResult Simulate() { auto* pWorld = GetGlobalActiveWorld(); auto* pSettings = GetGlobalSimulationSettings(); // Hidden dependencies on mutable global state. } The preferred interface makes its dependencies explicit and allows the operation to be invoked in different simulation contexts.\nCallback and Delegate Context Callbacks and delegates MUST receive or safely capture the context required for their execution.\nFor example:\nusing SimulationDelegate = xiiDelegate\u0026lt;void(xiiSimulationWorld\u0026amp;, const xiiSimulationSettings\u0026amp;, xiiTime)\u0026gt;; A delegate MUST NOT retain borrowed references beyond their guaranteed lifetime.\nWhen callbacks execute asynchronously or survive subsystem transitions, their captured ownership and executable-code lifetime must follow §6.11.\nGuiding principle:\nDependencies should be visible at the boundary where they matter. Hidden state should not determine behavior that callers reasonably expect to control.\n7.3 Parameter Passing Parameter types MUST accurately express input semantics, mutability, optionality, and ownership.\nChoose parameter-passing mechanisms according to the type\u0026rsquo;s size, semantics, lifetime requirements, and intended use.\nParameter category Preferred form Rationale Small scalar values By value Simple semantics without unnecessary indirection Enum values and handles By value Clear identity and inexpensive copying Small mathematical types By value when appropriate Avoid unnecessary indirection where copying is efficient Large read-only objects const T\u0026amp; Avoid copying while prohibiting mutation through the reference Required mutable objects T\u0026amp; Express non-null borrowed access with permitted mutation Required read-only objects const T\u0026amp; Express non-null borrowed access without mutation Optional mutable objects T* Allow absence while permitting modification Optional read-only objects const T* Allow absence without permitting modification String inputs xiiStringView where appropriate Accept supported string representations without ownership transfer Collection inputs xiiArrayPtr\u0026lt;T\u0026gt; or xiiArrayPtr\u0026lt;const T\u0026gt; as appropriate Express non-owning contiguous access Output parameters T\u0026amp; or T* with documented output semantics Communicate produced values Input/output parameters Mutable reference or pointer Communicate intentional modification of existing state Ownership transfer Movable owning type Make transfer of ownership explicit Callable inputs Appropriate callable or delegate type Communicate invocation and lifetime requirements General Requirements Prefer pass-by-value for inexpensive value-like types. Avoid copying large objects when borrowed access is sufficient. Use const references for read-only access to objects that are expensive or inappropriate to copy. Use mutable references only when mutation is part of the function\u0026rsquo;s contract. Prefer references for required objects and pointers for optional objects, unless existing API conventions establish different semantics. Do not use non-const references merely to avoid copying when the function does not modify the argument. Do not use output parameters when a direct return value is simpler and communicates the result adequately. Ownership transfer MUST be explicit and consistent with the owning type. Non-owning parameter views MUST NOT be retained beyond their documented lifetime. Parameter types SHOULD avoid unnecessarily constraining callers to a particular container implementation. Prefer:\nxiiResult CalculateBounds(xiiArrayPtr\u0026lt;const xiiVec3\u0026gt; positions, xiiBoundingBox\u0026amp; out_Bounds); This signature communicates that the function reads the positions and writes the resulting bounding box.\nPass-by-Value Versus References Pass-by-value is not automatically faster for every small type.\nCalling conventions, alignment, register availability, and the type\u0026rsquo;s copy semantics can affect performance.\nFor mathematical and SIMD types, follow established XII conventions and measured performance requirements.\nDo not mechanically replace const T\u0026amp; with T based solely on sizeof(T).\nGuiding principle:\nParameter types should communicate how data is used, whether it may change, and whether its ownership is transferred.\n7.4 Output and Input/Output Parameters Output parameters SHOULD use descriptive names that distinguish produced values from ordinary inputs.\nXII commonly uses the out_ and inout_ naming conventions. These are recommended documentation conventions rather than mandatory type-dependent prefix requirements for all parameters.\nNaming Conventions Use out_ for parameters whose primary purpose is to receive a produced value. Use inout_ for parameters whose existing value is intentionally read and modified. Names SHOULD retain applicable type-dependent prefixes after the direction marker. Do not use output-direction prefixes for parameters that are merely mutable implementation details. Output parameters MUST have documented validity guarantees on success and failure. Prefer direct return values when the function produces one simple result and does not require independent failure reporting. Example:\nxiiResult GetTaskCompletion(xiiTaskHandle hTask, xiiUInt64\u0026amp; out_uiCompletionValue); For an input/output parameter:\nvoid NormalizeWeights(xiiArrayPtr\u0026lt;float\u0026gt; inout_Weights); Failure Semantics When a function returns xiiResult and also writes output parameters, the contract MUST establish whether those outputs are modified when the operation fails.\nWhere practical, prefer leaving output parameters unchanged on failure.\nPrefer:\nxiiResult ParseCount(xiiStringView sText, xiiUInt32\u0026amp; out_uiCount) { xiiUInt32 uiParsedCount = 0U; XII_SUCCEED_OR_RETURN(ParseUnsignedInteger(sText, uiParsedCount)); out_uiCount = uiParsedCount; return XII_SUCCESS; } The output is committed only after parsing succeeds.\nThis provides a clear failure guarantee without requiring the caller to inspect partially modified output state.\nMultiple Outputs Several related output values may indicate that a small result structure would provide a clearer interface.\nHowever, do not introduce a new structure merely to eliminate two straightforward output parameters.\nChoose the representation that best communicates the operation\u0026rsquo;s contract.\nGuiding principle:\nOutput parameters must make data flow and failure behavior explicit. Prefer committing observable changes only after successful completion when practical.\n7.5 const Correctness const correctness MUST be maintained throughout function interfaces and implementations.\nRead-only access should be expressed through the type system wherever practical.\nGeneral Requirements Member functions that do not modify observable object state SHOULD be marked const. Read-only reference parameters SHOULD use const T\u0026amp;. Read-only pointer parameters SHOULD use const T*. Avoid exposing mutable access through const interfaces unless the operation has a justified logical-constness contract. Do not use const_cast to bypass interface restrictions without a concrete and documented reason. Prefer immutable local values when reassignment is unnecessary and immutability improves clarity. Use mutable only for members whose modification is compatible with the type\u0026rsquo;s logical constness. A const member function MUST NOT be assumed thread-safe solely because it is declared const. Prefer:\nclass xiiSimulationClock { public: XII_ALWAYS_INLINE xiiUInt64 GetStepCount() const { return m_uiStepCount; } private: xiiUInt64 m_uiStepCount = 0U; }; Logical Constness A const method may legitimately update internal implementation details, such as a cache or statistics counter, when those changes do not violate the method\u0026rsquo;s observable contract.\nSuch mutation MUST remain safe under the type\u0026rsquo;s concurrency guarantees.\nFor example, lazily populating a mutable cache inside a const function can introduce data races if multiple threads invoke the function concurrently.\nShallow Constness const qualification does not necessarily make all indirectly referenced objects immutable.\nA const smart pointer or const container holding pointers may still permit mutation of the pointed-to objects, depending on its API.\nInterfaces MUST consider the actual mutability of reachable state rather than relying exclusively on top-level const.\nGuiding principle:\nUse const to express permitted mutation accurately, but do not confuse logical immutability with thread safety or transitive immutability.\n7.6 [[nodiscard]] and Return-Value Significance Return values that communicate failure, ownership, identity, or other correctness-critical information SHOULD be marked [[nodiscard]].\nThe attribute helps identify accidental result omission but does not replace explicit error-handling contracts.\nGeneral Requirements Functions returning a failure-sensitive result SHOULD use [[nodiscard]] where the relevant result type is not already annotated. Resource creation functions returning owning objects or handles SHOULD use [[nodiscard]]. Functions returning values whose omission would likely indicate a programming error SHOULD use [[nodiscard]]. Do not apply [[nodiscard]] mechanically to every trivial getter without considering existing API conventions. Do not suppress return-value diagnostics merely to satisfy compilation. If ignoring a result is intentional, use the established mechanism and ensure that doing so is safe under the operation\u0026rsquo;s contract. [[nodiscard]] MUST NOT be treated as a guarantee that callers cannot discard the result. Prefer:\nxiiResult InitializeSubsystem(); // xiiResult is [[nodiscard]] by convention. [[nodiscard]] xiiResourceHandle CreateResource(const xiiResourceDescriptor\u0026amp; descriptor); [[nodiscard]] bool IsResourceValid(xiiResourceHandle hResource) const; An operation returning an important result should make its expected usage apparent.\nType-Level Attributes If xiiResult or another return type is already annotated with [[nodiscard]], repeating the attribute on every function may be unnecessary.\nContributors SHOULD follow the conventions established by the relevant XII type and surrounding APIs.\nGuiding principle:\nImportant return values should be difficult to ignore accidentally. The type system and compiler diagnostics should reinforce explicit handling.\n7.7 Results, Failures, and Error Propagation Expected runtime failures MUST be represented through explicit error mechanisms, consistent with §1.1.\nxiiResult is appropriate when an operation needs to communicate success or failure without returning detailed error information directly.\nGeneral Requirements Functions that may legitimately fail MUST provide an observable failure mechanism. Failure-sensitive results MUST NOT be silently discarded. Check operation results before executing dependent operations. Prefer early failure propagation when the current function cannot meaningfully recover. Use established XII result macros and methods where they improve clarity. Do not return XII_SUCCESS unless the operation has fulfilled its documented success conditions. Error logging SHOULD occur where sufficient diagnostic context exists. Avoid logging the same failure repeatedly at every propagation layer. Do not use xiiResult to conceal programming errors or violated API preconditions. Failure paths MUST preserve the documented invariants and cleanup guarantees of the owning component. Prefer:\nxiiResult LoadConfiguration(xiiStringView sPath) { xiiFileReader file; XII_SUCCEED_OR_RETURN(file.Open(sPath)); return ParseConfiguration(file); } Result Propagation Use XII_SUCCEED_OR_RETURN when the called operation\u0026rsquo;s failure can be propagated without additional handling.\nXII_SUCCEED_OR_RETURN(InitializeStorage()); XII_SUCCEED_OR_RETURN(LoadResources()); XII_SUCCEED_OR_RETURN(ValidateState()); When additional diagnostic information or recovery behavior is required, use an explicit check.\nif (LoadConfiguration(sPath).Failed()) { xiiLog::Error(\u0026#34;Failed to load configuration from \u0026#39;{0}\u0026#39;.\u0026#34;, sPath); return XII_FAILURE; } Ignoring Results Use .IgnoreResult() only when intentionally discarding the result is permitted by the operation\u0026rsquo;s contract.\nAvoid:\nSaveConfiguration(sPath).IgnoreResult(); // A required save may have failed silently. Ignoring a result can be appropriate when failure is genuinely inconsequential or explicitly handled through another mechanism.\nSuch decisions SHOULD be clear from the surrounding implementation or documented where necessary.\nRich Error Information xiiResult communicates success or failure but does not inherently carry a detailed failure reason.\nWhen callers need structured diagnostics, error categories, partial results, or recovery information, use an appropriate result structure or explicit error output.\nDo not introduce an elaborate generic error framework when a simple xiiResult and contextual diagnostic are sufficient.\nGuiding principle:\nFailure is part of an operation\u0026rsquo;s contract. Propagate it explicitly, preserve invariants, and report useful diagnostics without unnecessary complexity.\n7.8 Default Arguments Default arguments SHOULD represent common, safe, and unsurprising behavior.\nThey are appropriate when omitting an argument has a stable meaning that is understandable from the function\u0026rsquo;s name and contract.\nGeneral Requirements Default arguments MUST preserve valid behavior. Defaults SHOULD represent the most common or least surprising operation. Avoid defaults that depend on hidden mutable state or ambiguous execution context. Do not use default arguments to conceal required dependencies. Avoid long parameter lists containing multiple unrelated defaulted values. Boolean parameters SHOULD be avoided when their meaning is unclear at the call site. Prefer a configuration structure when an operation has several independently configurable options. Changes to public default arguments MUST consider source compatibility and behavioral effects on existing callers. Default arguments MUST NOT be redeclared inconsistently across declarations. Prefer:\nxiiResult StartSimulation(xiiUInt32 uiMaxIterations = 128U); Avoid:\nStartSimulation(true, false, true, 128U); The latter obscures the purpose of each boolean argument.\nConfiguration Structures When several independent options are required, a configuration structure can provide clearer call sites and allow future extension.\nPrefer:\nstruct xiiSimulationOptions { bool m_bEnableTracing = false; bool m_bAllowParallelism = true; xiiUInt32 m_uiMaxIterations = 128U; }; xiiResult StartSimulation(const xiiSimulationOptions\u0026amp; options); However, do not introduce a configuration structure for every function with two or three clearly understood parameters.\nDefault Arguments and Virtual Functions Default arguments are resolved statically according to the declaration visible at the call site, while virtual dispatch selects the implementation dynamically.\nTherefore, default arguments SHOULD NOT differ across overrides.\nWhere virtual interfaces require default behavior, consider a non-virtual convenience wrapper that delegates to the virtual operation with explicit arguments.\nGuiding principle:\nDefaults should simplify common use without concealing meaning, dependencies, or behavioral differences.\n7.9 Overloads and Operation Semantics Function overloads SHOULD represent the same conceptual operation applied to different supported argument forms.\nDo not use overloads to conceal significantly different behavior, ownership requirements, or side effects.\nGeneral Requirements Overloads MUST preserve a coherent semantic relationship. Functions with substantially different responsibilities SHOULD use distinct names. Overloads MUST NOT unexpectedly change ownership transfer or lifetime semantics. Avoid overload sets that create ambiguous conversions or surprising overload resolution. Do not introduce overloads solely to avoid explicit conversions when those conversions clarify intent. Prefer consistent failure and return-value semantics across related overloads. Overload implementations SHOULD share a common implementation where doing so reduces duplication without introducing unnecessary indirection. Changes to overload sets MUST consider their effects on existing call sites and implicit conversions. Prefer:\nxiiResult LoadResource(xiiStringView sPath); xiiResult LoadResource(const xiiResourceDescriptor\u0026amp; descriptor); Both functions perform resource loading using different forms of input.\nAvoid:\nvoid Process(xiiResourceHandle hResource); void Process(xiiUInt32 uiMilliseconds); The two operations have unrelated meanings despite sharing a name.\nLifecycle Semantics An operation named ExecuteTask() should not implicitly initialize an entire task system or advance an unrelated scheduler unless that behavior is part of its documented contract.\nPrefer distinct operations for distinct lifecycle stages.\nGuiding principle:\nOverloading should provide alternative ways to perform the same operation, not hide unrelated behavior behind a common name.\n7.10 noexcept and Exception Boundaries XII does not permit C++ exceptions for ordinary engine error handling.\nExpected failures MUST be represented through explicit mechanisms such as xiiResult, error outputs, or appropriately designed result types.\nExceptions may be encountered within deeply isolated platform or third-party integration code where their use is unavoidable. Such exceptions must not escape into ordinary engine-facing APIs.\nGeneral Requirements Engine-facing functions MUST NOT use exceptions to communicate expected runtime failures. Do not introduce throw expressions into ordinary engine code. Use noexcept only when the operation can satisfy the exception guarantee under its supported execution environments. Do not annotate functions noexcept merely because the current compiler configuration disables exceptions. Functions marked noexcept MUST NOT permit exception propagation from platform or third-party operations. Exception-generating dependencies MUST be isolated at appropriate integration boundaries. Where exception handling is available and unavoidable, caught failures SHOULD be translated into the engine\u0026rsquo;s established error-reporting mechanisms. Destructors MUST NOT propagate exceptions. Do not use exception specifications as substitutes for documenting ordinary failure behavior. Platform Exception Boundaries Where a third-party or platform API may throw exceptions, the integration layer should contain the exception mechanism when the build configuration supports catching those exceptions.\nThe translated failure should be returned through an ordinary engine-compatible mechanism.\nIf the relevant environment does not permit exception handling, the integration must use an appropriate non-throwing API or another explicitly supported boundary strategy.\nGuiding principle:\nExpected failures are explicit results. Exception behavior, when unavoidable, must remain isolated from ordinary XII engine contracts.\n7.11 Returning References, Pointers, and Views Returned pointers, references, and views MUST have clear validity and invalidation guarantees.\nA returned reference communicates access, not ownership.\nThe relevant lifetime and borrowing requirements are established in §§1.2 and 6.3–6.8.\nGeneral Requirements Never return references or views into local temporary storage. Returned borrowed access MUST remain valid for the documented duration. Do not return mutable references to internal state when doing so permits callers to violate invariants. Prefer returning small value-like objects directly when copying is inexpensive. Use handles when independently managed identity and validation are required. Do not return pointers into containers without considering relocation and removal invalidation. Access to shared mutable objects MUST respect their synchronization requirements. Prefer scoped access or controlled operations when a returned pointer cannot safely outlive an internal lock. Do not expose borrowed access across asynchronous boundaries without sufficient lifetime guarantees. Avoid:\nconst xiiString\u0026amp; GetName() const { xiiString sName = BuildName(); return sName; } The returned reference refers to an object destroyed when the function returns.\nPrefer:\nxiiString GetName() const { return BuildName(); } Returning an owning value avoids the lifetime violation.\nInternal Storage Returning references to internal storage may be appropriate for stable, read-only access when the owning object\u0026rsquo;s lifetime and invalidation rules are explicit.\nHowever, a const reference is not automatically safe if another thread can mutate or destroy the referenced object.\nGuiding principle:\nReturned access must remain valid for as long as callers are permitted to use it. Ownership, invalidation, and concurrency guarantees must be explicit.\n7.12 Inline and Force-Inline Functions Inlining decisions SHOULD be based on language requirements, implementation clarity, and demonstrated performance needs.\nThe inline keyword and compiler-specific force-inline mechanisms serve different purposes and must not be treated as interchangeable guarantees.\nGeneral Requirements Use ordinary inline where required for header-defined non-template functions with external linkage. Small, stable accessors MAY be defined directly within class declarations. Non-trivial non-template implementations SHOULD remain in .cpp files unless header placement is justified. Use XII_ALWAYS_INLINE sparingly and consistently with established performance-sensitive code. Do not assume that ordinary inline guarantees machine-code inlining. Do not use force-inlining to compensate for inefficient algorithms or unnecessary abstraction layers. Avoid excessive header-defined implementation code that increases compilation time and dependency propagation. Performance-motivated force-inlining SHOULD be supported by measurements, consistent with §1.5. Inlining decisions MUST NOT violate linkage, One Definition Rule (ODR), or binary interface requirements. Prefer:\nclass xiiTaskCounter { public: XII_ALWAYS_INLINE xiiUInt32 GetCount() const { return m_uiCount; } private: xiiUInt32 m_uiCount = 0U; }; The accessor is trivial and suitable for a header definition.\nFor larger implementations, prefer ordinary out-of-line definitions.\nForce-Inlining Trade-offs Force-inlining may reduce call overhead in appropriate hot paths, but it can also:\nIncrease generated code size. Increase instruction-cache pressure. Increase compilation time. Duplicate substantial implementation code. Prevent the compiler from making more effective code-size trade-offs. An aggressively inlined implementation is not necessarily faster.\nGuiding principle:\nInline for correctness and clarity where required; force-inline only when the performance benefit justifies the cost.\n7.13 Function Preconditions and Postconditions Function contracts MUST distinguish required preconditions from expected runtime failures.\nA precondition describes what must be true for a valid call. A postcondition describes the guarantees established after an operation completes.\nGeneral Requirements Preconditions SHOULD be apparent from the interface or documented where they are not obvious. Contract violations SHOULD be detected through appropriate assertions or explicit enforcement mechanisms. Expected runtime failures MUST use the applicable error-reporting mechanism. Successful operations MUST establish their documented postconditions. Failed operations MUST preserve their documented failure guarantees. Do not use assertions as substitutes for validating untrusted input. Do not return XII_FAILURE for every programmer error merely to avoid defining correct API contracts. Functions MUST NOT silently accept invalid states that compromise internal invariants. Example:\nvoid SetCapacity(xiiUInt32 uiCapacity) { XII_ASSERT_DEV(uiCapacity \u0026gt; 0U, \u0026#34;Capacity must be greater than zero.\u0026#34;); m_uiCapacity = uiCapacity; } Here, a positive capacity is a programmer-facing precondition.\nBy contrast:\nxiiResult LoadSettings(xiiStringView sPath) { xiiFileReader file; XII_SUCCEED_OR_RETURN(file.Open(sPath)); return ParseSettings(file); } File opening and parsing can fail during otherwise valid calls and therefore expose expected runtime failures.\nGuiding principle:\nPreconditions define valid calls. Results communicate expected failure. Postconditions define what callers may rely on after completion.\n7.14 Side Effects and Observable Behavior Function names and signatures SHOULD communicate meaningful side effects.\nAn operation\u0026rsquo;s observable behavior must be consistent with the expectations established by its interface.\nGeneral Requirements Query functions SHOULD NOT modify externally observable state unless their contract explicitly permits it. Functions that mutate state SHOULD use names that communicate the operation being performed. Do not conceal resource allocation, synchronization, scheduling, or substantial I/O behind apparently trivial accessors. Operations that may block for significant or unbounded periods SHOULD make that behavior apparent through their naming or documented contract. Avoid unexpected global state changes within functions that appear to operate on local data. Side effects MUST remain consistent across overloads that represent the same conceptual operation. Functions SHOULD avoid partially committing externally observable state before recoverable validation has completed. Prefer:\nbool IsTaskComplete(xiiTaskHandle hTask) const; xiiResult WaitForTask(xiiTaskHandle hTask); These names distinguish a non-blocking query from an operation that may wait.\nAvoid:\nbool GetTaskComplete(xiiTaskHandle hTask); If the implementation also waits for the task to finish, its name conceals important execution behavior.\nGuiding principle:\nCallers should not need to inspect an implementation to discover consequential side effects.\n7.15 Function Complexity and Reviewability Functions SHOULD remain understandable as cohesive units of behavior.\nComplexity should be controlled through appropriate decomposition, explicit state transitions, and meaningful abstraction boundaries rather than arbitrary limits on function length.\nGeneral Requirements Avoid deeply nested control flow when guard clauses or restructuring can simplify the implementation. Avoid functions that coordinate unrelated responsibilities or manipulate unrelated subsystem state. Prefer descriptive intermediate variables when they clarify non-obvious expressions. Do not introduce unnecessary temporaries or helpers that make straightforward logic harder to follow. Keep resource acquisition, use, and release relationships visible. Error paths SHOULD remain easy to follow and verify. Complex algorithms SHOULD document important invariants and non-obvious reasoning. Do not introduce generic abstractions solely to reduce apparent function complexity. Refactoring MUST preserve externally observable behavior unless a behavioral change is intentional. A long function implementing one cohesive algorithm may be clearer than several tightly coupled functions that require readers to reconstruct control flow across multiple files.\nConversely, a short function may still be poorly designed if it combines unrelated responsibilities or conceals significant side effects.\nGuiding principle:\nOptimize functions for comprehension and correctness, not arbitrary line counts. Structure should reveal intent, dependencies, and failure behavior.\n8. Control Flow and Expressions Control flow and expressions in XII MUST be explicit, predictable, and correct across supported platforms, compilers, and execution environments.\nPrefer straightforward branching, well-defined evaluation semantics, and mathematically sound operations over compact expressions that obscure program behavior.\nControl-flow decisions should preserve the invariants established in §1.1, follow the statement conventions in §2.4, and respect the error-handling requirements in §7.7.\n8.1 Prefer Early Exits and Guard Clauses Guard clauses SHOULD be used to handle invalid inputs, unmet preconditions, and recoverable failures before entering the main body of an operation.\nEarly exits reduce unnecessary nesting and make the successful execution path easier to understand.\nGeneral Requirements Prefer early returns when they simplify control flow and make failure conditions immediately apparent. Validate untrusted inputs before using them in operations that require specific ranges or invariants. Distinguish programmer contract violations from expected runtime failures, as established in §1.1. Do not use deeply nested conditionals when guard clauses express the same behavior more clearly. Error paths MUST preserve resource ownership, synchronization, and cleanup guarantees. Prefer RAII for resources that require cleanup across multiple return paths. Do not introduce multiple early returns when they obscure a required common finalization step. Avoid redundant validation when a preceding operation already establishes the necessary invariant. Prefer:\nxiiResult ProcessDocument(xiiStringView sPath) { if (sPath.IsEmpty()) return XII_FAILURE; xiiFileReader file; XII_SUCCEED_OR_RETURN(file.Open(sPath)); return ParseDocument(file); } Avoid:\nxiiResult ProcessDocument(xiiStringView sPath) { if (!sPath.IsEmpty()) { xiiFileReader file; if (file.Open(sPath).Succeeded()) { return ParseDocument(file); } } return XII_FAILURE; } The preferred implementation expresses the unsuccessful conditions first and leaves the primary operation at the function\u0026rsquo;s normal indentation level.\nCleanup and Early Returns Early returns MUST NOT bypass required cleanup.\nWhere cleanup is necessary, prefer scoped ownership or another established resource-management mechanism rather than duplicating cleanup logic across failure branches.\nA function with a single exit point may be preferable when a required finalization step cannot be represented safely through RAII.\nGuiding principle:\nReject invalid states early and keep the successful path clear, but never simplify control flow at the expense of cleanup or invariants.\n8.2 Use the Simplest Appropriate Branching Construct Branching constructs SHOULD match the nature of the decision being expressed.\nDo not introduce runtime polymorphism, template machinery, or complicated conditional expressions when ordinary control flow adequately expresses the requirement.\nGeneral Requirements Use if and else for general conditional logic. Use switch when selecting among discrete values, particularly enum states. Use if constexpr when compile-time conditions determine valid template implementations. Prefer ordinary compile-time constants over preprocessor conditionals when C++ language constructs are sufficient. Avoid long chains of runtime type checks when a genuine polymorphic interface would better represent the required behavior. Do not introduce inheritance or virtual dispatch solely to replace a small, stable conditional. Branches MUST preserve the same documented invariants regardless of the selected path. Exhaustive branching over enum values SHOULD make newly introduced values detectable through compiler diagnostics or review. Discrete Alternatives Prefer:\nxiiResult ProcessTask(xiiTaskState::Enum state) { switch (state) { case xiiTaskState::Pending: return QueueTask(); case xiiTaskState::Running: return UpdateTask(); case xiiTaskState::Completed: return FinalizeTask(); } return XII_FAILURE; } When an enum is intended to be exhaustive, explicitly listing its supported values makes the contract easier to inspect.\nA final failure return can handle invalid numeric representations without hiding missing cases behind a default branch.\nWhether compiler warnings detect new enum values depends on the compiler and configured warning policy.\nDefault Cases A default branch is appropriate when unknown values have a meaningful, documented fallback.\nPrefer when unknown values are expected:\nswitch (message.m_Type) { case xiiMessageType::Connect: HandleConnection(); break; case xiiMessageType::Disconnect: HandleDisconnection(); break; default: HandleUnknownMessage(message); break; } Do not use a silent default merely to suppress warnings when every known enum value requires explicit handling.\nCompile-Time Branching Use if constexpr when template logic depends on compile-time type properties.\ntemplate \u0026lt;typename T\u0026gt; void ResetValue(T\u0026amp; value) { if constexpr (std::is_trivially_copyable_v\u0026lt;T\u0026gt;) { value = T{}; } else { value.Reset(); } } This example assumes the supported non-trivially-copyable types provide Reset().\nCompile-time branching MUST still establish a valid contract for every supported template instantiation.\nGuiding principle:\nChoose control flow according to the decision being made. Prefer explicit branches over unnecessary dispatch machinery.\n8.3 Loops and Iteration Iteration MUST preserve container invariants, element validity, and any ordering guarantees required by the operation.\nSelect the loop construct that most clearly communicates whether the operation depends on elements, indices, or iterator behavior.\nGeneral Requirements Prefer range-based loops when only element access is required. Use index-based loops when the index has semantic meaning or is necessary for the algorithm. Use explicit iterators when container mutation, traversal rules, or specialized iteration requires them. Do not repeatedly perform expensive computations in loop conditions when their results can be safely reused. Avoid unnecessary allocations, copies, and indirect access in demonstrated hot loops. Do not mutate a container during iteration unless the iteration pattern explicitly supports the mutation. Container removal MUST account for iterator, pointer, reference, and index invalidation. Do not assume that container removal preserves order unless the selected operation guarantees it. Loop termination MUST be well-defined, including for empty collections and boundary values. Avoid modifying the loop control variable unexpectedly inside the body. Element-Based Iteration Prefer:\nfor (const xiiResourceEntry\u0026amp; entry : m_Entries) { ProcessEntry(entry); } Use mutable references only when modification is intended.\nfor (xiiResourceEntry\u0026amp; entry : m_Entries) { entry.m_bActive = false; } Index-Based Iteration Use indices when element positions are part of the operation\u0026rsquo;s meaning.\nfor (xiiUInt32 uiIndex = 0; uiIndex \u0026lt; m_Entries.GetCount(); ++uiIndex) { UpdateEntry(uiIndex, m_Entries[uiIndex]); } Index types SHOULD be compatible with the container\u0026rsquo;s supported index range.\nAvoid narrowing a container\u0026rsquo;s element count into a smaller integer type without establishing that the value fits.\nRemoval During Iteration Removal operations may shift elements, replace removed elements with the last element, or invalidate iterators.\nFor example, when removal order is unimportant and RemoveAtAndSwap() provides the expected semantics:\nfor (xiiUInt32 uiIndex = 0; uiIndex \u0026lt; m_Entries.GetCount();) { if (m_Entries[uiIndex].m_bExpired) { m_Entries.RemoveAtAndSwap(uiIndex); continue; } ++uiIndex; } The index is not advanced following removal because another element may have been moved into the current position.\nDo not use swap-based removal when preserving the order of surviving elements is a documented requirement.\nLoop Progress Every potentially unbounded loop MUST have a defined progress or termination mechanism.\nThis is especially important for polling, task scheduling, synchronization, and resource reclamation.\nBusy-wait loops SHOULD NOT be introduced when an appropriate blocking or event-driven mechanism is available.\nGuiding principle:\nIterate according to the semantics of the data and the operation. Container mutation, ordering, and progress must remain explicit.\n8.4 Comparators and Ordering Contracts Comparators used by sorting and ordered containers MUST satisfy the ordering requirements of the relevant algorithm.\nFor standard strict-ordering algorithms, the comparator must establish a strict weak ordering over the supported input domain.\nGeneral Requirements Strict ordering comparators MUST return false when comparing equivalent values. Do not use \u0026lt;= or \u0026gt;= as strict ordering comparators. Comparators MUST produce consistent results for unchanged inputs. Comparator behavior MUST NOT depend on mutable state that can change during sorting. Avoid comparators with observable side effects. Do not modify compared elements from within the comparator. Sorting MUST NOT be assumed stable unless the algorithm explicitly guarantees stability. Tie-breaking behavior MUST be defined when equal primary keys require deterministic ordering. Comparators involving floating-point values MUST account for NaNs or establish a valid input domain that excludes them. Prefer:\nm_Entries.Sort([](const auto\u0026amp; a, const auto\u0026amp; b) { return a.m_iPriority \u0026gt; b.m_iPriority; }); This sorts entries by descending priority.\nHowever, it does not guarantee the relative order of entries with equal priorities if the sorting algorithm is unstable.\nDeterministic Tie-Breaking When deterministic ordering among equal priorities is required, use an explicit secondary key.\nm_Entries.Sort([](const auto\u0026amp; a, const auto\u0026amp; b) { if (a.m_iPriority != b.m_iPriority) return a.m_iPriority \u0026gt; b.m_iPriority; return a.m_uiRegistrationOrder \u0026lt; b.m_uiRegistrationOrder; }); The registration order must represent a stable and well-defined value for the relevant sorting operation.\nDo not introduce artificial secondary keys unless deterministic tie ordering is a genuine requirement.\nInvalid Comparators Avoid:\nm_Entries.Sort([](const auto\u0026amp; a, const auto\u0026amp; b) { return a.m_iPriority \u0026gt;= b.m_iPriority; }); For equal values, the comparator returns true in both directions.\nThis violates the strict ordering requirements and can result in undefined behavior when used with sorting algorithms that require strict weak ordering.\nFloating-Point Ordering Ordinary floating-point \u0026lt; does not establish strict weak ordering over a domain containing NaNs.\nWhen sorting floating-point data that may contain NaNs, define an explicit ordering policy or reject invalid values before sorting.\nDo not use approximate equality in a comparator without verifying that the resulting relation still satisfies the ordering requirements.\nGuiding principle:\nOrdering is an algorithmic contract. Comparators must be mathematically valid, deterministic for their supported inputs, and consistent with required tie semantics.\n8.5 Boolean Logic and Conditional Expressions Boolean expressions SHOULD make their intended meaning and evaluation order apparent.\nPrefer straightforward predicates over compact expressions that require careful reconstruction of operator precedence.\nGeneral Requirements Use parentheses when mixing logical operators in expressions whose grouping may be misinterpreted. Prefer descriptive intermediate predicates for complex domain-specific conditions. Avoid unnecessary negations and double-negative state names. Do not introduce intermediate boolean variables for every trivial condition. Do not rely on implicit precedence when explicit grouping materially improves clarity. Avoid boolean expressions that combine unrelated side effects. Use short-circuit operators deliberately when later operands depend on earlier conditions. Conditional expressions MUST preserve the documented evaluation and failure semantics. Prefer:\nconst bool bValidSession = session.IsActive() \u0026amp;\u0026amp; session.GetSessionId() == uiExpectedSession; const bool bHasDependencies = pScheduler != nullptr \u0026amp;\u0026amp; pResourceManager != nullptr; if (!bValidSession || !bHasDependencies) return XII_FAILURE; This separates session validity from dependency availability.\nShort-Circuit Evaluation Logical \u0026amp;\u0026amp; and || operators evaluate their operands with defined short-circuit behavior.\nPrefer:\nif (pResource != nullptr \u0026amp;\u0026amp; pResource-\u0026gt;IsReady()) { ProcessResource(*pResource); } The resource is accessed only when the pointer is non-null.\nDo not replace logical operators with bitwise operators when short-circuit evaluation is required.\nAvoid Redundant Predicates Avoid:\nif (bInitialized == true) { Execute(); } Prefer:\nif (bInitialized) { Execute(); } For negative conditions:\nif (!bInitialized) return XII_FAILURE; Do not sacrifice readability merely to eliminate parentheses or intermediate variables.\nGuiding principle:\nBoolean logic should communicate domain meaning clearly while preserving correct precedence and short-circuit behavior.\n8.6 Avoid Hidden Mutations and Evaluation-Order Dependencies Expressions MUST NOT rely on undefined or unspecified evaluation behavior.\nSide effects should be explicit, appropriately sequenced, and easy to inspect.\nGeneral Requirements Do not modify the same scalar object multiple times in an expression when the modifications are unsequenced. Avoid combining resource creation, validation, mutation, and consumption in unnecessarily complex expressions. Prefer separate statements when evaluation order affects correctness. Do not rely on compiler-specific operand evaluation behavior. Function arguments SHOULD NOT contain interdependent side effects whose order matters. Increment and decrement operations SHOULD be used where their effects are obvious. Do not use comma expressions to combine unrelated operations. Side effects MUST remain visible when failure handling or synchronization depends on their ordering. Avoid:\nProcessValues(++uiIndex, uiIndex++); The expression modifies the same variable through multiple arguments and obscures the intended sequencing.\nPrefer:\n++uiIndex; const xiiUInt32 uiCurrentIndex = uiIndex; ++uiIndex; ProcessValues(uiCurrentIndex, uiIndex); The intended sequence is explicit.\nResource Creation and Validation Prefer:\nxiiResourceHandle hResource = CreateResource(descriptor); if (hResource.IsInvalidated()) return XII_FAILURE; InitializeResource(hResource); Avoid:\nInitializeResource(CreateResource(descriptor)); The avoided form prevents the caller from inspecting creation failure before attempting initialization.\nThis separation is especially important when the second operation requires a valid handle.\nGuiding principle:\nObservable state changes should occur in an explicit, understandable sequence. Expression compactness must not conceal evaluation or failure behavior.\n8.7 Integer Conversions and Arithmetic Integer arithmetic MUST respect the range, signedness, and representation requirements of the values being manipulated.\nOverflow and narrowing errors can lead to invalid allocations, incorrect indexing, corrupted resource descriptions, and undefined behavior.\nGeneral Requirements Use integer types whose widths and signedness match the domain\u0026rsquo;s required range. Check for overflow before calculations whose results control allocation sizes, offsets, strides, or buffer capacities. Do not assume that widening the destination prevents overflow if intermediate arithmetic occurs in a narrower type. Validate potentially negative signed values before converting them to unsigned types. Narrowing conversions MUST be intentional and range-checked when the source may exceed the destination range. Use explicit casts where they communicate intentional conversions. Avoid unnecessary casts that conceal incorrect type choices. Division and modulo operations MUST establish valid nonzero divisors. Signed arithmetic MUST NOT rely on overflow. Unsigned wraparound MUST be intentional and documented when its behavior is required. Integer comparisons SHOULD avoid unintended signed/unsigned conversions. Avoid arithmetic involving invalid sentinel values unless the sentinel has been checked. Multiplication Overflow Prefer:\nif (uiStride == 0U) return XII_FAILURE; if (uiElementCount \u0026gt; xiiMath::MaxValue\u0026lt;xiiUInt64\u0026gt;() / uiStride) return XII_FAILURE; const xiiUInt64 uiByteCount = static_cast\u0026lt;xiiUInt64\u0026gt;(uiElementCount) * uiStride; The example assumes that both operands are unsigned and that zero stride is invalid under the operation\u0026rsquo;s contract.\nChecking before multiplication prevents the result from wrapping.\nIntermediate Arithmetic Avoid:\nxiiUInt32 uiWidth = descriptor.m_uiWidth; xiiUInt32 uiHeight = descriptor.m_uiHeight; xiiUInt64 uiPixelCount = uiWidth * uiHeight; The multiplication may overflow at the 32-bit width before its result is assigned to a 64-bit variable.\nPrefer:\nconst xiiUInt64 uiPixelCount = static_cast\u0026lt;xiiUInt64\u0026gt;(descriptor.m_uiWidth) * descriptor.m_uiHeight; Widening an operand before multiplication causes the multiplication to occur in the wider type.\nThis is sufficient only when the resulting value is guaranteed to fit within that type. Otherwise, an overflow check is still required.\nNarrowing Conversions Prefer:\nif (uiElementCount \u0026gt; xiiMath::MaxValue\u0026lt;xiiUInt32\u0026gt;()) return XII_FAILURE; const xiiUInt32 uiCount = static_cast\u0026lt;xiiUInt32\u0026gt;(uiElementCount); Do not perform narrowing conversions merely to satisfy a function signature without confirming that the value fits.\nSigned and Unsigned Values Avoid:\nxiiInt32 iOffset = GetOffset(); xiiUInt32 uiOffset = static_cast\u0026lt;xiiUInt32\u0026gt;(iOffset); If iOffset is negative, conversion to an unsigned type produces an unintended large value.\nPrefer:\nconst xiiInt32 iOffset = GetOffset(); if (iOffset \u0026lt; 0) return XII_FAILURE; const xiiUInt32 uiOffset = static_cast\u0026lt;xiiUInt32\u0026gt;(iOffset); Division and Modulo Prefer:\nif (uiBucketCount == 0U) return XII_FAILURE; const xiiUInt32 uiBucketIndex = uiHash % uiBucketCount; Never assume a divisor is nonzero merely because normal execution typically supplies a valid value.\nWhen nonzero is a programmer-facing precondition rather than recoverable input validation, enforce it according to §7.13.\nGuiding principle:\nInteger correctness requires control over widths, ranges, and conversions. Validate arithmetic before a result can overflow, narrow incorrectly, or violate an invariant.\n8.8 Floating-Point Arithmetic and Comparisons Floating-point calculations MUST account for finite precision, rounding behavior, and the possibility of special values.\nNumerical code should use comparison and tolerance policies appropriate to the domain rather than assume that floating-point values behave like exact real numbers.\nGeneral Requirements Do not use exact equality for approximate numerical results unless exact representation or identity is part of the contract. Use tolerances appropriate to the scale, precision, and expected error of the operation. Avoid using a single universal epsilon for unrelated numerical domains. Consider absolute and relative tolerances when comparing values of different magnitudes. Handle NaNs and infinities when they may arise from external inputs or calculations. Do not assume floating-point expressions are associative. Avoid transformations that change numerical behavior unless their accuracy and performance implications are understood. Numerical algorithms SHOULD account for loss of precision, cancellation, and accumulation error where relevant. Do not rely on exact reproducibility across platforms unless the required compiler, instruction, and floating-point behavior is explicitly controlled. Floating-point values used in ordering, hashing, or identity comparisons MUST follow the requirements of those operations. Approximate Equality Avoid:\nif (fCalculatedValue == fExpectedValue) { AcceptResult(); } Prefer when approximate equality is intended:\nconst float fDifference = xiiMath::Abs(fCalculatedValue - fExpectedValue); const float fScale = xiiMath::Max(xiiMath::Abs(fCalculatedValue), xiiMath::Abs(fExpectedValue)); const float fTolerance = fAbsoluteTolerance + fRelativeTolerance * fScale; if (fDifference \u0026lt;= fTolerance) { AcceptResult(); } This comparison combines absolute and relative tolerances.\nIt assumes the inputs and tolerance parameters satisfy the algorithm\u0026rsquo;s finite-value and nonnegative-tolerance requirements. Validate those assumptions where they are not guaranteed.\nExact Equality Exact floating-point equality may be appropriate for:\nComparing values that are required to match an exact representation. Checking values assigned from the same defined constant. Certain sentinel values where their meaning is explicitly defined. Algorithms whose mathematical contract requires bitwise or exact-value identity. Do not replace every floating-point equality comparison with an epsilon check mechanically.\nNaNs and Infinities NaN values have unusual comparison semantics, including the fact that ordinary equality and ordering comparisons do not behave like comparisons over real numbers.\nWhen finite values are required, validate them before performing calculations that depend on finite arithmetic.\nif (!xiiMath::IsFinite(fDeltaTime)) return XII_FAILURE; This assumes the operation treats non-finite time values as invalid input.\nNumerical Stability Prefer numerically stable formulations when equivalent mathematical expressions exhibit different error behavior.\nFor iterative simulations, reductions, and long-running computations, consider how rounding error accumulates over time.\nReordering floating-point operations for performance MUST account for its effect on numerical accuracy and reproducibility.\nGuiding principle:\nFloating-point correctness is defined by acceptable numerical error and explicit domain assumptions, not by the appearance of mathematical equivalence.\n8.9 Switch Fallthrough Fallthrough between switch cases MUST be intentional and immediately recognizable.\nUse the standard [[fallthrough]] attribute where supported and consistent with the surrounding code.\nGeneral Requirements Implicit fallthrough MUST NOT occur accidentally. Intentional fallthrough SHOULD use [[fallthrough]]. Do not rely solely on comments to indicate fallthrough when the compiler supports the standard attribute. Each case SHOULD terminate through break, return, another control transfer, or explicit fallthrough. Avoid fallthrough when duplicating a small amount of straightforward code would communicate the behavior more clearly. Multiple case labels sharing the same body do not require [[fallthrough]] between consecutive labels. Fallthrough MUST NOT bypass required initialization or violate variable scope rules. Prefer:\nswitch (state) { case xiiTaskState::Failed: LogTaskFailure(); [[fallthrough]]; case xiiTaskState::Cancelled: ReleaseTaskResources(); break; case xiiTaskState::Completed: FinalizeTask(); break; } The failure case performs additional logging before intentionally sharing cleanup behavior with cancellation.\nShared Case Bodies When multiple values require identical behavior, group their labels.\nswitch (state) { case xiiTaskState::Failed: case xiiTaskState::Cancelled: ReleaseTaskResources(); break; case xiiTaskState::Completed: FinalizeTask(); break; } This is not an implicit fallthrough error; both labels intentionally select the same statements.\nGuiding principle:\nSwitch cases should have explicit termination behavior. Intentional fallthrough must be visible to both readers and compiler diagnostics.\n8.10 Conditional Compilation and Feature Selection Conditional compilation SHOULD be limited to cases where behavior genuinely differs by platform, compiler, build configuration, or compile-time feature availability.\nPrefer ordinary C++ constructs when they provide equivalent functionality without introducing unnecessary preprocessor complexity.\nGeneral Requirements Use established XII feature and platform macros rather than inventing alternative detection mechanisms. Follow the repository\u0026rsquo;s conventions for XII_ENABLED and related compile-time feature checks. Do not use runtime checks for features that are intentionally unavailable in a given build configuration when compile-time exclusion is required. Do not use preprocessor conditionals when if constexpr or ordinary constant expressions provide a clearer solution. Platform-specific code SHOULD remain within the appropriate platform implementation rather than spread throughout portable modules. Conditional branches MUST preserve the documented interface contract across supported configurations. Avoid deeply nested preprocessor conditionals. Build configurations MUST NOT silently change public type layouts or exported interfaces when those interfaces require binary compatibility. Example:\n#if XII_ENABLED(XII_PLATFORM_WINDOWS) InitializeWindowsPlatform(); #elif XII_ENABLED(XII_PLATFORM_LINUX) InitializeLinuxPlatform(); #else InitializeGenericPlatform(); #endif The exact feature macros and supported fallbacks must follow the existing repository definitions.\nCompile-Time Versus Runtime Capabilities Compile-time availability and runtime capability are separate concepts.\nA feature may be compiled into the engine but remain unsupported by the active device or execution environment.\nUse runtime capability queries when feature support depends on detected hardware, drivers, or system configuration.\nDo not assume that compilation of a backend implies support for every optional feature.\nGuiding principle:\nUse conditional compilation to express build-time availability and runtime checks to express actual capabilities. Keep both mechanisms explicit and appropriately scoped.\n8.11 Assertions and Unreachable States Assertions SHOULD be used to detect violated internal invariants and programmer-facing preconditions.\nThey MUST NOT replace ordinary error handling for expected runtime failures.\nGeneral Requirements Assertions MUST express conditions expected to hold during every valid execution of the relevant code path. Do not rely on assertions to perform required initialization, mutation, or cleanup. Do not place essential side effects inside assertion expressions. Expected failures caused by external data or environmental conditions MUST be handled through the appropriate error mechanism. Unreachable-state assumptions MUST be justified by established invariants. Do not introduce compiler-specific unreachable hints when an invalid state can legitimately occur. Assertions SHOULD include diagnostics that identify the violated contract. Do not use assertions merely to suppress handling of a state that the public API permits. Prefer:\nXII_ASSERT_DEV(uiIndex \u0026lt; m_Entries.GetCount(), \u0026#34;Resource index is outside the valid range.\u0026#34;); ProcessEntry(m_Entries[uiIndex]); This is appropriate when the caller is required by contract to supply a valid internal index.\nFor externally supplied indices, use explicit runtime validation instead.\nEssential Side Effects Avoid:\nXII_ASSERT_DEV(InitializeResources().Succeeded(), \u0026#34;Initialization failed.\u0026#34;); If assertions are disabled, the initialization operation may not execute.\nPrefer:\nconst xiiResult result = InitializeResources(); XII_ASSERT_DEV(result.Succeeded(), \u0026#34;Initialization failed.\u0026#34;); This version preserves execution of the operation regardless of whether the assertion is active.\nIf initialization failure is an expected runtime condition, the failure should instead be propagated or handled explicitly.\nGuiding principle:\nAssertions verify invariants; they must never be required for correct execution or substitute for recoverable error handling.\n8.12 Expression Complexity and Readability Expressions SHOULD remain understandable without requiring readers to reconstruct several independent operations or subtle language rules.\nPrefer explicit intermediate values when they clarify units, ownership, ordering, or domain meaning.\nGeneral Requirements Avoid excessively nested conditional operators. Do not combine unrelated calculations and side effects into a single expression. Prefer named intermediate values when they clarify important assumptions or units. Avoid introducing intermediate values that merely duplicate obvious expressions. Use parentheses to make non-obvious precedence explicit. Prefer ordinary statements over complicated expression constructs when side effects or failure behavior are involved. Expression refactoring MUST preserve evaluation order and numerical behavior where those characteristics are part of the contract. Do not assume that algebraically equivalent expressions have identical integer-overflow or floating-point behavior. Avoid:\nconst xiiUInt32 uiResult = bEnabled ? (bReady ? uiPrimary : uiFallback) : (bDefault ? uiDefault : 0U); Prefer:\nxiiUInt32 uiResult = 0U; if (bEnabled) { uiResult = bReady ? uiPrimary : uiFallback; } else if (bDefault) { uiResult = uiDefault; } The second form makes each decision explicit.\nA simple conditional expression remains appropriate when it selects between two straightforward values.\nconst xiiUInt32 uiCapacity = bUseLargeBuffer ? uiLargeCapacity : uiSmallCapacity; Do not replace every conditional operator with a multi-line branch mechanically.\nGuiding principle:\nExpressions should make calculations and decisions easier to understand, not compress multiple responsibilities into fewer lines.\n8.13 Determinism and Observable Ordering Operations whose correctness depends on execution order MUST establish that order explicitly.\nThis applies to scheduling, event processing, serialization, reductions, resource transitions, and other systems where observable results depend on sequencing.\nGeneral Requirements Do not assume iteration order is deterministic unless the container explicitly guarantees it. Do not rely on hash-table iteration order for behavior requiring stable ordering. Sorting MUST establish the required tie-breaking semantics when equivalent keys have observable consequences. Concurrent execution MUST NOT be assumed deterministic without an explicit synchronization or scheduling contract. Side-effect ordering MUST remain well-defined when observable behavior depends on it. Serialization and persistent formats MUST use stable ordering where the format requires reproducible output. Do not introduce global ordering requirements when operations are independent and the additional synchronization would be unnecessary. Performance optimizations that reorder operations MUST preserve required observable semantics. Container Ordering Avoid when stable output is required:\nfor (const auto\u0026amp; entry : m_ResourceMap) { SerializeResource(entry); } A hash-based container may not provide a stable iteration order.\nWhere deterministic serialization is required, collect and order the relevant entries according to a defined key before serializing them.\nParallel Execution Concurrent operations may complete in different orders across executions.\nWhen completion order affects externally visible state, the implementation must either enforce a deterministic order or explicitly document that ordering is not guaranteed.\nDo not impose determinism on unrelated independent operations unless it is a genuine system requirement.\nGuiding principle:\nOrdering must be intentional. When behavior depends on sequence, establish that sequence through documented contracts rather than incidental container or scheduling behavior.\n9. Modern C++23 Features XII targets C++23 and should take advantage of modern language facilities when they improve correctness, expressiveness, maintainability, or measurable performance.\nModern C++ features MUST be adopted deliberately. Language availability alone does not justify introducing a feature when an existing engine abstraction or simpler construct already expresses the intended behavior.\nThe requirements in this section complement §1.1 (Correctness), §1.5 (Performance), §5 (Type Design), §6 (Ownership and Lifetime), and §7 (Function Interfaces).\n9.1 Use Modern C++ When It Improves Clarity C++23 is XII\u0026rsquo;s language baseline, but the availability of a feature in the language standard does not guarantee complete support across all compilers, standard libraries, or target platforms.\nGeneral Requirements Modern C++ features SHOULD be preferred when they provide clearer semantics, stronger compile-time guarantees, or simpler implementations. Features MUST be supported by the minimum compiler and standard-library versions required by XII. Do not introduce language features merely because they are newer than an existing implementation. Prefer standard language facilities over custom implementations when the standard facility meets the engine\u0026rsquo;s requirements without introducing incompatible dependencies. Existing XII abstractions SHOULD remain authoritative where they provide engine-specific allocation, reflection, serialization, or platform behavior. Do not introduce new generic wrappers around standard language features without a demonstrated requirement. Avoid depending on compiler extensions in portable engine code unless those extensions are isolated behind established platform abstractions. Changes to compiler or standard-library requirements MUST consider every supported platform and build configuration. Modernization MUST NOT alter observable behavior, ownership semantics, or binary compatibility unintentionally. Feature Availability Before introducing a less commonly used C++23 feature, consider:\nWhether every supported compiler accepts the required syntax. Whether the required standard-library implementation is available. Whether the feature interacts correctly with XII\u0026rsquo;s build configurations. Whether it introduces additional dependencies or binary compatibility concerns. Whether the resulting implementation is clearer than the existing alternative. Feature-test macros SHOULD be used when conditional support is necessary and consistent with the repository\u0026rsquo;s platform configuration system.\nGuiding principle:\nModern C++ should simplify engineering, not complicate it. Prefer language features that strengthen contracts and reduce unnecessary implementation complexity.\n9.2 Type Deduction with auto Use auto when type deduction improves readability without concealing important semantic information.\nExplicit types remain valuable when they communicate precision, ownership, resource identity, or conversion behavior.\nGeneral Requirements Use auto when the type is immediately apparent from the initializer. Prefer explicit types when the type communicates meaningful domain semantics. Use auto for iterators, verbose template instantiations, and generic code where spelling the complete type adds little value. Do not use auto merely to avoid understanding the type returned by an operation. Avoid deduced types when unintended signedness, precision, copying, or reference removal could affect correctness. Use auto\u0026amp; and const auto\u0026amp; when reference semantics are intended. Avoid unintended copies caused by plain auto when the initializer produces a reference. Public interfaces SHOULD expose concrete return types when those types form part of the documented contract. Do not use abbreviated function templates accidentally when a non-template interface is intended. Prefer when the type communicates meaning:\nconst xiiUInt64 uiSimulationStep = simulation.GetStepCount(); xiiUniquePtr\u0026lt;xiiSimulationData\u0026gt; pData = CreateSimulationData(); Prefer when the initializer makes the type obvious:\nauto pData = XII_DEFAULT_NEW(xiiSimulationData); auto it = m_Resources.GetIterator(); Reference Preservation Plain auto generally deduces a value type rather than preserving the reference category of the initializer.\nAvoid when mutation of the original is intended:\nauto entry = m_Entries[uiIndex]; entry.m_bActive = false; Prefer:\nauto\u0026amp; entry = m_Entries[uiIndex]; entry.m_bActive = false; When the type is short and meaningful, an explicit reference may be clearer:\nxiiResourceEntry\u0026amp; entry = m_Entries[uiIndex]; decltype(auto) Use decltype(auto) only when preserving the exact deduced reference or value category is necessary.\nIt SHOULD NOT be used as a general replacement for auto.\nA function returning decltype(auto) may unintentionally expose references to internal storage or temporary objects if its return expression is not carefully designed.\nAbbreviated Function Templates C++20 permits auto in function parameters to introduce template parameters implicitly.\nvoid ProcessValue(const auto\u0026amp; value) { // Generic implementation. } This is appropriate for genuinely generic functions, but not when the supported parameter types should be constrained by an explicit interface.\nGuiding principle:\nDeduce types when their meaning remains obvious. Spell them explicitly when they communicate ownership, precision, identity, or API contracts.\n9.3 constexpr, consteval, and constinit Compile-time facilities SHOULD be used when they improve correctness, eliminate unnecessary runtime work, or communicate initialization requirements.\nDo not introduce elaborate compile-time computations when ordinary constants or runtime initialization provide a simpler solution.\nconstexpr Use constexpr for values and functions that are intended to support constant evaluation.\nPrefer:\nstatic constexpr xiiUInt32 MaxWorkerThreads = 64U; static constexpr xiiUInt32 DefaultAlignment = 16U; Functions may also be declared constexpr when they can meaningfully operate in constant-evaluation contexts.\nconstexpr xiiUInt32 AlignUpPowerOfTwo(xiiUInt32 uiValue, xiiUInt32 uiAlignment) { return (uiValue + uiAlignment - 1U) \u0026amp; ~(uiAlignment - 1U); } This function requires a nonzero power-of-two alignment and arithmetic that does not overflow. Those preconditions must be established by its callers or enforced by an appropriate implementation.\nThe use of constexpr does not automatically guarantee that every invocation is evaluated at compile time.\nconsteval Use consteval when an operation MUST be evaluated at compile time.\nAppropriate applications may include compile-time validation, constant metadata generation, and calculations whose runtime invocation would violate the intended design.\nDo not use consteval merely because a function happens to support constant evaluation.\nconstinit Use constinit for static or thread-local variables that require static initialization without necessarily being immutable.\nconstinit xiiUInt32 s_uiInitializationCount = 0U; constinit guarantees static initialization but does not make the variable constant or thread-safe.\nGeneral Requirements Prefer constexpr over macros for ordinary typed compile-time constants. Use consteval only when compile-time evaluation is a required contract. Use constinit when preventing dynamic initialization is important. Do not confuse compile-time initialization with immutable state. Compile-time computation MUST preserve the same correctness requirements as runtime computation. Avoid excessive template instantiation and compile-time work without a demonstrated benefit. Do not use compile-time facilities to conceal complex initialization logic. Guiding principle:\nUse compile-time evaluation to enforce meaningful guarantees, not merely to move complexity from runtime into compilation.\n9.4 Strongly Typed Enums and Engine Enum Wrappers Enumeration types SHOULD express closed sets of discrete values with well-defined semantics.\nXII provides established enum wrapper and reflection patterns. These conventions take precedence when types participate in engine reflection, serialization, tooling, or configuration systems.\nGeneral Requirements Prefer strongly typed enum representations over unscoped global constants. Follow XII\u0026rsquo;s established enum wrapper conventions when reflection or serialization requires them. Enum values MUST use meaningful names consistent with their domain. Underlying storage types SHOULD reflect the required value range and serialization contract. Numeric enum values MUST NOT be changed casually when used in persistent formats or public binary interfaces. Do not assume enum declaration order represents execution order, priority, or dependency ordering. Do not introduce closed enum types for concepts that are intentionally extensible through registration. Invalid or unknown enum values MUST be handled according to the API\u0026rsquo;s contract. Enum conversion and validation SHOULD use established XII helpers where available. Engine Enum Wrappers Illustrative XII-style enum:\nstruct XII_CORE_DLL xiiTaskExecutionMode { using StorageType = xiiUInt8; enum Enum : StorageType { Immediate = 0U, Deferred, Parallel, ENUM_COUNT, Default = Deferred }; }; This example illustrates the enum declaration structure, not a requirement to introduce this particular type.\nThe wrapper\u0026rsquo;s associated reflection, conversion, and registration requirements must follow the conventions of the owning module.\nScoped Enums Standard enum class is appropriate for internal or standalone types that do not require an established XII enum wrapper.\nenum class ProcessingState : xiiUInt8 { Idle, Running, Completed }; Do not mechanically convert existing reflected enum wrappers into enum class without evaluating serialization, reflection, and compatibility requirements.\nEnum Exhaustiveness Switch statements over closed enums SHOULD make newly added values visible during maintenance.\nWhen every value requires explicit handling, avoid a silent default that conceals missing cases.\nSee §8.2 for the relevant control-flow requirements.\nGuiding principle:\nUse enums to represent genuinely closed sets of alternatives. Preserve XII\u0026rsquo;s reflection conventions and do not confuse numeric representation with semantic ordering.\n9.5 Concepts and Template Constraints Concepts and requires expressions SHOULD be used when they express meaningful template requirements and improve compiler diagnostics.\nConstraints should clarify what operations or properties a template requires rather than create unnecessary abstraction hierarchies.\nGeneral Requirements Use concepts when they communicate a reusable and meaningful semantic requirement. Prefer existing standard or XII concepts when they accurately express the required contract. Do not create a new named concept for every isolated type trait or trivial template condition. Constraints SHOULD reflect the operations and semantics actually required by the implementation. Do not use concepts merely to disguise an excessively generic interface. Constrained overloads MUST avoid unexpected ambiguity. Public template constraints SHOULD provide understandable diagnostics for unsupported types. Include the appropriate standard or engine headers explicitly when using constraint facilities. Avoid unnecessary template specialization when an ordinary function overload provides a clearer solution. Example:\n#include \u0026lt;concepts\u0026gt; template \u0026lt;typename T\u0026gt; concept UnsignedInteger = std::unsigned_integral\u0026lt;T\u0026gt;; This is an illustrative standard C++ concept and is not a requirement to duplicate it within XII.\nWhere the repository already provides suitable traits or constraints, prefer those established facilities.\nConstrained Functions template \u0026lt;std::unsigned_integral T\u0026gt; constexpr T CalculateMask(T uiBitIndex) { return T{1} \u0026lt;\u0026lt; uiBitIndex; } The caller must ensure that the shift count is smaller than the bit width of T.\nA type constraint alone does not establish every runtime precondition of the operation.\nSemantic Concepts A concept that merely checks whether a method exists does not automatically prove that an implementation satisfies the method\u0026rsquo;s semantic contract.\nFor example, a type with a Reset() method is not necessarily safe to reset in every execution state.\nDocument semantic requirements that cannot be enforced through compile-time syntax checks.\nGuiding principle:\nConcepts should make generic contracts clearer and invalid instantiations easier to diagnose, without introducing speculative template architecture.\n9.6 Ranges and Views Standard ranges and views MAY be used when they express data transformation or iteration more clearly than ordinary loops.\nLazy evaluation and borrowed storage introduce lifetime and mutation considerations that must be understood before adopting a ranges-based implementation.\nGeneral Requirements Prefer ordinary loops when they express the operation more clearly. Use ranges and views when composition, filtering, or transformation materially improves readability. Standard ranges operations MUST be supported by the repository\u0026rsquo;s required compiler and standard-library implementations. Do not retain lazy views beyond the lifetime of their backing storage. Container mutation MUST respect the iterator and reference validity requirements of the view. Avoid returning views that reference temporary or otherwise invalid storage. Do not assume a view owns the elements it exposes. Ranges-based implementations in performance-critical code SHOULD be evaluated for allocation behavior, generated code, and iteration overhead. Prefer XII container and view abstractions when interoperability, allocator requirements, or engine conventions make them more appropriate. Simple Iteration Prefer:\nfor (const xiiResourceEntry\u0026amp; entry : m_Entries) { if (!entry.m_bActive) continue; ProcessEntry(entry); } This loop is straightforward and does not benefit significantly from introducing a lazy range pipeline.\nRange Pipelines A ranges-based implementation may be appropriate when several transformations naturally compose.\nauto activeValues = values | std::views::filter([](const auto\u0026amp; value) { return value.IsActive(); }); The view does not necessarily own values.\nIts validity depends on the backing range, iterator stability, and the semantics of the selected adaptors.\nLazy Evaluation Lazy views may execute predicates or transformations when elements are accessed rather than when the view is constructed.\nSuch operations SHOULD NOT contain surprising side effects.\nDo not assume that iterating a view multiple times always repeats exactly the same observable behavior when its underlying data may change.\nGuiding principle:\nUse ranges when they clarify data processing, but preserve explicit control over lifetime, mutation, and evaluation semantics.\n9.7 Structured Bindings Structured bindings SHOULD be used when they make the components of a tuple-like value or aggregate easier to understand.\nThey should not conceal important ownership, reference, or lifetime behavior.\nGeneral Requirements Use descriptive binding names that communicate the meaning of each element. Prefer const auto\u0026amp; when iterating read-only tuple-like elements without copying. Use auto\u0026amp; when mutation of the original elements is intended. Avoid copying large elements inadvertently through plain auto. Do not use structured bindings merely to shorten code when an existing named type communicates the same information more clearly. Structured bindings MUST respect the lifetime and invalidation guarantees of their source objects. Do not assume every XII container supports standard tuple-like structured binding syntax. Illustrative standard C++ example:\nfor (const auto\u0026amp; [resourceName, resourceHandle] : resources) { ProcessResource(resourceName, resourceHandle); } This example assumes resources exposes suitable tuple-like elements.\nFor ordinary engine objects with descriptive member names, direct member access may be clearer.\nCopy Versus Reference Avoid when mutation is intended:\nauto [key, value] = *iterator; value = newValue; This may modify a local copy rather than the original stored value.\nPrefer when the underlying tuple-like element permits it:\nauto\u0026amp; [key, value] = *iterator; value = newValue; The exact behavior depends on the type returned by the iterator.\nGuiding principle:\nStructured bindings should expose the meaning of grouped values without obscuring whether those values are copied or borrowed.\n9.8 Lambdas, Captures, and Callbacks Lambdas SHOULD be used for localized behavior, callbacks, predicates, and small operations that naturally belong near their point of use.\nCapture semantics MUST remain explicit when ownership, asynchronous execution, or object lifetime is relevant.\nGeneral Requirements Prefer explicit captures for callbacks that may outlive the current scope. Avoid broad reference captures such as [\u0026amp;] in asynchronous or long-lived callbacks. A captured this pointer MUST remain valid for every possible invocation. Callback registrations MUST have a defined invalidation or unregistration mechanism when the event source may outlive the receiver. Do not capture stack references in deferred operations unless their lifetime is guaranteed. Prefer value or move captures when a callback must own the captured data. Do not use shared ownership merely to simplify callback lifetime management when explicit unregistration is sufficient. Callback implementations SHOULD remain small and cohesive. Substantial callback logic SHOULD be extracted into an appropriately named member or helper function when doing so improves clarity. Lambda return types SHOULD be explicit when deduction would obscure an important interface or conversion. Short-Lived Lambdas Prefer:\nm_Entries.Sort([](const auto\u0026amp; a, const auto\u0026amp; b) { return a.m_iPriority \u0026gt; b.m_iPriority; }); The lambda executes synchronously within the sorting operation and does not retain references beyond its intended scope.\nLong-Lived Callbacks m_EventHandler = [this](const xiiEventData\u0026amp; event) { HandleEvent(event); }; This is valid only when the callback registration and receiver lifetime are coordinated.\nThe handler MUST be removed or rendered permanently inactive before the receiver is destroyed if the event source could invoke it afterward.\nCapturing Ownership For deferred operations that require data to survive the scheduling scope, a move capture can make ownership transfer explicit.\nxiiString sMessage = \u0026#34;Task completed\u0026#34;; QueueTask([sMessage = std::move(sMessage)]() { LogMessage(sMessage); }); The task system must preserve the callable\u0026rsquo;s lifetime until it is invoked or discarded safely.\nAvoid Hidden Reference Captures Avoid:\nvoid QueueUpdate() { xiiUInt32 uiCounter = 0U; QueueTask([\u0026amp;]() { ++uiCounter; }); } The task may execute after the local variable has been destroyed.\nThis is a lifetime error, not merely a stylistic concern.\nGuiding principle:\nLambdas make behavior local, but captures create lifetime dependencies. Every retained callback must have an explicit ownership and invalidation contract.\n9.9 Move Semantics and Perfect Forwarding Move semantics SHOULD be used when they express meaningful ownership transfer or avoid unnecessary copying.\nA move operation must preserve the invariants of both the destination and the moved-from object.\nGeneral Requirements Prefer moving uniquely owned or expensive movable objects when ownership transfer is intended. Do not use std::move indiscriminately on inexpensive values. Do not assume that std::move itself performs a move; it is a cast enabling move construction or assignment where supported. Do not use std::move on a const object expecting ordinary mutable move semantics. Avoid using an object after it has been moved from unless its documented moved-from state permits the operation. Do not return std::move(local) mechanically when returning a local object by value. Use forwarding references and std::forward only when implementing genuine forwarding interfaces. Avoid introducing forwarding templates when ordinary overloads or concrete parameter types provide clearer contracts. Move operations MUST NOT invalidate externally registered identities or addresses unless the type explicitly supports that behavior. Ownership Transfer Prefer:\nxiiUniquePtr\u0026lt;xiiSimulationData\u0026gt; pData = CreateSimulationData(); m_pSimulationData = std::move(pData); The transfer of exclusive ownership is explicit.\nReturn-Value Optimization Prefer:\nxiiString BuildResourceName() { xiiString sName = \u0026#34;SimulationResource\u0026#34;; return sName; } Avoid:\nxiiString BuildResourceName() { xiiString sName = \u0026#34;SimulationResource\u0026#34;; return std::move(sName); } Applying std::move to a named local in a return statement can prevent named return-value optimization.\nThe ordinary return form allows the compiler to perform copy elision where permitted and use move construction where appropriate.\nPerfect Forwarding Use std::forward when preserving the value category of an argument is an actual requirement of a generic wrapper or constructor.\nDo not introduce variadic forwarding constructors without considering overload resolution, accidental conversions, and interactions with copy or move constructors.\nGuiding principle:\nMove semantics express transfer, not automatic performance. Move deliberately and preserve valid object states.\n9.10 Casts and Type Conversions Type conversions MUST be explicit when they may alter meaning, precision, representation, or ownership assumptions.\nPrefer the narrowest C++ cast that accurately expresses the intended conversion.\nGeneral Requirements Use static_cast for well-defined explicit numeric and related conversions. Use const_cast only when constness changes are explicitly justified and safe. Use reinterpret_cast only when low-level representation conversion is necessary and valid. Avoid C-style casts because they obscure the conversion category. Narrowing conversions MUST be range-checked when values may exceed the destination range. Do not use casts to suppress legitimate type mismatches. Prefer XII\u0026rsquo;s established checked casting or type conversion utilities where available. Pointer conversions MUST respect object lifetime, alignment, and type accessibility. Representation-dependent conversions MUST consider strict aliasing, object representation, and platform portability. Casts MUST NOT be used to bypass ownership or const-correctness contracts. Numeric Conversions Prefer:\nconst float fWidth = static_cast\u0026lt;float\u0026gt;(uiWidth); When converting to an integer type with a narrower range, validate that the original value is representable.\nDo not assume that an explicit cast makes an otherwise invalid conversion safe.\nreinterpret_cast Low-level systems code may require reinterpretation of memory representations, especially within platform integration, serialization, or hardware interfaces.\nSuch casts demand careful consideration of:\nAlignment requirements. Strict aliasing rules. Object lifetime. Pointer provenance and validity. Endianness. Representation compatibility. Bounds and allocation size. Where byte-level access is intended, prefer well-defined byte-oriented representations and copying facilities rather than pointer punning.\nstd::bit_cast C++20 provides std::bit_cast for copying object representations between eligible trivially copyable types of equal size.\nconst xiiUInt32 uiBits = std::bit_cast\u0026lt;xiiUInt32\u0026gt;(fValue); This example assumes the implementation uses a 32-bit float and that the target representation is supported.\nstd::bit_cast does not eliminate the need to reason about the validity of the destination representation.\nUse it when object-representation conversion is intended, rather than reinterpret_cast-based aliasing.\nGuiding principle:\nA cast should reveal a deliberate conversion. It must never conceal invalid assumptions about representation, lifetime, precision, or ownership.\n9.11 Standard Library and XII Abstractions The C++ standard library is available to XII, but engine-level APIs SHOULD use established XII abstractions when those abstractions define important architectural contracts.\nThis is particularly relevant to allocation, memory ownership, reflection, serialization, containers, strings, delegates, and platform interoperability.\nGeneral Requirements Prefer established XII types at shared engine interfaces when they provide required engine semantics. Do not introduce parallel abstraction families without a demonstrated need. Standard-library facilities MAY be used when they are appropriate and compatible with XII\u0026rsquo;s architecture. Avoid unnecessary conversion between standard and XII container or string types. Do not expose standard-library types across binary interfaces when doing so violates the module\u0026rsquo;s ABI requirements. Allocation and destruction MUST remain compatible with the relevant ownership model. Do not replace established XII types merely because a similar standard-library facility exists. Do not recreate standard-library functionality unnecessarily when the existing facility satisfies the requirements. Standard-library usage MUST respect the repository\u0026rsquo;s required compiler and standard-library versions. Include the required standard headers directly rather than relying on the PCH. Common Engine Types Responsibility Established XII abstraction Owning strings xiiString Non-owning string access xiiStringView Dynamic arrays xiiDynamicArray Non-owning contiguous arrays xiiArrayPtr Explicit success/failure xiiResult Callable delegates xiiDelegate Exclusive pointer ownership xiiUniquePtr Shared pointer ownership xiiSharedPtr Resource identity Appropriate XII handle type This table identifies commonly used engine abstractions rather than prohibiting corresponding standard-library types.\nInteroperability When integrating external libraries, standard types may be required by their APIs.\nPerform conversions at clearly defined boundaries when doing so prevents allocation, ownership, or representation assumptions from leaking through the engine.\nAvoid repeated conversions within established hot paths when direct interoperable representations are available.\nGuiding principle:\nPrefer consistency at engine boundaries and interoperability at integration boundaries. Use the abstraction whose contract matches the requirement.\n9.12 Coroutines and Asynchronous Code C++20 coroutines provide a language mechanism for suspending and resuming execution, but do not independently define scheduling, cancellation, thread safety, or resource ownership.\nCoroutines MUST NOT be introduced into XII without a clearly defined runtime and lifetime model.\nGeneral Requirements Coroutine use MUST have a defined executor or resumption policy. Suspension and resumption behavior MUST be documented. Objects referenced across suspension points MUST remain valid until their final use. Cancellation behavior MUST be explicit. Error propagation MUST follow XII\u0026rsquo;s no-exception policy. Coroutine frame ownership and destruction responsibilities MUST be defined. Shutdown MUST account for suspended and outstanding coroutines. Do not assume coroutines execute on a single thread unless the executor guarantees it. Do not assume a coroutine is thread-safe because its source code appears sequential. Avoid introducing coroutine abstractions when the existing task system adequately expresses the required operation. Do not retain raw engine pointers across suspension points without enforceable lifetime guarantees. Suspension and Lifetime A suspension point may allow the current scope\u0026rsquo;s external dependencies to change or disappear before the coroutine resumes.\nBorrowed references and pointers remain subject to their original lifetime contracts.\nIn particular, a coroutine that suspends while holding a mutex, reference into a mutable container, or device-owned resource may require additional coordination.\nCancellation Cancellation does not necessarily imply immediate completion.\nThe coroutine\u0026rsquo;s owning system MUST define whether cancellation:\nPrevents future resumption. Requests cooperative termination. Causes completion with a failure result. Requires explicit draining or destruction of the coroutine frame. Do not assume that destroying a cancellation token safely destroys all work associated with it.\nException Handling Coroutine implementations must not rely on exception propagation as their ordinary failure mechanism.\nPromise types and coroutine runtimes MUST respect the engine\u0026rsquo;s exception policy and supported compiler configurations.\nGuiding principle:\nCoroutines simplify the expression of asynchronous control flow, but do not remove the need for explicit scheduling, cancellation, ownership, or synchronization contracts.\n9.13 C++ Modules and Header Integration XII currently relies on a header-based compilation architecture involving module export macros, precompiled headers, reflection code, generated sources, and plugin interfaces.\nC++ modules MUST NOT be introduced piecemeal without an architectural and build-system plan.\nGeneral Requirements Do not migrate existing headers to C++ modules solely to adopt newer language features. Module adoption MUST account for all supported compilers and build systems. Generated reflection and serialization code MUST remain compatible with the proposed module architecture. DLL export/import behavior MUST be preserved. Precompiled header integration or replacement MUST be planned. Macro visibility and preprocessing requirements MUST be evaluated. Plugin and dynamically loaded module compilation MUST remain supported. Public interface compatibility MUST be considered before replacing established header contracts. Avoid maintaining duplicate header and module implementations without a clear transition plan. C++ module support MUST be validated across relevant platform and toolchain configurations before broad adoption. Migration Considerations A future C++ module strategy should evaluate:\nCompiler and standard-library module support. Build-system dependency scanning and compilation ordering. Binary module interface artifact management. Generated code and reflection integration. Exported C++ interfaces and dynamic linking. Platform-specific preprocessing. Third-party library compatibility. Incremental build performance. Developer tooling and IDE support. Compatibility with existing plugins and downstream consumers. C++ modules may improve dependency isolation and compilation performance, but those benefits must be demonstrated within XII\u0026rsquo;s actual build architecture.\nGuiding principle:\nLanguage modules are an architectural change, not a formatting change. Adopt them only through a coordinated migration that preserves the engine\u0026rsquo;s existing contracts.\n9.14 std::span, std::string_view, and Borrowed Standard Views Non-owning standard-library views MAY be used where they are appropriate, but their lifetime contracts must remain consistent with XII\u0026rsquo;s established borrowing conventions.\nThe ownership and view lifetime requirements of §6.8 apply equally to standard and engine-specific views.\nGeneral Requirements Prefer XII\u0026rsquo;s established view types at engine interfaces where they are part of the module contract. Standard-library views MUST NOT outlive their backing storage. Do not assume that std::span or std::string_view owns the referenced memory. Avoid returning views into temporary containers or strings. Conversions between XII and standard views MUST preserve bounds, representation, and lifetime validity. Do not introduce repeated view conversions when one representation can be used consistently. Mutable spans MUST be treated as borrowed mutable access. A const-qualified view object is not necessarily a view of const elements. Example:\nvoid ProcessSamples(std::span\u0026lt;const float\u0026gt; samples) { for (float fSample : samples) { ProcessSample(fSample); } } This is a standard-library example. Shared XII interfaces should normally use the established engine array-view convention where appropriate.\nGuiding principle:\nViews describe access to storage, not ownership of it. Their simplicity must not obscure lifetime or mutability.\n9.15 std::optional, std::variant, and Explicit State Representation Modern vocabulary types can make optional values and alternative states explicit.\nThey SHOULD be used when their semantics fit the problem and their adoption does not conflict with established XII abstractions.\nOptional Values Use an optional representation when absence is a meaningful value rather than an error.\nFor example, an optional configuration setting may legitimately be unspecified.\nDo not use optional values to conceal operations that should report failure.\nAlternatives Use a variant-like representation when a value may be one of a defined set of alternative types.\nA variant is not automatically preferable to a polymorphic interface, tagged engine structure, or existing reflection-compatible representation.\nGeneral Requirements Optional values MUST distinguish absence from valid values unambiguously. Variant alternatives SHOULD represent a meaningful closed set of possibilities. Do not introduce optional wrappers when an existing invalid-handle or sentinel contract is clearer and already established. Do not use variant types as replacements for extensible runtime registries without considering their closed nature. Public vocabulary types MUST be compatible with reflection, serialization, and ABI requirements. Do not introduce unnecessary allocations or conversions to represent simple state. State alternatives MUST have documented validity and transition rules. Guiding principle:\nMake absence and alternatives explicit when they are real domain concepts, but avoid introducing additional state representations without a concrete benefit.\n9.16 Three-Way Comparison and Ordering C++20 provides the three-way comparison operator (\u0026lt;=\u0026gt;) and associated comparison categories.\nThese facilities MAY be used when they simplify well-defined value ordering and equality semantics.\nGeneral Requirements Comparison operators MUST reflect the type\u0026rsquo;s semantic identity or ordering contract. Do not generate ordering operators automatically when member declaration order does not represent meaningful comparison order. Defaulted equality SHOULD be considered for simple value-like types when memberwise equality is correct. Do not use three-way comparison merely to reduce the number of explicitly written operators. Comparison categories MUST accurately represent the supported ordering semantics. Floating-point comparison MUST account for partial ordering caused by NaNs. Resource handles and identity-bearing objects SHOULD use comparison semantics consistent with their documented identity rules. Ordering used by sorting and ordered containers MUST satisfy the required algorithmic contracts. Example:\nstruct xiiVersion { xiiUInt32 m_uiMajor = 0U; xiiUInt32 m_uiMinor = 0U; xiiUInt32 m_uiPatch = 0U; auto operator\u0026lt;=\u0026gt;(const xiiVersion\u0026amp;) const = default; }; The defaulted comparison is appropriate if versions are intentionally ordered lexicographically by major, minor, and patch values.\nIt would be inappropriate if the type required a different domain-specific ordering.\nGuiding principle:\nGenerate comparisons when memberwise semantics are correct. Otherwise, implement the actual domain contract explicitly.\n9.17 Attributes and Compile-Time Diagnostics Standard C++ attributes SHOULD be used when they communicate meaningful intent or enable useful diagnostics.\nThey must not replace the underlying correctness requirements.\nCommon Attributes Attribute Intended use [[nodiscard]] Warn when an important return value is discarded [[maybe_unused]] Mark intentionally unused declarations [[deprecated]] Identify APIs scheduled for replacement or removal [[fallthrough]] Document intentional switch fallthrough [[likely]] Express an expected branch probability [[unlikely]] Express an expected uncommon branch General Requirements Use [[nodiscard]] according to §7.6. Use [[fallthrough]] according to §8.9. Do not introduce [[maybe_unused]] merely to conceal obsolete code or unused dependencies. Deprecation attributes SHOULD provide a useful migration message where supported. Branch-likelihood attributes SHOULD be reserved for cases supported by representative profiling or established workload knowledge. Do not use [[likely]] or [[unlikely]] as substitutes for profiling. Attributes MUST remain compatible with the repository\u0026rsquo;s required compilers. Do not stack attributes mechanically when they provide no practical benefit. Branch Prediction Example:\nif (bUnexpectedFailure) [[unlikely]] { HandleFailure(); } This syntax communicates an expected uncommon branch.\nHowever, compiler behavior varies, and branch-likelihood hints are not guaranteed to improve performance.\nUse them only when their assumptions are justified and their effect is appropriate for the relevant workload.\nGuiding principle:\nAttributes should communicate meaningful contracts or evidence-based assumptions, not decorate code without purpose.\n9.18 Compile-Time Reflection and Metaprogramming Boundaries Modern C++ template facilities can generate types, select implementations, and enforce compile-time constraints.\nThese capabilities SHOULD remain focused on requirements that genuinely benefit from compile-time reasoning.\nGeneral Requirements Prefer ordinary C++ constructs when they express the same behavior more clearly. Do not introduce complex template metaprogramming solely to eliminate small amounts of duplication. Compile-time mechanisms SHOULD provide understandable diagnostics for invalid use. Avoid unnecessary instantiation depth and excessive compile-time computation. Template-generated interfaces MUST preserve the intended ownership, lifetime, and type contracts. Compile-time metadata MUST NOT be confused with XII\u0026rsquo;s established runtime reflection system. Do not replace engine reflection or serialization registration with unrelated template machinery without a coordinated architectural change. Metaprogramming abstractions SHOULD have concrete consumers and demonstrate a reduction in overall complexity. Compile-Time Versus Runtime Reflection XII\u0026rsquo;s runtime reflection and registration mechanisms may support capabilities that ordinary C++ compile-time type inspection does not provide, including dynamic discovery, serialization, tooling, and plugin interoperability.\nCompile-time traits and concepts may complement these systems but do not automatically replace them.\nGuiding principle:\nCompile-time abstraction is justified when it strengthens contracts or eliminates meaningful runtime complexity. It should not become a parallel architecture without a demonstrated need.\n10. Memory, Allocators, and Containers Memory management in XII MUST be explicit, predictable, and appropriate for the lifetime, access patterns, and performance requirements of the owning subsystem.\nXII provides specialized containers, allocators, ownership abstractions, and resource-management facilities. These should be selected according to their actual semantics rather than convenience, familiarity, or speculative performance assumptions.\nThis section extends §1.2 (Ownership and Lifetime), §1.5 (Optimization), §6 (Initialization, Ownership, and Lifetime), and §8 (Control Flow and Expressions).\n10.1 Choose Storage According to Access Patterns Container selection SHOULD be driven by the operations performed most frequently and the guarantees required by consumers.\nNo single container is optimal for every workload. Consider access complexity, iteration patterns, memory locality, allocation behavior, reference stability, and modification frequency.\nGeneral Requirements Prefer contiguous storage when sequential iteration, indexed access, and cache locality dominate. Prefer associative containers when lookup by key is a primary operation. Use handle-based storage when independently managed identity and stale-reference detection are required. Use stable-address storage when external references must survive container growth or relocation. Do not introduce multiple authoritative representations of the same data without a clear consistency and ownership model. Container selection MUST account for insertion, removal, relocation, and invalidation semantics. Avoid choosing containers solely by asymptotic complexity without considering practical workload characteristics. Prefer established XII containers over introducing incompatible storage abstractions without a demonstrated need. Changes to container representation MUST preserve the documented interface and lifetime guarantees. Requirement Typical approach Important consideration Frequent sequential iteration xiiDynamicArray\u0026lt;T\u0026gt; Growth and removal may relocate elements Fixed-size storage xiiStaticArray\u0026lt;T, N\u0026gt; or appropriate fixed-capacity type Capacity and overflow behavior must be understood Small collections with occasional growth Suitable XII hybrid-array type Verify inline capacity and allocator behavior Lookup by stable logical identity xiiIdTable Handle validity does not automatically guarantee concurrent lifetime safety Hash-based lookup xiiHashTable Hashing, equality, memory overhead, and iteration order matter Ordered associative access Appropriate ordered container Ordering requirements and insertion costs must be justified Stable heap allocation xiiUniquePtr\u0026lt;T\u0026gt; stored in a collection Address remains stable only while the allocation exists Non-owning contiguous access xiiArrayPtr\u0026lt;T\u0026gt; Backing storage must remain valid Temporary allocations Appropriate arena or frame allocator Allocations must not outlive allocator reset Independently managed resources Registry with generation-checked handles Ownership and synchronization remain separate concerns These are typical approaches, not mandatory mappings. The selected container must satisfy the actual subsystem contract.\nAccess Pattern Considerations Before selecting a container, consider:\nIteration: Is the collection frequently traversed sequentially? Lookup: Are elements located by index, identifier, key, or predicate? Mutation: How frequently are elements inserted, removed, or reordered? Identity: Must references remain valid independently of storage position? Ordering: Is element order meaningful or externally observable? Memory: What allocation, fragmentation, and locality characteristics matter? Concurrency: Are operations performed concurrently, and what synchronization is required? Scale: What are the representative and worst-case element counts? Do not replace a straightforward container with a complex indexing structure when measurements do not justify the additional state and maintenance cost.\nGuiding principle:\nSelect containers according to the operations and guarantees they must support, not merely their familiarity or theoretical complexity.\n10.2 Allocation on Performance-Critical Paths Dynamic allocation can introduce latency, fragmentation, synchronization, and unpredictable execution costs.\nAllocation behavior SHOULD be considered when designing frequently executed operations, particularly simulation updates, resource processing, rendering, task scheduling, and other latency-sensitive workloads.\nGeneral Requirements Avoid unnecessary heap allocations in demonstrated hot paths. Prefer retaining and reusing storage when its lifetime and memory cost justify doing so. Use pooled or arena-based allocation when allocation patterns and lifetimes are compatible with those mechanisms. Batch allocations when doing so reduces measurable overhead without obscuring ownership. Do not introduce custom allocators solely because an object is performance-sensitive. Allocation optimizations SHOULD be supported by profiling or representative allocation measurements. Avoid hidden allocations in frequently invoked interfaces when their cost is significant. Temporary storage MUST remain valid for the entire duration of its use. Memory reuse MUST NOT expose stale object state or violate construction and destruction requirements. Reusing Storage Prefer when repeated allocation is measurable:\nvoid CollectVisibleObjects(xiiArrayPtr\u0026lt;const xiiObject\u0026gt; pObjects) { m_VisibleObjects.Clear(); for (const xiiObject* pObject : pObjects) { if (pObject-\u0026gt;IsVisible()) { m_VisibleObjects.PushBack(pObject-\u0026gt;GetHandle()); } } } The collection retains its allocated capacity when Clear() preserves storage according to the container\u0026rsquo;s contract.\nThis can avoid repeated allocation across successive calls.\nThe retained capacity also consumes memory between uses. Reuse is appropriate only when that trade-off is justified.\nAllocation Strategies Common allocation strategies include:\nStack allocation: Appropriate for bounded, scope-local objects. General-purpose heap allocation: Appropriate for independently managed objects with less predictable lifetimes. Pool allocation: Appropriate for frequently created objects with compatible allocation sizes or reuse characteristics. Arena allocation: Appropriate for groups of allocations sharing a common lifetime. Frame allocation: Appropriate for temporary data guaranteed not to survive the frame allocator\u0026rsquo;s reset boundary. Persistent reserved storage: Appropriate for collections that repeatedly operate near a known capacity. These strategies are not interchangeable.\nFor example, frame allocation is unsafe when the allocated data is retained by an asynchronous task or GPU submission beyond the frame allocator\u0026rsquo;s validity period.\nAllocation in Real-Time Systems Performance-sensitive systems SHOULD consider not only average allocation time but also latency variation and contention.\nAn allocator that provides good average throughput may still introduce unacceptable stalls under certain workloads.\nWhere predictable execution latency is required, measure allocation behavior under representative contention, object sizes, and allocation lifetimes.\nGuiding principle:\nReduce allocation overhead where it matters, but do not introduce complex memory-management machinery without evidence that it improves the intended workload.\n10.3 Capacity, Reservation, and Container Growth Container capacity and element count represent different properties and MUST NOT be treated interchangeably.\nCapacity describes how much storage may be available without additional allocation. Count describes the number of logically contained elements.\nGeneral Requirements Reserve capacity when expected element counts are known and avoiding growth is beneficial. Do not reserve excessive memory without considering actual workload requirements. Avoid repeatedly reserving the same capacity in frequently executed operations. Do not assume that reserved capacity represents initialized or constructed elements. Container access MUST remain within the valid element count. Growth policies SHOULD balance allocation frequency against retained memory consumption. Capacity reductions SHOULD be intentional, particularly for repeatedly reused containers. Do not assume that clearing a container releases its allocation unless documented by the container. Reservation and growth operations MUST account for reference and pointer invalidation. Prefer:\nm_VisibleObjects.Reserve(uiEstimatedVisibleObjectCount); for (const xiiObject* pObject : pObjects) { if (pObject-\u0026gt;IsVisible()) { m_VisibleObjects.PushBack(pObject-\u0026gt;GetHandle()); } } Reservation is beneficial when the estimate is meaningful and the container would otherwise require repeated growth.\nCount Versus Capacity Avoid:\nm_Entries.Reserve(128U); ProcessEntry(m_Entries[127]); Reservation alone does not establish that the collection contains 128 valid elements.\nPrefer:\nm_Entries.SetCount(128U); ProcessEntry(m_Entries[127]); The preferred example assumes SetCount() establishes the required elements according to the container\u0026rsquo;s documented construction and initialization semantics.\nRepeated Reservation Calling Reserve() repeatedly may be harmless when the requested capacity is already available, but unnecessary calls can obscure the intended allocation strategy.\nPrefer reserving storage at initialization or another appropriate capacity-planning boundary when the expected requirement is stable.\nGuiding principle:\nCapacity is an allocation property; count is an element-validity property. Reserve intentionally and never confuse available storage with constructed objects.\n10.4 Pointer, Reference, and Iterator Invalidation Container modifications may invalidate pointers, references, iterators, views, and indices into their elements.\nSuch invalidation MUST be considered whenever references to container-managed storage escape the immediate operation.\nGeneral Requirements Do not assume elements retain stable addresses across container growth. Verify the invalidation guarantees of the specific XII container being used. Do not retain pointers into relocatable storage across operations that may trigger relocation. Element removal MUST invalidate access to the removed object. Iteration MUST respect container mutation and invalidation rules. Stable addresses MUST NOT be confused with stable logical identity. Avoid unnecessary indirection when stable addresses are not required. Use handles when logical identity must remain independent of storage location. Generation-checked handles MUST be validated through their authoritative registry. Container relocation MUST NOT invalidate externally registered addresses when those addresses are contractually required to remain stable. Relocation Hazards Avoid:\nxiiDynamicArray\u0026lt;Entry\u0026gt; m_Entries; Entry* pEntry = \u0026amp;m_Entries[0]; m_Entries.PushBack(anotherEntry); // pEntry may now refer to invalid storage. ProcessEntry(*pEntry); Appending an element may require the dynamic array to relocate its existing elements.\nThe retained pointer may therefore become invalid.\nStable Allocation When stable element addresses are genuinely required, independently allocated objects may be appropriate.\nxiiDynamicArray\u0026lt;xiiUniquePtr\u0026lt;Entry\u0026gt;\u0026gt; m_Entries; Relocating the container\u0026rsquo;s smart pointers does not relocate the independently allocated Entry objects.\nHowever, removing or destroying an entry still invalidates pointers to that entry.\nIndex-Based References Indices may survive relocation when logical ordering remains unchanged, but they can become invalid or refer to different elements after removal or reordering.\nDo not treat an index as a persistent resource identity unless the owning subsystem explicitly guarantees that behavior.\nFor independently managed objects, generation-checked handles are generally more appropriate.\nSee §6.4 for the distinction between stable identity and stable address.\nGuiding principle:\nContainer stability is defined by documented invalidation rules. Never infer pointer, index, or iterator validity from the container\u0026rsquo;s apparent implementation.\n10.5 Removal, Ordering, and Compaction Removal operations MUST preserve the ordering and identity guarantees required by the collection\u0026rsquo;s consumers.\nDifferent removal strategies offer different performance and semantic trade-offs.\nGeneral Requirements Use unordered removal only when element order is not part of the collection\u0026rsquo;s contract. Ordered removal MUST be used when the relative order of surviving elements must be preserved. Removal MUST account for pointer, reference, iterator, and index invalidation. Do not optimize away deterministic ordering when it affects observable behavior. Use compaction when it provides a meaningful benefit and does not invalidate required external references. Avoid unnecessary stable ordering when no consumer depends on it. Removal during iteration MUST follow the container\u0026rsquo;s documented mutation rules. Secondary lookup structures MUST remain consistent after removal or relocation. Unordered Removal When order is irrelevant, swap-based removal can reduce the number of elements that must be moved.\nfor (xiiUInt32 uiIndex = 0; uiIndex \u0026lt; m_Entries.GetCount();) { if (m_Entries[uiIndex].m_bExpired) { m_Entries.RemoveAtAndSwap(uiIndex); continue; } ++uiIndex; } The element moved into the removed position must still be examined.\nOrdered Removal Ordered removal preserves relative ordering but may require shifting subsequent elements.\nIt is appropriate when order affects:\nExecution sequencing. Scheduling. Serialization. Deterministic iteration. User-visible ordering. Stable relative ordering required by tests or algorithms. Do not replace ordered removal with swap-based removal without evaluating these requirements.\nCompaction Compaction may improve locality and reduce fragmentation, but it can also invalidate addresses and indices.\nA system that supports compaction MUST distinguish logical identity from physical location when external references must survive relocation.\nGuiding principle:\nRemoval semantics are part of the collection\u0026rsquo;s contract. Optimize movement costs only when ordering and identity requirements permit it.\n10.6 Allocator Ownership and Lifetime An allocator MUST remain valid for every allocation that depends on it.\nAllocator lifetime is a dependency relationship and must be preserved through initialization, ordinary execution, failure recovery, and shutdown.\nThe general allocator compatibility requirements are established in §6.2.\nGeneral Requirements Allocators MUST outlive the allocations and containers that rely on them. Objects MUST NOT release memory through an allocator that has already been destroyed. Allocator reset operations MUST NOT invalidate allocations that remain in use. Allocator ownership SHOULD be explicit when a component selects or manages its own allocator. Member declaration order MUST account for reverse-order destruction when members depend on an allocator owned by the same object. Explicit shutdown operations MUST release dependent resources before destroying their allocator. Plugin and module-specific allocators MUST remain available until all allocations requiring them have been released. Arena and frame allocator boundaries MUST account for deferred execution and asynchronous resource use. Member Destruction Order C++ destroys non-static data members in the reverse order of their declaration.\nPrefer:\nclass xiiStorageManager { private: xiiProxyAllocator m_Allocator; xiiDynamicArray\u0026lt;xiiUInt32\u0026gt; m_Entries; }; This arrangement is appropriate when m_Entries uses m_Allocator, assuming the allocator is correctly supplied during construction.\nBecause m_Entries is declared after m_Allocator, it is destroyed first.\nThe allocator remains alive throughout the container\u0026rsquo;s destruction.\nExplicit Shutdown void xiiStorageManager::Shutdown() { m_Entries.Clear(); } Clearing entries may destroy contained objects, but whether it releases the container\u0026rsquo;s backing allocation depends on the container contract.\nIf allocator teardown requires all backing allocations to be released before the manager itself is destroyed, the implementation must use the appropriate storage-release operation or lifecycle design.\nDo not assume Clear() necessarily returns memory to the allocator.\nArena Reset An arena allocator may invalidate many allocations simultaneously.\nResetting an arena is appropriate only when every object using its storage has completed its required lifetime.\nFor non-trivially destructible objects, the implementation must also ensure that required destruction occurs according to the allocator and container contracts.\nGuiding principle:\nAn allocator is part of the lifetime of every allocation it serves. Destruction order, reset behavior, and shutdown must respect that dependency.\n10.7 Ownership, Caching, Pooling, and Aliasing Ownership, caching, pooling, and memory aliasing are distinct concepts and MUST NOT be conflated.\nA subsystem may implement several of these mechanisms simultaneously, but each must have a clearly defined responsibility and lifetime contract.\nDefinitions Concept Responsibility Ownership Determines which component is responsible for releasing a resource Caching Retains reusable data or resources to avoid repeated computation or creation Pooling Recycles compatible objects or allocations according to defined reuse rules Aliasing Permits separate logical resources to share physical memory when their usage constraints allow it Registration Associates an object with a lookup, discovery, or identity mechanism Residency Describes whether a resource or allocation is present in a required memory domain General Requirements Resource ownership MUST remain explicit regardless of whether the resource is cached or pooled. A cache MUST define when entries become valid, stale, reusable, and eligible for eviction. A pool MUST define compatibility requirements for reusing an allocation or object. Memory aliasing MUST respect the lifetime and synchronization constraints of the underlying memory system. Registration MUST NOT be assumed to transfer ownership unless the API explicitly establishes that behavior. Caches MUST NOT silently become authoritative owners of unrelated subsystem state without an explicit contract. Do not introduce caches or pools without a demonstrated benefit. Cached resources MUST be invalidated when the assumptions underlying their correctness no longer hold. Resource eviction MUST respect outstanding references and asynchronous operations. The distinction between logical resource identity and physical allocation reuse MUST remain clear. Cache Validity A cached result may depend on:\nResource contents. Configuration values. Platform capabilities. Compilation settings. Device state. Version identifiers. Other external dependencies. The cache must define which changes invalidate its entries.\nA cache that does not correctly represent its invalidation dependencies may return stale results even when its memory management is otherwise correct.\nPool Reuse A pool may reuse memory without reusing an object\u0026rsquo;s logical identity.\nWhen physical allocations are recycled, new objects MUST NOT accidentally inherit stale state, references, or generation identifiers from previous occupants.\nFor GPU resources, reuse must also respect outstanding GPU execution dependencies.\nResource Aliasing Aliasing permits multiple logical resources to use overlapping physical storage under defined conditions.\nIt is valid only when their memory usage, synchronization, and API requirements permit that overlap.\nTwo resources having non-overlapping apparent application lifetimes does not automatically prove that their GPU usage does not overlap.\nGuiding principle:\nOwnership determines responsibility, caching determines retention, pooling determines reuse, and aliasing determines memory sharing. Each mechanism requires its own correctness contract.\n10.8 Memory Alignment, Layout, and Representation Memory layout and alignment MUST satisfy the requirements of the C++ object model and any external interface that consumes the data.\nThis is particularly important for SIMD operations, atomic types, binary serialization, GPU resources, and platform API structures.\nGeneral Requirements Allocations MUST satisfy the alignment requirements of the objects stored within them. Use XII\u0026rsquo;s established alignment helpers and allocation facilities where appropriate. Do not assume compiler-generated structure padding is stable across different toolchains or build configurations. Do not rely on undocumented sizeof() equivalence between unrelated types. Binary data exchange MUST account for representation, alignment, field layout, and endianness where applicable. Do not reinterpret arbitrary byte sequences as C++ objects without satisfying object lifetime, alignment, and representation requirements. SIMD storage MUST meet the alignment requirements of the selected operations. Atomics MUST be correctly aligned and used according to their synchronization contract. Packed structures SHOULD be avoided unless required by an explicit external format or platform interface. Explicit padding MAY be used when a binary layout contract requires it. Alignment Example:\nstruct alignas(16) xiiAlignedVectorData { float m_fValues[4] = {}; }; The explicit alignment establishes a minimum alignment requirement for the type.\nSuch alignment should be introduced only when it is necessary for the intended operations or external layout contract.\nSerialization Do not serialize arbitrary object memory directly merely because a type appears to contain simple fields.\nC++ object representations may contain:\nPadding bytes. Platform-dependent layouts. Pointer values. Endianness-dependent representations. Non-portable implementation details. Persistent formats SHOULD define their field representations explicitly.\nGPU Data Layout GPU-facing data structures must satisfy the layout rules of the relevant graphics API and shader interface.\nCPU structure size, member offsets, and alignment must not be assumed compatible with shader data layouts without verification.\nWhere necessary, validate layout assumptions using compile-time checks and established reflection or shader interface mechanisms.\nGuiding principle:\nMemory layout is a correctness contract whenever data crosses representation boundaries. Alignment, padding, and object lifetime must be established explicitly.\n10.9 Data-Oriented Access and Locality Data-oriented design SHOULD be considered when a subsystem repeatedly processes large collections of objects or performs homogeneous operations over substantial datasets.\nThe goal is to organize data according to how it is accessed, not to replace every object-oriented abstraction with arrays.\nGeneral Requirements Prefer storage layouts that support the dominant access patterns of the subsystem. Avoid repeatedly scanning large heterogeneous collections when narrower indexing or categorization would provide a demonstrated benefit. Group frequently accessed data when doing so improves locality and reduces unnecessary memory traffic. Separate rarely accessed data from hot-path data when measurements justify the additional organization. Do not duplicate authoritative state across multiple representations without a defined synchronization or invalidation strategy. Secondary indices MUST remain consistent with the owning data. Data layout changes MUST preserve identity, ownership, and ordering requirements. Avoid introducing complex data-oriented structures for small collections where straightforward storage is sufficient. Evaluate data-oriented optimizations using representative workloads. Array of Structures An array of structures can be appropriate when operations frequently access most fields of each element.\nstruct xiiParticle { xiiVec3 m_vPosition; xiiVec3 m_vVelocity; float m_fMass = 1.0f; }; xiiDynamicArray\u0026lt;xiiParticle\u0026gt; m_Particles; This representation keeps each particle\u0026rsquo;s data together.\nIt may be suitable when updating position, velocity, and mass together.\nStructure of Arrays A structure-of-arrays representation may be preferable when operations repeatedly access only selected fields across many elements.\nstruct xiiParticleStorage { xiiDynamicArray\u0026lt;xiiVec3\u0026gt; m_Positions; xiiDynamicArray\u0026lt;xiiVec3\u0026gt; m_Velocities; xiiDynamicArray\u0026lt;float\u0026gt; m_Masses; }; This layout may improve locality for operations that process positions independently of other particle properties.\nHowever, it introduces an invariant: corresponding array elements must remain aligned by index.\nInsertion, removal, resizing, and reordering MUST preserve that relationship.\nSecondary Indices A subsystem may maintain a collection of handles or indices identifying a frequently accessed subset of objects.\nxiiDynamicArray\u0026lt;xiiObjectHandle\u0026gt; m_ActiveObjects; Such indices can reduce repeated filtering costs, but they introduce additional state that must be maintained when objects change eligibility or are destroyed.\nSecondary indices SHOULD be introduced only when their benefits justify those maintenance requirements.\nPerformance Considerations Data-oriented representations may improve:\nCache locality. Sequential memory access. SIMD utilization. Batch processing. Parallel work partitioning. Reduced pointer chasing. They may also increase:\nState synchronization complexity. Update and removal costs. Memory duplication. Interface complexity. Difficulty preserving independent object identity. The appropriate representation depends on the workload.\nGuiding principle:\nOrganize data for the operations that consume it. Optimize locality where it matters without sacrificing ownership, consistency, or architectural clarity.\n10.10 Smart-Pointer Ownership Cycles Shared ownership graphs MUST avoid cycles that prevent resources from being released.\nThis is particularly important for callbacks, editor services, plugin systems, asynchronous tasks, and resource managers.\nGeneral Requirements Shared ownership MUST reflect genuine lifetime responsibility. Do not introduce mutual strong ownership when one relationship is logically non-owning. Ownership graphs SHOULD have an identifiable direction or authoritative owner. Use weak references, non-owning pointers, or manager handles where appropriate to break ownership cycles. A non-owning reference MUST still have a valid lifetime and invalidation contract. Callback registrations MUST be released before their owning objects or executable code become invalid. Do not assume reference counting automatically prevents resource leaks. Shared ownership across plugin boundaries MUST account for module unloading and destruction requirements. Ownership Cycles Avoid conceptually:\nclass xiiServiceA { xiiSharedPtr\u0026lt;xiiServiceB\u0026gt; m_pServiceB; }; class xiiServiceB { xiiSharedPtr\u0026lt;xiiServiceA\u0026gt; m_pServiceA; }; If both objects hold strong ownership of each other, their reference counts may never reach zero.\nPrefer Directed Ownership When one service owns another, express that relationship directly.\nclass xiiServiceA { private: xiiUniquePtr\u0026lt;xiiServiceB\u0026gt; m_pServiceB; }; The owned service may retain a non-owning reference to its owner only when that reference is required and its lifetime is guaranteed.\nWhere neither service should own the other, an authoritative manager or handle-based lookup may provide a clearer relationship.\nPlugin and Callback Cycles Callbacks can create indirect ownership cycles when they capture strong references to objects that own the callback registration.\nA callback\u0026rsquo;s lifetime must therefore be reviewed together with its capture semantics and registration owner.\nGuiding principle:\nShared references are not automatically shared responsibility. Make ownership directional wherever practical, and prevent cycles that obstruct deterministic destruction.\n10.11 Memory Budgets and Resource Accounting Systems that manage substantial or variable memory consumption SHOULD define appropriate memory accounting and capacity policies.\nThis is particularly relevant to caches, streaming systems, asset processing, simulations, and GPU resource management.\nGeneral Requirements Memory budgets SHOULD be explicit when a subsystem may otherwise grow without meaningful bounds. Resource accounting MUST distinguish logical resource sizes from actual physical allocation costs when the distinction affects correctness or budgeting. Do not assume that container element counts accurately represent total memory usage. Cached and pooled allocations SHOULD be included in memory accounting when they remain resident. Eviction and reclamation strategies MUST respect outstanding references and asynchronous execution. Budget enforcement MUST preserve valid resource states when allocation or reclamation fails. Avoid introducing complex budget-management systems when the subsystem\u0026rsquo;s memory requirements are small and predictable. Memory accounting SHOULD be validated against allocator or platform measurements where practical. Logical Versus Physical Memory A resource\u0026rsquo;s logical data size may differ from its actual allocation size due to alignment, metadata, allocator granularity, padding, and internal storage requirements.\nFor example, a texture\u0026rsquo;s apparent pixel-data size may not equal the physical memory reserved by a graphics backend.\nBudgets must use measurements appropriate to the resource-management decisions they control.\nCache Growth A cache that retains every previously used resource without an eviction policy may exhibit unbounded memory growth.\nCaches SHOULD establish an appropriate retention policy when resource volume is not naturally bounded.\nPossible strategies include explicit capacity limits, memory budgets, age-based retirement, or reclamation under pressure.\nThe selection should follow actual workload requirements rather than a universal policy.\nGuiding principle:\nMemory retained for performance remains a resource cost. Budgeting and accounting should reflect physical ownership and realistic workload behavior.\n10.12 Memory Diagnostics and Validation Memory correctness and performance SHOULD be verified using appropriate diagnostics, tests, and profiling tools.\nThe objective is to detect lifetime violations, allocation mismatches, invalidation errors, leaks, and performance regressions before they become difficult to reproduce.\nGeneral Requirements Allocation and destruction paths SHOULD be tested under normal execution and recoverable failure conditions. Resource-owning containers MUST preserve their invariants during insertion, growth, removal, and clearing. Memory diagnostics SHOULD cover allocator mismatch, double destruction, invalid access, and leaked ownership where applicable. Use supported sanitizers and diagnostic tools when they are compatible with the relevant platform and build configuration. Do not assume that successful execution without a crash proves memory correctness. Profile allocation count, allocated bytes, retained capacity, and fragmentation where those measurements matter. Validate memory behavior under representative workloads rather than relying exclusively on small synthetic tests. GPU memory diagnostics SHOULD account for asynchronous execution and deferred destruction. Tests SHOULD exercise subsystem shutdown and partial initialization failure paths. Performance improvements SHOULD include measurements demonstrating the intended reduction in allocation or memory overhead. Development Assertions Assertions may detect incorrect indices, unexpected allocation states, or violated container invariants during development.\nHowever, assertions MUST NOT perform essential allocation, destruction, or cleanup work.\nExpected allocation failures MUST follow the subsystem\u0026rsquo;s explicit error-handling contract.\nSanitizers and Instrumentation Where supported, memory diagnostics may include:\nAddressSanitizer for invalid memory accesses and lifetime errors. UndefinedBehaviorSanitizer for supported categories of undefined behavior. Leak detection for unreleased allocations. Heap profiling for allocation frequency and retained memory. Platform memory analyzers for fragmentation and allocation behavior. Graphics API validation and GPU-assisted diagnostics where available. These tools provide complementary evidence. No single tool can establish every ownership or lifetime guarantee.\nGuiding principle:\nMemory correctness must be demonstrated across allocation, mutation, failure, and destruction. Performance claims should be supported by measurements rather than assumptions.\n11. Strings, Identifiers, Hashes, and Handles Strings, identifiers, hashes, and handles serve distinct purposes in XII. Their representations MUST reflect the intended semantics of ownership, identity, comparison, lookup, and lifetime.\nChoosing an appropriate representation is important for correctness, performance, and interoperability across engine modules. In particular, textual names must not be confused with object identity, hash values must not be assumed collision-free, and resource handles must not be treated as owning pointers.\nThis section extends §1.2 (Ownership and Lifetime), §3 (Naming Conventions), §6 (Resource Lifetime), and §10 (Memory and Containers).\n11.1 Use the Appropriate String Representation XII provides several string abstractions with different ownership, construction, comparison, and storage characteristics.\nThe selected string type SHOULD reflect how the text is produced, stored, and consumed.\nType Typical responsibility Important consideration xiiString Owned string storage Suitable when text must remain valid independently of its source xiiStringBuilder Mutable construction and manipulation Appropriate for repeated formatting, concatenation, and modification xiiStringView Non-owning string access Backing storage must remain valid xiiHashedString Persistent names with cached hashing Useful for repeated comparisons and lookups xiiTempHashedString Temporary hashed lookup values Appropriate when the receiving API supports compatible hashed lookup const char* C-compatible string interfaces Requires explicit null-termination and lifetime assumptions General Requirements Prefer xiiStringView for read-only string inputs when ownership transfer is unnecessary. Use xiiString when a component must retain text independently of the caller. Use xiiStringBuilder when constructing or modifying text repeatedly. Use xiiHashedString when cached hashing provides a meaningful benefit for a persistent name. Use xiiTempHashedString for temporary lookup operations when supported by the target container or API. Do not store non-owning string views without establishing the backing storage\u0026rsquo;s lifetime. Do not repeatedly compute hashes for stable identities in demonstrated hot paths when a suitable cached representation is available. Do not intern or persistently hash every temporary diagnostic string without a concrete requirement. Avoid unnecessary conversions among string types, particularly in frequently executed operations. String representations MUST preserve the encoding and comparison semantics required by their consumers. Do not assume that all string representations guarantee NUL termination. Borrowed String Inputs Prefer:\nxiiResult LoadDocument(xiiStringView sPath); This permits callers to supply compatible string storage without requiring ownership transfer at the function boundary.\nIf the implementation needs to retain the path beyond the call, it must copy or otherwise safely acquire the required storage.\nString Construction Prefer:\nxiiStringBuilder sResourcePath; sResourcePath.Append(sDirectory); sResourcePath.AppendPath(sResourceName); This is appropriate when a mutable string is being assembled from several components and the path operations match the filesystem abstraction\u0026rsquo;s requirements.\nDo not introduce a builder when a simple string value or view already satisfies the operation.\nHashed String Lookup Example:\nxiiHashedString m_sResourceName; xiiUInt32 uiResourceIndex = 0U; if (!m_ResourceLookup.TryGetValue(xiiTempHashedString(sName), uiResourceIndex)) { return XII_FAILURE; } The temporary hashed representation avoids unnecessarily constructing a persistent hashed-string object for an immediate lookup, assuming the table\u0026rsquo;s key and heterogeneous lookup contracts support the operation.\nThe actual hashing and equality guarantees depend on the XII string and container implementations.\nGuiding principle:\nUse owned strings for retained data, views for borrowed access, builders for construction, and hashed strings where identity-oriented lookup justifies them.\n11.2 Distinguish Names, Identifiers, and Display Labels A textual name does not automatically represent a unique object identity.\nXII subsystems MUST distinguish between names used for identification, labels used for presentation, and handles used for authoritative resource identity.\nGeneral Requirements Names used as identifiers MUST have documented uniqueness and comparison semantics. Display names SHOULD NOT determine object identity unless the subsystem explicitly defines that behavior. Resource identity SHOULD remain stable independently of diagnostic or user-facing names when the subsystem requires renaming. Do not assume that identical display labels identify the same object. Do not assume that distinct strings necessarily identify distinct objects when normalization or case-insensitive comparison is applied. Name-based registries MUST define how duplicate names are handled. Rename operations MUST preserve registry consistency and the documented identity contract. Names used for serialization or persistent references MUST have defined stability and compatibility requirements. Avoid maintaining redundant names and identifiers unless they serve distinct responsibilities. Example:\nstruct xiiNamedResource { xiiHashedString m_sName; xiiResourceHandle m_hResource; }; The name provides a lookup or descriptive identity within a particular namespace, while the handle identifies the registered resource according to its manager\u0026rsquo;s rules.\nThe two values should not be treated as interchangeable.\nIdentity Scope Name uniqueness must be defined within an explicit scope.\nPossible scopes include:\nA single object. A document or scene. A resource registry. A loaded plugin. An application instance. A persistent asset database. A name unique within one scene is not necessarily unique across the entire engine.\nDo not introduce global uniqueness requirements when a smaller namespace satisfies the actual consumer requirements.\nDuplicate Names A registry MUST define whether duplicate names are:\nRejected. Replacements for existing registrations. Permitted under different owning contexts. Represented as multiple entries. Resolved through an explicit namespace or qualification mechanism. The selected behavior must be consistent across creation, lookup, renaming, serialization, and destruction.\nGuiding principle:\nA name describes or locates an object within a defined namespace. An authoritative identifier establishes which object is being referenced.\n11.3 String Ownership, Conversion, and Lifetime String APIs MUST make ownership and lifetime requirements clear.\nA function that accepts xiiStringView receives borrowed access to text. It does not automatically receive ownership of the backing storage.\nThe borrowing rules established in §6.3 and §6.8 apply to all non-owning string representations.\nGeneral Requirements A function retaining text beyond its invocation MUST establish ownership or a valid externally guaranteed lifetime. Do not retain xiiStringView objects that reference temporary strings. Do not assume that converting a string into a view extends the original string\u0026rsquo;s lifetime. Avoid returning views into temporary builders or locally owned strings. Do not retain pointers to string storage across mutations that may reallocate or invalidate that storage. String conversions MUST preserve required encoding and representation semantics. Do not assume that string views are NUL-terminated. Interfaces requiring NUL-terminated strings MUST receive storage that satisfies that requirement. Asynchronous operations MUST own or otherwise guarantee the lifetime of text retained across execution boundaries. Safe Ownership Transfer Prefer:\nclass xiiDocument { public: void SetName(xiiStringView sName) { m_sName = sName; } private: xiiString m_sName; }; The document stores an owning copy of the provided text.\nThe caller may destroy or modify its original string after SetName() returns without invalidating the document\u0026rsquo;s stored name.\nUnsafe Borrowing Avoid:\nclass xiiDocument { public: void SetName(xiiStringView sName) { m_sName = sName; } private: xiiStringView m_sName; }; This implementation stores a borrowed view without establishing that the caller\u0026rsquo;s backing storage will remain valid.\nIt may be correct only when the lifetime dependency is explicitly documented and enforced.\nNUL-Terminated Strings C-compatible APIs often require pointers to NUL-terminated character sequences.\nA string view representing an arbitrary substring does not necessarily satisfy this requirement.\nDo not pass a string view\u0026rsquo;s underlying pointer to such an API unless its termination contract is guaranteed.\nWhen necessary, construct an appropriately terminated owning or temporary string representation at the integration boundary.\nGuiding principle:\nString ownership must follow the duration of use. A borrowed view is an access mechanism, not a storage or lifetime guarantee.\n11.4 Hashing and Hash-Based Lookup Hashing SHOULD be used when it provides an appropriate representation for lookup, comparison, caching, or indexing.\nA hash is a derived value and must not automatically be treated as a globally unique identifier.\nGeneral Requirements Hash functions MUST produce consistent results for equivalent keys under the defined comparison contract. Hash-table equality semantics MUST be compatible with the selected hash function. Hash collisions MUST be handled according to the container or identifier system\u0026rsquo;s documented contract. Do not assume that a hash value uniquely identifies arbitrary input data. Do not persist implementation-dependent hash values as stable identifiers unless their algorithm and representation are explicitly part of the persistent format. Avoid repeatedly hashing unchanged values in performance-critical lookup paths when cached hashes are appropriate. Do not introduce persistent hash storage when its maintenance cost exceeds its demonstrated benefit. Hash-based lookup MUST preserve the required string comparison and normalization semantics. Do not use ordinary non-cryptographic hashes to establish security or authenticity. Hash-table iteration order MUST NOT be treated as deterministic unless the container explicitly guarantees it. Hash Equality and Identity A hash function maps a potentially large input domain into a finite range.\nDifferent inputs can therefore produce the same hash value.\nWhen exact identity matters, the implementation must either:\nCompare the original values following a hash match. Use an established string interning mechanism with documented identity guarantees. Use a registry-managed identifier that resolves collisions correctly. Explicitly define and accept the collision semantics of the representation. Do not assume that a 32-bit or 64-bit hash is collision-free merely because collisions are uncommon for representative workloads.\nPersistent Hashes A hash value used in a persistent format requires a stable contract.\nThat contract should specify:\nHash algorithm. Input encoding. Normalization rules. Case sensitivity. Width and representation. Versioning requirements. Collision handling. Changing any of these properties may invalidate stored identifiers or serialized references.\nHashing and Performance Hashing reduces some comparison costs but introduces its own computation and storage overhead.\nFor small collections, direct comparisons or linear scans may outperform a hash table because of locality and reduced indirection.\nContainer selection should follow §10.1 and the evidence-based performance requirements of §1.5.\nGuiding principle:\nHashes accelerate lookup and comparison; they do not inherently provide collision-free identity, deterministic ordering, or persistent compatibility.\n11.5 Generation-Checked Handles Handles SHOULD be used for objects whose identity and lifetime are managed independently by a registry, manager, or subsystem.\nA handle represents a logical reference rather than a direct memory address.\nGeneration checking can detect stale references when registry slots are reused.\nGeneral Requirements Handle types MUST represent clearly defined resource categories or identity domains. Prefer distinct handle types for semantically different resources, even when their underlying representations are identical. Handle validity MUST be determined according to the authoritative registry\u0026rsquo;s rules. Handles MUST NOT imply ownership unless explicitly documented. Reusable registry slots SHOULD employ generation tracking or another appropriate stale-reference detection mechanism. Handle construction and invalidation MUST preserve the representation\u0026rsquo;s invariants. Invalid handles MUST have a defined representation or validity-checking mechanism. Generation exhaustion or wraparound MUST be considered when the handle format permits repeated slot reuse. Handle validation MUST NOT be confused with lifetime protection during concurrent access. Do not expose underlying handle bit fields unnecessarily through public interfaces. Illustrative Handle Definition using xiiSimulationId = xiiGenericId\u0026lt;24, 8\u0026gt;; class XII_CORE_DLL xiiSimulationHandle { XII_DECLARE_HANDLE_TYPE(xiiSimulationHandle, xiiSimulationId); }; This example illustrates the use of a typed handle and an underlying identifier representation.\nThe actual handle declaration, identifier widths, friend declarations, storage layout, and generation policy must follow the owning subsystem\u0026rsquo;s implementation.\nThe example does not prescribe a universal 24-bit index or 8-bit generation format.\nStale Handles A generation-checked handle typically identifies both a slot and a particular lifetime of that slot.\nWhen the object is destroyed and its slot is reused, the generation changes according to the registry\u0026rsquo;s rules.\nA stale handle can then be rejected instead of resolving to the new object.\nHowever, generation checking does not guarantee permanent protection.\nA finite-width generation counter can eventually wrap, potentially recreating an earlier encoded identifier if the slot is reused sufficiently many times.\nThe acceptable generation width and reuse policy must reflect the expected object churn and lifetime requirements.\nTyped Handles Prefer:\nxiiSimulationHandle hSimulation; xiiResourceHandle hResource; Avoid:\nxiiUInt32 uiSimulationId; xiiUInt32 uiResourceId; Distinct handle types make accidental cross-subsystem usage more difficult and allow the compiler to enforce meaningful interface distinctions.\nGuiding principle:\nHandles provide typed logical identity and permit validation. Their safety depends on the registry\u0026rsquo;s generation, lifetime, and synchronization contracts.\n11.6 Handle Ownership Domains and Context Identity A handle\u0026rsquo;s underlying numeric representation does not automatically identify its owning subsystem, manager, or execution context.\nWhere handles are valid only within a particular ownership domain, that relationship MUST be established and enforced.\nGeneral Requirements Every handle type MUST have a defined authority responsible for its validity. The scope of handle uniqueness MUST be documented when multiple registries or contexts can create the same handle type. Do not assume that a globally accessible handle type implies globally unique identifiers. Cross-context handle access MUST be validated when the API permits references from multiple contexts. Handle ownership MAY be represented through encoded identifiers, registry metadata, or an explicit context parameter. Do not increase handle width or introduce additional identifier fields without a demonstrated requirement. Resource lookup MUST use the correct authoritative registry. Handle transfer between contexts MUST be explicitly supported or rejected. Context destruction MUST invalidate or retire resources according to the context\u0026rsquo;s ownership contract. Contextual Identity Consider a system supporting multiple independently managed simulation worlds.\nA handle allocated by one world may have the same underlying index and generation as a handle allocated by another world.\nIf those handles share a type, the implementation must prevent accidental cross-world resolution when such access is invalid.\nPossible approaches include:\nEncoding an owner or context identifier into the handle. Recording the owner in authoritative registry metadata. Requiring an explicit owning context during lookup. Using globally allocated identifiers with documented uniqueness guarantees. No single strategy is universally preferable.\nThe selected approach should reflect the actual lifetime, lookup, and cross-context requirements.\nAvoid Hidden Ownership Assumptions Do not infer ownership from unrelated properties such as:\nA handle\u0026rsquo;s numeric magnitude. The order in which resources were created. The currently active frame. The currently active device. The global registry that happens to be accessible. A resource\u0026rsquo;s display name. Such assumptions are fragile unless they form part of an explicitly enforced contract.\nGuiding principle:\nA handle identifies a resource only within the domain that defines its meaning. Ownership context must be represented or validated explicitly.\n11.7 Authoritative Registries and Mapping Consistency Systems maintaining names, handles, indices, or resource relationships MUST define which representation is authoritative.\nSecondary mappings are useful for lookup and iteration but introduce consistency requirements.\nGeneral Requirements Each resource MUST have one clearly defined authoritative ownership or registration mechanism. Secondary lookup tables MUST remain consistent with authoritative state. Registration MUST preserve uniqueness and identity requirements. Resource destruction MUST remove or invalidate associated secondary mappings according to the lifecycle contract. Renaming MUST update affected name-based mappings consistently. Partial registration failures MUST leave the system in a documented valid state. Avoid duplicating complete resource state across multiple containers without a demonstrated need. Multi-container updates MUST be coordinated through appropriate synchronization or rollback mechanisms. Readers MUST NOT observe inconsistent intermediate states when the subsystem\u0026rsquo;s concurrency contract prohibits them. Do not introduce global registries merely to simplify access to otherwise locally owned resources. Authoritative and Secondary Mappings A subsystem may use:\nA handle table as the authoritative identity registry. A name-to-handle table for lookup. A list of handles for ordered iteration. A context-to-handle table for membership. A cache for reusable derived data. These structures serve different responsibilities.\nOnly one should be considered authoritative for each particular ownership or identity fact unless the system explicitly maintains a stronger consistency model.\nRegistration Failure When registration requires modifying several structures, failure must not leave partially published resources.\nPrefer conceptually:\nxiiResult RegisterResource(xiiStringView sName) { if (m_NameLookup.Contains(sName)) return XII_FAILURE; // Prepare and validate the required resource state. // Publish the resource only when registration can // satisfy the subsystem\u0026#39;s consistency contract. return CommitRegistration(sName); } This example illustrates the required separation between validation and commitment.\nIt does not prescribe a specific transaction mechanism.\nIf publishing a resource requires multiple allocations or container updates, the implementation must provide rollback or another safe method of preserving consistency when an intermediate operation fails.\nConcurrency Consistency may require a lock spanning multiple registry updates, a staged publication mechanism, or another synchronization strategy.\nDo not assume that individually thread-safe containers make a multi-container operation atomic.\nLikewise, registry synchronization does not automatically protect the lifetime of objects returned from a lookup.\nSee §6.5 for lifetime-safe access requirements.\nGuiding principle:\nMaintain one authority for each resource fact. Every secondary mapping must remain consistent with it across creation, mutation, failure, and destruction.\n11.8 String Comparison, Normalization, and Equality String comparison semantics MUST match the meaning of the data being compared.\nByte equality, case-insensitive equality, normalized path equality, and logical identifier equality are not interchangeable.\nGeneral Requirements String comparisons MUST use semantics appropriate to the domain. Do not assume that byte equality implies logical equivalence for every text representation. Do not introduce case-insensitive comparisons merely for user convenience when case sensitivity is part of the identifier contract. Unicode normalization MUST be considered when the application requires canonical textual equivalence. Name-based lookup and name-based insertion MUST use compatible comparison rules. Hashing MUST follow the same equivalence rules used by equality comparisons. Do not normalize identifiers in a way that silently merges previously distinct valid values. Public identifier comparison rules SHOULD remain stable across versions when persistent compatibility is required. Do not assume ASCII-only transformations are sufficient for arbitrary Unicode text. Comparison Categories Comparison Meaning Typical use Byte comparison Exact encoded-byte equality Binary identifiers, explicitly byte-oriented protocols Case-sensitive text comparison Text comparison preserving case distinctions Case-sensitive resource names Case-insensitive comparison Equality under a defined case-mapping rule Explicitly case-insensitive identifiers Normalized text comparison Equality after a defined normalization process Text systems requiring canonical equivalence Normalized path comparison Equality according to a defined path model Virtual filesystem or resource path handling Hashed lookup Lookup using a hash compatible with the equality relation Name registries and associative containers A hashed lookup must preserve the same equality semantics as the corresponding non-hashed representation.\nHash and Equality Consistency If two keys compare equal, they must produce compatible hash values according to the hash table\u0026rsquo;s contract.\nFor example, a case-insensitive equality function cannot safely be paired with a case-sensitive hash that produces different values for keys considered equivalent.\nUnicode and Normalization Unicode strings may contain different encoded representations of text that appears visually identical.\nWhether those representations should compare equal depends on the subsystem\u0026rsquo;s contract.\nDo not apply Unicode normalization automatically to every engine identifier.\nFor identifiers used in persistent formats, normalization policy changes may alter lookup results and compatibility.\nGuiding principle:\nEquality is a domain contract. Hashing, lookup, normalization, and serialization must agree on what constitutes the same identifier.\n11.9 Paths, Virtual Filesystems, and Resource Identifiers Filesystem paths, virtual filesystem paths, resource identifiers, and display names MUST be treated as distinct concepts when their semantics differ.\nXII operates across platforms with different filesystem conventions and must not assume that one platform\u0026rsquo;s path representation applies universally.\nGeneral Requirements Prefer established XII path and filesystem utilities for path construction and manipulation. Do not concatenate filesystem paths using arbitrary string operations when a path-aware API is available. Do not assume that all platforms use identical path separators. Do not assume that all filesystems are case-sensitive or case-insensitive. Do not assume that drive letters, root paths, or volume representations are universally available. Relative paths MUST be interpreted against a defined base or virtual filesystem context. Do not assume lexical path normalization proves that two paths refer to the same physical file. Resource identifiers SHOULD remain independent of physical filesystem paths when the resource system supports virtualized lookup. User-facing labels SHOULD NOT be treated as authoritative filesystem identifiers. Paths received from external sources MUST be validated according to the filesystem and security boundaries of the receiving subsystem. Path comparison and hashing MUST use compatible normalization and equality semantics. Path Categories Category Meaning Physical filesystem path Refers to a location interpreted by the host filesystem Virtual filesystem path Refers to a location resolved through an engine-managed filesystem abstraction Resource identifier Identifies an asset or resource through the resource-management system Display name Human-readable text used for presentation or diagnostics Relative path A path interpreted against an explicitly defined base Canonical path A path resolved or normalized according to a particular filesystem or path contract These representations may contain similar text but do not necessarily have equivalent meanings.\nPath Construction Prefer:\nxiiStringBuilder sPath = sDirectory; sPath.AppendPath(sFileName); Avoid:\nxiiStringBuilder sPath; sPath.Append(sDirectory); sPath.Append(\u0026#34;/\u0026#34;); sPath.Append(sFileName); The preferred example delegates path separator handling to a path-aware operation.\nThe specific semantics of AppendPath() and the applicable virtual or physical filesystem rules must still be respected.\nPath Normalization Normalization may include:\nSeparator normalization. Removal of redundant separators. Resolution of . components. Lexical handling of .. components. Case normalization under a defined policy. Resolution relative to a known base. Lexical normalization does not necessarily resolve symbolic links, filesystem aliases, mount points, or physical file identity.\nDo not use string normalization alone as proof that a path is safe or contained within a permitted filesystem root.\nSecurity Boundaries Paths originating from plugins, user configuration, network requests, or other untrusted sources may require additional validation.\nValidation MUST follow the actual filesystem operation and trust boundary.\nIn particular, string-prefix comparisons are not sufficient to establish secure filesystem containment when path traversal, symbolic links, or filesystem resolution can change the target location.\nGuiding principle:\nPaths describe locations under a filesystem contract; resource identifiers describe engine-managed identity. Preserve the distinction and use platform-aware resolution.\n11.10 Persistent and Serialized Identifiers Identifiers used in serialized data, asset databases, network protocols, or other persistent formats require stronger stability guarantees than temporary runtime identifiers.\nA runtime handle MUST NOT automatically be treated as a persistent identifier.\nGeneral Requirements Persistent identifiers MUST have documented uniqueness and stability requirements. Do not serialize runtime handles as durable references unless their representation and resolution semantics explicitly support persistence. Generation counters and registry indices SHOULD be treated as runtime implementation details unless otherwise documented. Persistent identifier formats MUST have defined encoding and versioning requirements. Identifier changes MUST consider migration and compatibility with existing data. Do not use process-local pointer values as persistent identifiers. Hash-derived persistent identifiers MUST define their hashing and collision policies. Deserialization MUST validate identifiers before resolving them to runtime resources. Resource resolution MUST distinguish missing, invalid, and unavailable resources where those states require different behavior. Identifier representations SHOULD remain independent of memory layout and process-specific allocation behavior. Runtime Versus Persistent Identity A runtime handle may contain an index and generation used to locate an object within the current process.\nThat representation may change when:\nThe application restarts. Resources load in a different order. A registry is reconstructed. Resources are destroyed and recreated. An asset is loaded into another context. A persistent resource identifier must retain its intended meaning across the operations and environments defined by its contract.\nSerialization Boundaries When loading persistent references, resolve them through an authoritative resource system rather than assuming that stored numeric values remain valid runtime handles.\nGuiding principle:\nRuntime handles identify objects within an active ownership system. Persistent identifiers must survive the lifecycle and compatibility boundaries they are designed to cross.\n11.11 Handle Validation and Resource Access Handle validation MUST be performed by the subsystem that owns the associated identity and lifetime contract.\nA handle\u0026rsquo;s local validity representation is not necessarily sufficient to establish that its resource still exists.\nGeneral Requirements Distinguish structurally valid handles from handles that currently resolve to live resources. Resource lookup MUST reject handles that are invalid according to the authoritative registry. Stale-generation checks MUST occur where required by the registry contract. A successful lookup MUST NOT be assumed to extend resource lifetime beyond the documented access boundary. Concurrent destruction MUST be coordinated with resource access. Do not expose unchecked handle-to-pointer conversions through general public interfaces. Handle invalidation SHOULD occur after successful release where supported. Invalidating one handle variable MUST NOT be assumed to revoke copies held by other consumers. Do not introduce repeated registry lookups within critical paths without considering their cost and alternatives. Optimization of handle access MUST NOT bypass required validation or lifetime guarantees. Structural Versus Registry Validity A handle may encode a value that is not the designated invalid representation while still referring to a destroyed resource.\nFor example:\nif (!hResource.IsInvalidated()) { // The handle is not locally marked invalid. // The owning registry must still determine whether it // refers to a currently valid resource. } The exact validity methods depend on the handle implementation.\nScoped Access When resource destruction may occur concurrently, prefer scoped access mechanisms that preserve validity for the duration of the operation.\nA registry lookup protected by a short-lived mutex is insufficient if the returned pointer may be destroyed immediately after the mutex is released.\nSee §6.5 for the supported lifetime strategies.\nGuiding principle:\nA handle is meaningful only through its authoritative registry. Validation establishes current identity; safe access additionally requires lifetime and synchronization guarantees.\n11.12 Identifier and Hash Diagnostics Identifiers and hashes frequently appear in logs, assertions, diagnostic tools, and debugging interfaces.\nDiagnostic output SHOULD provide enough context to identify the affected resource without confusing implementation values with user-facing identity.\nGeneral Requirements Diagnostics SHOULD include meaningful resource names or identifiers when available. Do not rely exclusively on opaque hash values when a descriptive name can be reported. Handle diagnostics SHOULD distinguish the resource category and owning context when relevant. Do not assume that a raw numeric identifier is globally unique. Logging MUST NOT dereference invalid resources merely to obtain descriptive information. Diagnostic helpers SHOULD remain safe during failure handling and subsystem shutdown. Do not retain unnecessary diagnostic strings in performance-critical data structures without a demonstrated requirement. Diagnostic formatting SHOULD use established XII formatting and logging facilities. Error messages MUST avoid implying that a handle is valid merely because its encoded value is nonzero. Diagnostic Context Prefer:\nxiiLog::Error(\u0026#34;Failed to resolve resource \u0026#39;{0}\u0026#39; in simulation context {1}.\u0026#34;, sResourceName, uiContextId); Avoid:\nxiiLog::Error(\u0026#34;Resource lookup failed.\u0026#34;); The preferred diagnostic identifies the relevant operation and lookup context.\nWhen name storage is unavailable or unsafe to access, reporting a validated handle representation or other available context may be appropriate.\nHash Diagnostics A hash value may be useful for debugging lookup behavior, but it should not be presented as definitive proof of resource identity unless the identifier system explicitly guarantees that interpretation.\nGuiding principle:\nDiagnostics should describe the resource and its context, not merely expose an opaque implementation value.\n11.13 Identifier Design and Review New identifier and handle types SHOULD be introduced only when they establish a meaningful distinction in ownership, identity, or validity.\nDo not create additional identifier layers merely to rename existing integer values.\nDesign Considerations Before introducing a new identifier or handle type, determine:\nIdentity: What exactly does the identifier represent? Authority: Which subsystem creates and validates it? Scope: Within which registry, context, process, or persistent domain is it unique? Lifetime: When does the identifier become invalid? Reuse: Can the underlying identifier value be reassigned? Generation: Is stale-reference detection required? Ownership: Does possession of the identifier confer ownership or only access? Concurrency: Can lookup and destruction occur concurrently? Persistence: Must the identifier survive process or registry reconstruction? Compatibility: Is the representation part of a public binary or serialized contract? The selected representation MUST satisfy the actual answers to these questions.\nAvoid encoding unrelated properties into handles merely because spare bits are available.\nSimilarly, do not expose internal identifier fields unless consumers have a legitimate need to inspect them.\nGuiding principle:\nAn identifier is an architectural contract. Its type, scope, authority, lifetime, and reuse semantics must be deliberate and consistently enforced.\n12. Errors, Assertions, Diagnostics, and Logging Error handling in XII MUST distinguish expected operational failures from programming errors, preserve the invariants of affected components, and provide sufficient diagnostic context to identify and resolve problems.\nXII does not permit C++ exceptions for ordinary error handling. Expected failures are communicated through explicit mechanisms such as xiiResult, appropriate result types, and diagnostic outputs. Exceptions may only be encountered within deeply isolated platform or third-party integration code where they are unavoidable and must not propagate through ordinary engine interfaces.\nAssertions, error results, logging, and profiling serve different purposes and MUST NOT be treated as interchangeable mechanisms.\nThis section extends §1.1 (Correctness), §6 (Ownership and Lifetime), §7.7 (Result Handling), §7.10 (Exception Boundaries), and §8.11 (Assertions).\n12.1 Choose the Correct Failure Channel The mechanism used to report a problem MUST reflect whether the condition represents an expected runtime failure, a violated programming contract, or a diagnostic event.\nGeneral Requirements Use xiiResult or an appropriate error-returning type for recoverable operational failures. Use assertions to detect programming errors, violated preconditions, and internal invariants that must hold during valid execution. Use logging to provide diagnostic context, not as a substitute for returning or handling failure. Do not use exceptions for ordinary engine error handling. Do not use assertions as substitutes for validating untrusted input. Do not return XII_FAILURE for every programming error merely to avoid enforcing a documented contract. Do not continue an operation after a required dependency has failed unless a valid recovery or fallback path exists. Error-handling paths MUST maintain the documented ownership and state invariants of the affected components. Failure reporting SHOULD provide sufficient information for callers to determine whether recovery, retry, fallback, or termination of the current operation is appropriate. Situation Preferred mechanism Invalid external configuration Validate and return explicit failure File cannot be opened xiiResult with contextual diagnostic Malformed serialized data Validation failure with actionable diagnostic Shader or asset compilation failure Explicit failure and compiler diagnostics Network connection failure Error result with relevant connection context Resource allocation failure Explicit failure or established allocator failure mechanism Required pointer violates an internal contract Assertion; runtime enforcement where required Internal invariant is violated Assertion or appropriate fatal-error mechanism Optional capability is unavailable Defined fallback or explicit unsupported-operation result GPU device loss Propagate failure and preserve backend diagnostic context Invalid or stale resource handle Contract enforcement or recoverable failure according to the API Non-actionable telemetry event Trace, profiling event, or appropriately leveled log Unrecoverable subsystem corruption Established fatal-error or termination mechanism Expected Failures Versus Contract Violations An expected failure can occur during an otherwise valid operation.\nFor example, opening a file may fail because the file does not exist, access is denied, or the underlying filesystem encounters an error.\nA contract violation occurs when an operation is invoked in a state prohibited by its documented interface.\nExpected failure:\nxiiResult LoadConfiguration(xiiStringView sPath) { xiiFileReader file; XII_SUCCEED_OR_RETURN(file.Open(sPath)); return ParseConfiguration(file); } Contract violation:\nvoid SetCapacity(xiiUInt32 uiCapacity) { XII_ASSERT_DEV(uiCapacity \u0026gt; 0U, \u0026#34;Capacity must be greater than zero.\u0026#34;); m_uiCapacity = uiCapacity; } The second example assumes positive capacity is an enforced programmer-facing precondition. If capacity originates from untrusted external data, validation must occur before calling this function.\nGuiding principle:\nExpected failures are communicated through results. Programming errors violate contracts. Diagnostics explain what happened without replacing either mechanism.\n12.2 Assertions Do Not Replace Runtime Validation Assertions are development-time mechanisms for detecting violated assumptions and internal invariants.\nTheir presence MUST NOT be required for correct execution in configurations where the assertions may be disabled.\nGeneral Requirements Assertions MUST express conditions expected to hold during valid execution. Do not place essential side effects inside assertion expressions. Do not assume that development assertions execute in every build configuration. Invalid external input MUST be handled through runtime validation. Runtime checks MUST remain active when their absence could permit invalid memory access, corrupted state, or other unacceptable behavior. Do not duplicate assertions and runtime checks mechanically when the API contract requires only one mechanism. Assertions SHOULD include meaningful diagnostic messages identifying the violated invariant. Do not use an assertion to suppress handling of a legitimate operational failure. Assertion conditions SHOULD remain inexpensive enough for their intended build configurations. Runtime Validation Prefer when invalid input must be handled safely:\nxiiResult InitializeStorage(xiiAllocator* pAllocator) { if (pAllocator == nullptr) return XII_FAILURE; return CreateStorage(pAllocator); } This example treats the allocator as a potentially absent dependency that must be validated at runtime.\nIf a valid allocator is instead an unconditional internal precondition, an assertion may be more appropriate.\nAssertion with Runtime Enforcement Both mechanisms may be justified when a condition simultaneously represents a programming error and must be guarded against in release configurations.\nXII_ASSERT_DEV(pDevice != nullptr, \u0026#34;A valid device is required.\u0026#34;); if (pDevice == nullptr) return XII_FAILURE; The assertion identifies an unexpected caller violation during development, while the runtime guard prevents the implementation from dereferencing a null pointer.\nThis pattern SHOULD NOT be applied indiscriminately. The necessity of runtime enforcement depends on the API\u0026rsquo;s trust boundary and documented failure contract.\nAssertion Side Effects Avoid:\nXII_ASSERT_DEV(InitializeResources().Succeeded(), \u0026#34;Resource initialization failed.\u0026#34;); If assertions are disabled, InitializeResources() may not execute.\nPrefer:\nconst xiiResult result = InitializeResources(); XII_ASSERT_DEV(result.Succeeded(), \u0026#34;Resource initialization failed.\u0026#34;); This preserves the side effect independently of assertion evaluation.\nHowever, if initialization failure is expected, handle or propagate the result rather than relying exclusively on an assertion.\nGuiding principle:\nAssertions verify contracts. Runtime validation enforces safety at boundaries where invalid conditions can legitimately occur or must be defended against.\n12.3 Log Actionable Diagnostic Context Diagnostic messages SHOULD explain which operation failed, what object or subsystem was involved, and which relevant conditions contributed to the failure.\nA useful diagnostic should reduce the amount of additional investigation required to identify the problem.\nGeneral Requirements Error messages SHOULD identify the operation that failed. Include relevant object names, identifiers, paths, resource types, or context values when available. Preserve underlying platform or backend error information when it materially helps diagnosis. Prefer precise failure descriptions over generic messages such as \u0026quot;Failed\u0026quot; or \u0026quot;Invalid state\u0026quot;. Do not log successful high-frequency operations unnecessarily. Avoid excessive diagnostic output that obscures meaningful failures. Do not log credentials, secrets, authentication tokens, or other sensitive information. Do not dump large objects or binary buffers without a demonstrated diagnostic requirement. Do not treat raw pointers as stable resource identities. Diagnostic formatting MUST follow XII\u0026rsquo;s established logging and formatting APIs. Diagnostic code MUST NOT access resources whose lifetime is no longer guaranteed. Messages SHOULD use established engine terminology and correct spelling. Prefer:\nxiiLog::Error(\u0026#34;Failed to load configuration \u0026#39;{0}\u0026#39;: {1}\u0026#34;, sConfigurationPath, sError); Avoid:\nxiiLog::Error(\u0026#34;Loading failed.\u0026#34;); The preferred diagnostic identifies the affected file and provides relevant failure context.\nThe examples assume that the diagnostic values are available and compatible with the selected logging API.\nStructured Diagnostic Context When several values are necessary to understand a failure, include them deliberately.\nFor example, a resource allocation failure may benefit from reporting:\nResource type. Requested size. Required alignment. Owning subsystem. Relevant memory budget. Underlying allocator or platform error. Not every diagnostic requires all available context.\nInclude the information needed to distinguish the failure from similar failures without producing unnecessarily verbose logs.\nLogging During Failure Handling Logging itself may allocate memory, acquire locks, or invoke formatting code.\nFailure-handling paths SHOULD avoid diagnostics that introduce additional failure risks, particularly during low-memory conditions, device loss, or subsystem shutdown.\nGuiding principle:\nDiagnostics should identify the failed operation, preserve relevant context, and help locate the cause without introducing unnecessary noise or additional failure risk.\n12.4 Do Not Continue After Required Operations Fail An operation MUST NOT continue into a state that depends on a failed prerequisite unless an explicit recovery or fallback path establishes the required invariants.\nGeneral Requirements Check failure-sensitive results before performing dependent operations. Propagate failures when the current function cannot meaningfully recover. Do not substitute default values for failed operations unless the fallback is part of the documented contract. Do not report success when required work was not completed. Failed operations MUST leave their owning components in documented valid states. Error paths MUST release or preserve acquired resources according to their ownership contracts. Do not retry failed operations indefinitely without a defined progress or termination policy. Recovery paths MUST establish that the original failure condition has been resolved or safely bypassed. Prefer:\nxiiResult LoadAndValidateDocument(xiiStringView sPath) { XII_SUCCEED_OR_RETURN(LoadDocument(sPath)); XII_SUCCEED_OR_RETURN(ValidateDocument()); return XII_SUCCESS; } Avoid:\nxiiResult LoadAndValidateDocument(xiiStringView sPath) { LoadDocument(sPath).IgnoreResult(); ValidateDocument().IgnoreResult(); return XII_SUCCESS; } The avoided implementation reports success even if the document could not be loaded or validated.\nRecovery and Fallback Continuing after failure is appropriate only when the operation has a defined recovery strategy.\nFor example, loading an optional configuration file may permit falling back to documented defaults.\nxiiResult LoadOptionalSettings(xiiStringView sPath) { if (LoadSettings(sPath).Succeeded()) return XII_SUCCESS; ApplyDefaultSettings(); return XII_SUCCESS; } This behavior is appropriate only when every failure from LoadSettings() is permitted to trigger the fallback.\nIf malformed configuration, permission errors, and missing optional files require different responses, the implementation must preserve enough error information to distinguish them.\nGuiding principle:\nA failed prerequisite invalidates the dependent path until recovery establishes a valid alternative.\n12.5 Error Propagation and Diagnostic Ownership Errors SHOULD be propagated to the layer responsible for deciding how the failure affects the application or subsystem.\nLogging and propagation are complementary, but repeated logging of the same failure can obscure its original cause.\nGeneral Requirements Propagate failures when the current layer cannot meaningfully resolve them. Add diagnostic context when crossing a boundary that provides relevant information. Avoid logging identical errors at every propagation layer. Preserve underlying failure information when it is needed for recovery or diagnosis. Do not replace specific errors with generic failures prematurely when callers require the distinction. Public interfaces MUST communicate failure through their documented return or output mechanism. Internal helpers SHOULD avoid deciding application-level failure policy unless that responsibility belongs to them. Error translation at platform boundaries MUST preserve the information necessary to distinguish important failure categories. Propagation Without Additional Context Prefer:\nxiiResult InitializeSubsystem() { XII_SUCCEED_OR_RETURN(InitializeStorage()); XII_SUCCEED_OR_RETURN(InitializeResources()); return XII_SUCCESS; } This is appropriate when the underlying operations already provide sufficient diagnostics or when a higher layer is responsible for reporting the failure.\nPropagation With Context xiiResult InitializeSubsystem() { if (InitializeResources().Failed()) { xiiLog::Error(\u0026#34;Failed to initialize subsystem resources.\u0026#34;); return XII_FAILURE; } return XII_SUCCESS; } The diagnostic adds useful subsystem context without requiring every lower-level helper to produce redundant messages.\nLogging Responsibility For complex call chains, establish a consistent diagnostic strategy.\nOne practical approach is:\nLow-level operations preserve detailed failure information. Intermediate layers propagate failures and add context only when useful. The responsible boundary logs the actionable failure or presents it to the caller. This is a guideline rather than a universal requirement. Some failures must be logged immediately because critical diagnostic information is unavailable after returning.\nGuiding principle:\nPropagate errors without losing their meaning. Log where context is useful, and avoid turning one failure into many redundant diagnostics.\n12.6 Transactional Operations and Rollback Operations that update multiple dependent structures MUST preserve consistency when an intermediate step fails.\nWhenever practical, prefer preparing and validating changes before publishing them to externally observable state.\nGeneral Requirements Multi-step operations MUST define their failure guarantees. Prefer completing potentially failing preparation before modifying authoritative state. Partial failure MUST NOT leave registries, indices, or ownership structures inconsistent. Rollback operations MUST preserve the original invariants. Avoid publishing partially initialized objects to unrelated consumers. Resources acquired during failed operations MUST be released or retained according to a documented ownership contract. Concurrent readers MUST NOT observe inconsistent intermediate state when the subsystem guarantees atomic updates. Use synchronization appropriate to the transaction\u0026rsquo;s scope. Do not introduce elaborate transaction frameworks for simple operations that can maintain consistency through straightforward ordering. Complex rollback paths SHOULD be tested with deliberate failure injection. Prepare, Validate, Commit A useful pattern for multi-step operations is:\nPrepare: Construct temporary state and acquire required resources. Validate: Confirm that all prerequisites and constraints are satisfied. Commit: Publish the completed state to the owning subsystem. Rollback: Release temporary resources or restore the previous valid state if an operation fails. Illustrative example:\nxiiResult RegisterResource(xiiStringView sName, xiiResourceHandle\u0026amp; out_hResource) { if (m_NameLookup.Contains(sName)) return XII_FAILURE; xiiUniquePtr\u0026lt;ResourceEntry\u0026gt; pEntry = CreateResourceEntry(sName); if (pEntry == nullptr) return XII_FAILURE; XII_SUCCEED_OR_RETURN(ValidateResourceEntry(*pEntry)); return CommitResourceEntry(std::move(pEntry), out_hResource); } This example assumes CommitResourceEntry() provides the required consistency and failure guarantees.\nIf commitment updates multiple structures and may fail partway through, it must provide rollback or another safe atomic-publication strategy.\nFailure Guarantees Where practical, an operation SHOULD provide the strong failure guarantee: failure leaves externally observable state unchanged.\nWhen that guarantee cannot reasonably be provided, the operation must at least preserve its documented invariants and clearly define which state changes may remain.\nGuiding principle:\nPrepare before publishing. Failure must not leave partially committed state that violates the subsystem\u0026rsquo;s contracts.\n12.7 Optional Values and Recoverable Absence Absence is not always an error.\nFunctions SHOULD distinguish expected absence from operational failure when callers need to handle those conditions differently.\nGeneral Requirements Use optional or nullable representations when the absence of a value is an expected outcome. Do not assert merely because a valid lookup finds no matching object. Do not use a generic failure result when expected absence is a distinct and meaningful outcome that callers must distinguish. Do not return null or an invalid handle without documenting its meaning. Multiple failure causes SHOULD remain distinguishable when callers require different recovery behavior. Avoid introducing complex error types when callers only need success or failure. Optional results MUST preserve clear ownership and lifetime semantics. Do not use sentinel values that overlap valid domain values without an explicit encoding contract. Expected Absence Example:\n[[nodiscard]] const xiiResourceEntry* FindResource(xiiStringView sName) const; Returning nullptr may be appropriate when no matching resource exists and absence is a normal lookup outcome.\nOperational Failure A function that loads or constructs a resource may require a different result contract.\nxiiResult LoadResource(xiiStringView sPath, xiiResourceHandle\u0026amp; out_hResource); This communicates an operation that can fail while producing a handle on success.\nRich Error Categories When callers must distinguish missing files, malformed data, unavailable dependencies, and permission failures, a richer result representation may be appropriate.\nDo not collapse these categories into a boolean if doing so prevents required recovery behavior.\nGuiding principle:\nAbsence is a value when it is expected. Failure is an outcome when an operation cannot fulfill its contract. Keep them distinct when the distinction matters.\n12.8 Destructors, Shutdown, and Failure Reporting Destruction and shutdown frequently occur when parts of the engine are no longer operational.\nCleanup paths MUST preserve resource safety without depending on services that may already be unavailable.\nGeneral Requirements Destructors MUST NOT propagate exceptions. Destructors MUST preserve safe cleanup behavior according to their ownership contracts. Cleanup SHOULD continue as far as safely possible when independent operations fail. Do not continue cleanup steps that depend on resources whose failure invalidated the required state. Explicit shutdown operations MAY return a failure result when callers must observe cleanup failures. Avoid performing unbounded waits during destruction unless required by the documented lifetime contract. Logging during shutdown MUST respect the lifetime of the logging subsystem. Do not access destroyed allocators, platform services, or graphics devices while reporting cleanup failures. Failure handling MUST account for partially initialized resources. Important shutdown failures SHOULD preserve diagnostic context before the relevant subsystem becomes unavailable. Explicit Shutdown Results When cleanup can legitimately fail and the caller needs to know, an explicit shutdown operation may expose the failure.\nxiiResult ShutdownSubsystem() { xiiResult result = FlushPendingData(); ReleaseOwnedResources(); return result; } This example assumes resource release remains safe regardless of the flush result.\nIt also assumes FlushPendingData() does not invalidate the resources required by ReleaseOwnedResources().\nDestructors and Error Channels Destructors cannot return xiiResult.\nConsequently, operations requiring meaningful recoverable failure reporting SHOULD expose an explicit close, flush, or shutdown method when appropriate.\nDestruction may then provide final safety cleanup without promising recoverable error reporting.\nGuiding principle:\nShutdown must preserve ownership and safety even when operations fail. Report meaningful failures while the required diagnostic infrastructure is still available.\n12.9 Diagnostic Levels and Logging Performance Logging and diagnostics can introduce meaningful runtime overhead through formatting, allocation, synchronization, and I/O.\nDiagnostic instrumentation SHOULD be appropriate to the severity and frequency of the event.\nGeneral Requirements Select diagnostic severity according to the impact and actionability of the event. Avoid high-frequency informational or success logs in performance-critical paths. Do not eagerly construct expensive diagnostic strings when the corresponding logging level is disabled. Prefer existing XII logging and profiling mechanisms over introducing independent diagnostic systems. Repeated failures SHOULD be rate-limited or aggregated when unbounded logging would obscure useful information or degrade performance. Diagnostic instrumentation MUST NOT change required synchronization or lifetime behavior. Do not rely on logging for essential program state changes. Profiling and tracing SHOULD use established structured instrumentation where available. Logging overhead in demonstrated hot paths SHOULD be measured when relevant. Diagnostic output MUST remain safe under the subsystem\u0026rsquo;s threading and shutdown contracts. Diagnostic Categories Category Typical purpose Error Operation failed or a required condition could not be satisfied Warning Unexpected or degraded behavior that permits continued operation Information Meaningful lifecycle or operational event Debug Development-oriented state or behavior details Trace Fine-grained execution information Profiling Timings, counters, allocations, and performance measurements Use the categories and APIs actually provided by XII rather than introducing parallel naming conventions.\nHigh-Frequency Logging Avoid:\nvoid UpdateSimulation() { xiiLog::Info(\u0026#34;Simulation update started.\u0026#34;); AdvanceSimulation(); xiiLog::Info(\u0026#34;Simulation update completed.\u0026#34;); } If this function executes frequently, the logs may become noisy and expensive without providing useful diagnostic value.\nPrefer profiling scopes or selectively enabled trace diagnostics when detailed timing or execution information is required.\nDiagnostic Construction A disabled log statement should ideally avoid unnecessary formatting and allocation work.\nFollow the behavior of XII\u0026rsquo;s logging macros and functions when determining whether argument evaluation or formatting occurs.\nDo not assume every logging API lazily evaluates its arguments.\nGuiding principle:\nDiagnostics must be useful and proportionate. Their cost should not dominate the operation they are intended to explain.\n12.10 Security and Malformed Input Data originating outside a trusted subsystem MUST be treated as potentially malformed, inconsistent, or adversarial.\nThis includes file contents, serialized objects, network packets, shader source, plugin-provided data, configuration files, and application arguments.\nGeneral Requirements Validate untrusted input before using it to calculate memory sizes, offsets, indices, or allocation requirements. Check integer overflow before arithmetic involving untrusted sizes, counts, strides, or dimensions. Validate indices and offsets against the actual bounds of the referenced storage. Validate declared lengths against available data before reading or copying. Establish reasonable limits for recursion depth, nesting, object counts, and resource consumption where required. Validate enum values and identifiers before interpreting them as trusted internal state. Reject unsupported or malformed encodings where the consuming interface requires valid text. Do not use assertions as the sole validation mechanism for untrusted input. Do not assume that successful parsing guarantees semantic validity. Resource allocation decisions MUST account for validated sizes and applicable memory limits. Error handling MUST NOT expose sensitive data unnecessarily. Input validation SHOULD occur at the boundary where untrusted data enters the responsible subsystem. Validation MUST remain consistent with the actual platform and serialization contracts. Bounds Validation Prefer:\nxiiResult ReadPayload(xiiArrayPtr\u0026lt;const xiiUInt8\u0026gt; pData, xiiUInt64 uiOffset, xiiUInt64 uiLength) { const xiiUInt64 uiDataSize = pData.GetCount(); if (uiOffset \u0026gt; uiDataSize) return XII_FAILURE; if (uiLength \u0026gt; uiDataSize - uiOffset) return XII_FAILURE; return ProcessPayload(pData, uiOffset, uiLength); } Subtracting after validating the offset avoids overflow that could occur in a direct uiOffset + uiLength bounds check.\nThe example assumes ProcessPayload() accepts these index widths and respects the validated range.\nResource Exhaustion Malformed or adversarial inputs may request unreasonable amounts of memory or processing work without containing an explicitly invalid field.\nInput boundaries SHOULD establish appropriate resource limits when such requests could affect system availability.\nExamples include:\nExcessively large array counts. Unbounded recursive structures. Oversized shader permutations. Pathological serialized object graphs. Unexpectedly large resource dimensions. Repeated registration requests. Excessive string or allocation lengths. These limits should reflect supported workloads rather than arbitrary restrictions that prevent valid use.\nParsing and Validation Parsing establishes that input conforms to a syntactic representation.\nSemantic validation must separately establish that the parsed values satisfy the requirements of the consuming operation.\nFor example, a configuration may parse successfully while specifying incompatible settings or values outside the supported domain.\nBoth stages may require distinct failure reporting.\nGuiding principle:\nExternal data is not trustworthy merely because it can be parsed. Validate representation, bounds, resource requirements, and semantic constraints before use.\n12.11 Platform and Third-Party Error Translation Platform APIs and third-party libraries may expose error mechanisms that differ from XII\u0026rsquo;s ordinary result conventions.\nIntegration boundaries MUST translate these mechanisms into stable engine-facing contracts where appropriate.\nGeneral Requirements Platform-specific error handling SHOULD remain within the corresponding implementation layer. Preserve relevant native error codes or diagnostic context when they are necessary for diagnosis. Do not expose arbitrary platform-specific error types through portable interfaces without a documented interoperability requirement. Translate recoverable failures into xiiResult or an appropriate engine-compatible result type. Exceptions encountered at unavoidable third-party or platform boundaries MUST NOT propagate through ordinary engine APIs. Error translation MUST NOT silently convert failure into success. Do not discard distinctions between native failures when callers require them for recovery. Device-loss, out-of-memory, and other significant platform failures MUST follow the owning subsystem\u0026rsquo;s documented recovery policy. Platform diagnostics SHOULD identify the failing operation and the relevant native error category. Do not assume that native error codes have identical meanings across platforms or backends. Native Error Context A portable operation may expose a simple failure result while the platform implementation records richer native details.\nFor example, a filesystem operation may return XII_FAILURE while reporting the relevant platform error through the established diagnostic system.\nWhen callers require programmatic access to native or categorized errors, use an explicit error representation rather than requiring them to parse log messages.\nException Boundaries Where a third-party API can throw exceptions and the integration environment supports catching them, exception handling must remain inside the isolated adapter.\nWhen the build configuration does not support C++ exception handling, the adapter must use a suitable non-throwing interface or another explicitly supported integration strategy.\nGuiding principle:\nPlatform differences belong at platform boundaries. Preserve useful native diagnostics while maintaining consistent engine-facing failure semantics.\n12.12 Fatal Errors and Unrecoverable States Some failures invalidate fundamental engine assumptions and cannot be safely recovered from within the current execution context.\nSuch failures MUST be distinguished from ordinary operational errors.\nGeneral Requirements Fatal-error mechanisms MUST be reserved for conditions where continued execution cannot preserve required correctness or safety guarantees. Do not terminate execution for ordinary recoverable failures merely because recovery is inconvenient. Unrecoverable corruption MUST NOT be hidden behind a generic success or fallback result. Fatal diagnostics SHOULD preserve the most relevant context available without relying on invalid subsystem state. Do not perform unsafe cleanup after corruption has invalidated the invariants required by that cleanup. Fatal-error handling MUST respect the engine\u0026rsquo;s established platform and diagnostic mechanisms. Do not invent independent termination policies within individual components without architectural justification. Distinguish local operation failure from failure requiring subsystem or process termination. Recovery Scope A failure may be unrecoverable for one operation while remaining recoverable at a higher level.\nFor example:\nA document parser may reject a malformed file. A resource manager may fail to load an asset. A graphics subsystem may need to rebuild resources following a supported device-recovery event. An invalid fundamental memory invariant may prevent safe continuation. The appropriate response depends on the actual guarantees of the affected subsystem.\nDo not classify every hardware or platform failure as fatal without evaluating the supported recovery contract.\nGuiding principle:\nFatal errors indicate that safe continuation cannot be guaranteed. Reserve them for genuine unrecoverable states rather than ordinary operational failure.\n12.13 Error-Handling Tests and Failure Injection Correctness testing SHOULD cover failure behavior, not only successful execution.\nFailure paths often exercise ownership, cleanup, synchronization, and state transitions that ordinary tests do not reach.\nGeneral Requirements Test expected failure conditions for operations with explicit failure contracts. Verify that failed operations preserve documented invariants. Exercise partial initialization and cleanup where multiple resources are acquired. Test registration rollback when several authoritative or secondary structures must be updated. Validate stale-handle rejection and resource invalidation where applicable. Test malformed external inputs, including boundary values and inconsistent lengths. Verify that required operations do not continue after failed prerequisites. Test shutdown behavior when resources or tasks remain outstanding. Use failure injection when it provides a practical means of exercising otherwise rare error paths. Do not require external infrastructure failures to occur naturally before validating critical recovery behavior. Diagnostic tests SHOULD focus on meaningful error context and severity rather than unnecessarily brittle exact-message comparisons. Tests MUST NOT depend on debug-only assertions to establish runtime behavior required in release builds. Failure Injection Useful failure-injection scenarios may include:\nAllocation failure. File-open failure. Partial resource initialization. Invalid serialized data. Registration conflict. Resource exhaustion. Backend initialization failure. Asynchronous cancellation. Simulated device loss where supported. Subsystem shutdown during pending work. Injected failures should reproduce the relevant contract without introducing unrelated undefined behavior.\nRecovery Verification A successful failure-handling test should establish more than the presence of an error result.\nWhere applicable, verify that:\nThe failure is observable to the caller. Relevant diagnostic context is preserved. No required dependent operation executes incorrectly. Acquired resources are released or retained safely. Registries and secondary mappings remain consistent. The component reaches its documented post-failure state. Retry or recovery behaves correctly when supported. Guiding principle:\nFailure handling is part of implementation correctness. Test the guarantees that remain after an operation fails, not merely whether an error was returned.\n13. Contributor and Reviewer Checklists Contributors and reviewers share responsibility for maintaining XII\u0026rsquo;s correctness, architectural consistency, portability, and long-term maintainability.\nThe following checklists provide a practical review framework for implementing and evaluating changes. They complement the requirements established throughout this document and MUST NOT be treated as substitutes for understanding the contracts of the affected subsystems.\nNot every checklist item applies to every change. Contributors SHOULD evaluate the items relevant to their work, while reviewers SHOULD focus on correctness, design decisions, integration risks, and the adequacy of validation.\n13.1 Before Writing Code Before beginning implementation, contributors SHOULD establish the responsibility, scope, and expected behavior of the proposed change.\nArchitecture and Scope I understand the problem being solved and can describe the expected behavior. I have identified the module or subsystem responsible for the functionality. I have reviewed existing implementations and APIs that may already provide the required capability. I understand the dependency direction between the affected modules. I have identified the public interfaces, plugins, tools, and applications that may be affected. I have considered whether the change affects reflection, serialization, persistent formats, or exported binary interfaces. I am not introducing an abstraction, manager, registry, or configuration mechanism solely for hypothetical future requirements. I have considered whether the implementation can remain localized without compromising architectural boundaries. Ownership and Lifetime I can identify who creates, owns, and destroys every new resource. I understand whether objects require stable addresses, stable logical identities, or generation-checked handles. I have identified the lifetime of borrowed pointers, references, views, iterators, and callbacks. I understand the initialization and shutdown dependencies of the affected components. I know whether asynchronous work can outlive the scope or component that initiates it. I have considered whether concurrent access or destruction requires synchronization. Execution and Performance I know whether the code executes during initialization, shutdown, ordinary runtime operation, or a performance-critical path. I understand the expected workload, data volume, and access patterns. I have identified any relevant latency, allocation, memory, or throughput requirements. I am not introducing performance-sensitive complexity without an identifiable need. I have considered whether the behavior depends on platform capabilities, backend implementations, or build configuration. Implementation Preparation I have reviewed neighboring source files for established design and naming conventions. I understand the relevant .clang-format, .editorconfig, and module build requirements. I have identified the appropriate header and Implementation directory placement. I understand how expected failures will be communicated without exceptions. I have considered which existing tests should be updated and which additional tests may be necessary. Guiding principle:\nBefore implementing a change, establish its responsibility, contracts, dependencies, and validation requirements. Design decisions should be deliberate rather than discovered accidentally during integration.\n13.2 Before Submitting Code Before submitting a change, contributors MUST verify that the implementation satisfies the applicable coding standards and preserves the documented behavior of affected components.\nSource Organization and Formatting Modified C++ code follows the repository\u0026rsquo;s .clang-format and .editorconfig configurations. License and copyright notices appear before #pragma once or the implementation PCH, as applicable. Header files include their module\u0026rsquo;s DLL.h immediately after #pragma once where required. Implementation files use their module PCH first after the license notice where required. Implementation files are placed in the appropriate Implementation directory. Public headers compile independently and include their required dependencies. Include ordering and module dependencies follow the repository\u0026rsquo;s conventions. Naming, member prefixes, and symbol visibility follow the established XII rules. No unrelated formatting changes, obsolete implementations, or temporary debugging code remain. Correctness and API Design The implementation solves the stated problem without unnecessary abstraction or indirection. Public interfaces expose meaningful capabilities rather than incidental implementation details. Object construction establishes valid initial states. Preconditions, postconditions, and expected failure behavior are clear. Recoverable failures use explicit results rather than exceptions or assertions. Failure-sensitive return values are checked or intentionally handled. Output parameters and observable state remain valid according to their failure contracts. Integer arithmetic, conversions, array bounds, and allocation sizes are validated where required. Floating-point operations use appropriate precision and comparison semantics. Control flow does not depend on ambiguous evaluation order or hidden side effects. State transitions preserve the invariants of their owning components. Ownership and Memory Every dynamic allocation and long-lived resource has an identifiable owner. Allocation and destruction mechanisms are compatible. Allocators outlive every allocation that depends on them. Copy and move semantics reflect the intended ownership and identity contracts. Borrowed pointers, references, strings, spans, views, and iterators do not outlive their backing storage. Container growth, removal, and relocation do not invalidate persistent references unexpectedly. Handles are validated according to their authoritative registry. Resource destruction and invalidation preserve registry consistency. Shared ownership does not introduce unintended ownership cycles. Initialization failure and shutdown paths release resources safely. Concurrency and Asynchronous Execution Shared mutable state has an explicit synchronization contract. Lock ordering, thread affinity, and reentrancy requirements are respected. Resource lookup does not expose objects to concurrent destruction without lifetime protection. Asynchronous tasks retain valid access to their dependencies until completion. Cancellation and shutdown do not invalidate resources still in use. Callback registrations are removed or invalidated before their receivers are destroyed. Plugin-owned callbacks cannot execute after the corresponding plugin is unloaded. Execution ordering is deterministic where the subsystem requires determinism. Graphics and Hardware Integration Where applicable:\nGPU resource states and transitions satisfy the active graphics API\u0026rsquo;s requirements. CPU submission is not confused with GPU completion. Resource retirement and reuse respect outstanding queue dependencies. Frame-scoped and device-scoped resources have compatible lifetimes. Multiple views, contexts, and frames in flight remain isolated where required. Feature queries correctly distinguish compile-time availability from runtime hardware support. Backend-specific behavior preserves the common interface contract. GPU-facing data layouts satisfy the required alignment and representation rules. Compatibility and Validation Exported interfaces and DLL visibility have been reviewed where applicable. Reflection metadata and registration remain correct. Serialized data and persistent identifier compatibility have been considered. Platform-specific and conditional compilation paths remain valid. Relevant modules and dependent targets compile successfully. Existing tests pass, and new behavior has appropriate test coverage. Failure paths and boundary conditions have been tested where practical. Performance-sensitive changes are supported by representative measurements. Comments, documentation, diagnostics, and samples reflect the implemented behavior. Any unverified platforms, backends, or configurations are identified in the change description. Pull Request Quality The change description explains the problem, approach, and relevant design decisions. The scope is focused and does not include unrelated refactoring. Non-obvious ownership, synchronization, and compatibility decisions are documented. Any intentional deviation from a SHOULD rule has a clear justification. Applicable MUST requirements have been satisfied or formally addressed under §14. Reviewers have enough information to evaluate correctness without reconstructing the entire development process. Guiding principle:\nSubmission readiness means more than successful compilation. A change must preserve its contracts, integrate correctly, and provide sufficient evidence for review.\n13.3 Reviewer Checklist Reviewers SHOULD evaluate the correctness of the proposed design and implementation rather than focus exclusively on formatting or local coding style.\nAutomated tools should handle mechanical requirements where possible, allowing human review to concentrate on semantics, architecture, and risks that require engineering judgment.\nDesign and Responsibility Does the change solve the stated problem without introducing unnecessary complexity? Is the functionality implemented in the appropriate module or abstraction layer? Are interfaces cohesive and limited to demonstrated consumer requirements? Are names precise enough to communicate responsibilities and behavior? Does the implementation preserve established dependency direction? Are abstractions justified by actual ownership, reuse, extensibility, or invariants? Does the change unnecessarily duplicate existing engine functionality? Correctness and Invariants Are all relevant preconditions and postconditions satisfied? Can callers construct invalid objects or invoke operations in unsafe lifecycle states? Are expected failures distinguished from programming errors? Can any failure path leave partially committed or inconsistent state? Are numeric conversions, indexing, bounds, and arithmetic safe? Does the implementation rely on undefined behavior or unsupported platform assumptions? Are control flow and side effects predictable? Do assertions verify invariants without providing essential program behavior? Ownership and Lifetime Is there a clear authority responsible for every resource? Are allocation and destruction mechanisms compatible? Do borrowed references remain valid for their intended use? Can container relocation or removal invalidate retained pointers? Are handles validated through the appropriate registry? Does destruction preserve outstanding asynchronous dependencies? Are copy and move operations semantically safe? Can callbacks or shared ownership create lifetime cycles or use-after-free risks? Concurrency and Execution Can concurrent lookup, mutation, or destruction invalidate active operations? Are locking and synchronization boundaries sufficient without being unnecessarily broad? Are lock ordering and reentrancy requirements respected? Can asynchronous work access resources after their owners are destroyed? Does cancellation provide the guarantees expected by its callers? Is observable ordering preserved where deterministic behavior is required? Are GPU submission, completion, and resource retirement correctly distinguished? Portability and Compatibility Will the change work across the intended platforms, compilers, and build configurations? Are backend-specific dependencies isolated appropriately? Are optional capabilities checked rather than assumed? Are public ABI, reflection, and serialization contracts preserved? Are persistent identifiers and binary formats compatible where required? Can existing plugins and downstream consumers continue using the interface as intended? Performance and Maintainability Does the chosen algorithm or container match the relevant access patterns? Are new allocations, caches, or synchronization mechanisms justified? Have measurements demonstrated the benefit of performance-sensitive complexity? Does the implementation remain understandable without unnecessary indirection? Are diagnostics actionable and proportionate to their execution frequency? Do tests exercise likely regression and failure scenarios? Is the implementation maintainable without requiring undocumented subsystem knowledge? Review Priorities Reviewers SHOULD prioritize findings in the following order:\nCorrectness, safety, and violated invariants. Ownership, lifetime, and synchronization. Architectural boundaries and public contracts. Portability, compatibility, and failure behavior. Performance characteristics and demonstrated regressions. Maintainability, readability, and documentation. Mechanical formatting and stylistic consistency. This ordering does not make formatting optional. It reflects the relative value of human review attention when automated tooling can enforce mechanical requirements.\nGuiding principle:\nReview the behavior and contracts of the system, not merely the appearance of the code. A successful review establishes confidence in correctness, integration, and future maintainability.\n13.4 Specialized Checklist: Render Graph Changes Render graph changes require additional review because logical resource declarations, execution dependencies, physical allocations, and GPU synchronization operate across distinct lifetime boundaries.\nThis checklist supplements the general requirements and applies only to changes involving XII\u0026rsquo;s render graph or related rendering systems.\nGraph Construction and Compilation Every pass declares the resources it reads and writes. Imported resources have explicit ownership and lifetime contracts. Side effects and ordering constraints are represented correctly. Resource versions are associated with the correct logical graph. Pass dependencies preserve the intended execution semantics. Resource culling does not eliminate passes or resources with required side effects. Compilation failures preserve valid graph and resource state. Cached compilation results are invalidated when their dependencies change. Resource Identity and Ownership Logical handles belong to the correct graph and resource domain. Resource versioning prevents invalid access to outdated logical states. Transient resources have well-defined allocation and retirement boundaries. Temporal histories remain valid across the frames and views that require them. Graph-owned resources are released before required device infrastructure. Shared resources are not invalidated by unrelated graphs or submissions. Physical resource aliasing respects actual GPU execution dependencies. Execution and Synchronization Execution identifies the correct graph, view, and execution context. Registration and destruction cannot race active graph operations without adequate synchronization. Graph execution does not implicitly start or end unrelated frame lifecycles. Shared frame resources are not reset between unrelated submissions within the same active frame. CPU-side execution completion is not treated as GPU completion. Resource state transitions satisfy the requirements of the active backend. Queue ownership and synchronization are correct for the required execution dependencies. Physical allocations are not recycled before the relevant GPU work has completed. Readback resources and profiling timestamps remain alive until their results are safe to consume. Multiple Views and Frames Per-view state is isolated where the contract requires independent histories. Graph-local blackboards do not leak state across unrelated graph executions. Shared resources have explicit synchronization and lifetime guarantees. Temporal resources remain valid across the required frames in flight. Resizing and swap-chain recreation preserve valid resource transitions. Zero-area or unavailable swap-chain conditions are handled where applicable. Graph recompilation and resource replacement do not invalidate outstanding GPU work. Validation Relevant graph compilation and execution tests pass. Resource lifetime tests cover invalidation and retirement behavior. Changes are verified against supported graphics backends where practical. GPU validation does not report new synchronization or resource-state errors. Profiling and timestamp behavior remains correct. Performance-sensitive changes include representative CPU and GPU measurements. Any backend-specific limitations are documented. Guiding principle:\nRender graph correctness requires agreement between logical dependencies, resource ownership, execution scheduling, and physical GPU lifetimes. These contracts must be validated independently and together.\n14. Maintenance, Exceptions, and Standards Governance XII\u0026rsquo;s coding standards establish a consistent engineering contract while allowing justified evolution as the engine\u0026rsquo;s architecture, supported platforms, and development tools change.\nThe standards MUST be maintained deliberately. Deviations should reflect concrete technical requirements rather than individual preference or convenience.\nThis section governs the interpretation, maintenance, and evolution of the standards document.\n14.1 Interpreting Requirement Levels The terms MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY express different levels of obligation.\nTheir meanings apply consistently throughout this document.\nTerm Meaning MUST Mandatory requirement for applicable code MUST NOT Prohibited behavior SHOULD Strong recommendation that may be overridden with a justified reason SHOULD NOT Strong recommendation against a practice, with justified exceptions permitted MAY Optional practice permitted when appropriate General Requirements MUST requirements are normative and MUST NOT be bypassed informally. MUST NOT requirements prohibit the described behavior unless an approved exception or policy revision explicitly permits it. Deviations from SHOULD requirements MUST have a defensible technical rationale. A MAY statement grants permission but does not imply that the practice is preferred. Requirements MUST be interpreted within their documented scope. Examples MUST NOT be treated as additional mandatory rules when their accompanying text explicitly identifies them as illustrative. Existing compatibility constraints MUST be considered when applying standards to previously established APIs. Exceptions to Recommended Practices A deviation from a SHOULD rule may be justified by:\nPlatform or compiler limitations. Existing public interface compatibility. Established subsystem architecture. Demonstrated performance requirements. Clearer local implementation. Toolchain or generated-code constraints. External API requirements. Ownership or lifetime contracts that make the recommended approach unsuitable. Such deviations SHOULD be documented when their rationale would not be apparent to future maintainers.\nExceptions to Mandatory Requirements A MUST requirement should not be bypassed through an undocumented local exception.\nWhen compliance is genuinely impossible or would violate a more fundamental system requirement, contributors MUST follow the project\u0026rsquo;s established design-review or standards-amendment process.\nThe proposed deviation should identify:\nThe affected requirement. The technical reason compliance is not possible or appropriate. The scope of the deviation. The alternatives considered. The correctness and compatibility implications. The tests or validation supporting the decision. Approval of a specific exception does not automatically establish a new repository-wide convention.\nGuiding principle:\nMandatory rules establish engineering contracts. Recommended practices permit judgment, but deviations must be intentional and technically justified.\n14.2 Avoid Silent Policy Drift Coding standards must evolve alongside the engine, but changes to established conventions MUST be deliberate.\nA pattern appearing in several implementation files does not automatically make it an approved repository-wide requirement.\nGeneral Requirements New conventions SHOULD be documented when they become established across relevant subsystems. Experimental implementations MUST NOT be presented as universal standards without architectural review. Changes to naming, formatting, ownership, or interface conventions MUST consider existing code and compatibility requirements. New engine abstractions SHOULD be incorporated into the guide when they replace or substantially change established practices. Documentation examples MUST remain representative of supported engine interfaces. Deprecated patterns SHOULD be identified when a supported replacement has been established. Do not introduce contradictory guidance across sections without resolving or explicitly scoping the difference. Standards updates SHOULD distinguish mandatory policy changes from additional recommendations or explanatory examples. Repository-wide migrations SHOULD be planned separately from unrelated functional changes. Triggers for Standards Review A standards update may be appropriate when:\nA new allocator or container becomes an established engine abstraction. Resource handles adopt revised identity or generation semantics. Module export or binary interface conventions change. Reflection or serialization mechanisms evolve. A supported compiler or C++23 feature changes the preferred implementation pattern. New platform or graphics backend requirements affect portable interfaces. The build system adopts new formatting, analysis, or testing tools. A previously recommended pattern produces recurring correctness or maintenance problems. An architectural change establishes a new lifecycle or concurrency contract. These developments should be evaluated before changing mandatory language in the document.\nMigration Scope A new standard does not automatically justify rewriting all existing code.\nBefore introducing a broad migration, consider:\nCompatibility risks. Review complexity. Version-control noise. Tooling support. Measurable maintenance benefits. Interaction with active development. Whether gradual adoption is safe. Prefer incremental adoption when it preserves consistency and avoids unnecessary disruption.\nGuiding principle:\nStandards should evolve through reviewed engineering decisions, not through accidental repetition or undocumented changes in local practice.\n14.3 Authority and Provenance This document consolidates XII\u0026rsquo;s established coding conventions with additional engineering requirements intended to guide development across the engine.\nNot every convention has the same historical origin or enforcement status.\nEstablished Repository Conventions The original Coding-Standards.md provides the baseline naming and tooling conventions incorporated into this guide.\nThese include:\nEngine naming and symbol conventions. Type-dependent member prefixes. The distinction between mandatory member prefixes and optional local-variable or parameter prefixes. Existing flexibility for public fields, static fields, and fixed-size arrays. Repository formatting and tooling conventions. These baseline rules MUST remain accurately represented unless intentionally revised through the project\u0026rsquo;s standards governance process.\nExtended Engineering Requirements The additional sections establish broader guidance for:\nCorrectness and invariants. Ownership and lifetime. Resource and allocator management. Concurrency and asynchronous execution. Public interface design. Error handling and diagnostics. Container selection and performance. Portability and platform abstraction. GPU resource management. Testing and review practices. These requirements are intended to form a coherent engineering standard for XII rather than merely describe historical source-code patterns.\nVerification Requirements Before treating a repository-specific convention as authoritative, its details SHOULD be verified against the current implementation and project configuration.\nRelevant sources include:\n.clang-format .editorconfig Module build configuration. Supported compiler and standard-library versions. Existing public interfaces. Allocator and container contracts. Reflection and serialization declarations. Platform and graphics backend implementations. Automated tests and continuous integration configuration. Existing contributor documentation. Illustrative code examples MUST NOT be interpreted as proof that a particular engine API, enum, macro, or helper exists unless it has been verified against the repository.\nWhere examples describe conceptual APIs, they should remain clearly distinguishable from established interfaces.\nSource of Authority When documents or examples appear to conflict, contributors SHOULD resolve the discrepancy according to the following considerations:\nExplicitly approved project standards and architectural requirements. Current build, formatting, and platform configuration. Documented public API and subsystem contracts. Established implementation conventions. Illustrative examples and informal recommendations. This ordering is guidance for resolving ambiguity, not permission to ignore mandatory requirements. A substantive conflict between an approved standard and the implementation should be resolved through review rather than silently choosing whichever is more convenient.\nGuiding principle:\nA coding standard must distinguish established requirements, approved extensions, and illustrative guidance. Authority should be explicit and verifiable.\n14.4 Standards Changes and Review Changes to this document SHOULD receive the same engineering scrutiny as changes to shared engine interfaces.\nA wording change can alter implementation expectations, compatibility requirements, or the interpretation of existing code.\nGeneral Requirements Standards changes MUST be internally consistent with the rest of the document. New mandatory requirements SHOULD identify the correctness, maintainability, portability, or compatibility problem they address. Do not introduce requirements solely to formalize personal preferences. Rules SHOULD be sufficiently precise that contributors can determine whether an implementation complies. Examples SHOULD demonstrate the intended behavior without introducing unnecessary subsystem-specific assumptions. Standards updates MUST consider existing public APIs and compatibility obligations. New requirements SHOULD be enforceable through review, tests, static analysis, or automated tooling where practical. Do not establish rules that depend on unsupported compiler or platform features. Avoid duplicate requirements that create inconsistent interpretations across sections. Substantial changes SHOULD identify whether they apply to new code, modified code, or existing code requiring migration. Proposed Rule Evaluation Before adding a new requirement, consider:\nPurpose: What concrete problem does the rule prevent? Scope: Which modules, platforms, or code categories does it affect? Correctness: Does it strengthen an enforceable invariant or contract? Consistency: Does it agree with existing XII architecture? Feasibility: Can supported toolchains and environments comply? Compatibility: Would adoption break public interfaces or persistent data? Verification: Can violations be identified reliably? Cost: Does the benefit justify additional implementation or review complexity? A requirement that cannot be explained or verified clearly SHOULD be reconsidered before adoption.\nGuiding principle:\nStandards are engineering interfaces for contributors. Changes to them should be precise, justified, and reviewed for their consequences.\n14.5 Automated Enforcement and Human Judgment Automated tooling SHOULD enforce objective and mechanically verifiable requirements wherever practical.\nHuman review should concentrate on correctness, architecture, ownership, concurrency, and other matters that require interpretation.\nAutomated Enforcement Suitable automated checks may include:\nClangFormat compliance. Whitespace and line-ending validation. Required license notices. Include-order or header self-sufficiency checks. Compiler warnings. Static analysis. Supported sanitizers. Build configuration validation. Unit and integration tests. Serialization compatibility tests. Platform and backend compilation. These tools should follow the repository\u0026rsquo;s established configuration and supported toolchains.\nHuman Review Human reviewers remain responsible for evaluating:\nWhether abstractions are justified. Whether interfaces preserve meaningful invariants. Whether ownership and lifetime relationships are correct. Whether concurrency guarantees are sufficient. Whether error-handling policy matches the operation. Whether performance complexity is evidence-based. Whether cross-module compatibility is preserved. Whether the implementation remains maintainable. Automated success MUST NOT be treated as proof of architectural or semantic correctness.\nLikewise, reviewers SHOULD NOT repeatedly debate formatting decisions already governed by authoritative tooling.\nGuiding principle:\nAutomate objective consistency and reserve engineering judgment for decisions that tools cannot establish reliably.\n14.6 Technical Debt and Legacy Code Existing code may predate the conventions established in this document.\nSuch code SHOULD be improved deliberately without creating unnecessary compatibility risks or unrelated changes.\nGeneral Requirements New code MUST follow the applicable current standards. Modified code SHOULD move toward current standards where practical and compatible with the change. Do not rename established public symbols solely for stylistic consistency without reviewing their consumers. Avoid broad mechanical refactoring within unrelated functional changes. Legacy patterns MUST NOT automatically be treated as approved conventions for new implementations. Necessary compatibility behavior SHOULD remain documented until an approved migration is completed. Technical debt SHOULD be addressed when it materially affects correctness, maintainability, or future development. Do not introduce replacement abstractions without identifying how existing consumers will transition. Large migrations SHOULD include a plan for validation, compatibility, and rollback where applicable. Incremental Improvement When modifying legacy code, prioritize:\nCorrectness and undefined behavior. Ownership, lifetime, and resource safety. Failure handling and invariant enforcement. Concurrency and synchronization. Public interface consistency. Structural maintainability. Formatting and naming improvements. This ordering reflects engineering risk rather than permission to disregard formatting requirements for modified code.\nGuiding principle:\nImprove legacy code where the change provides real value, but avoid turning focused maintenance into uncontrolled architectural migration.\n14.7 Documentation and Example Maintenance Examples in this document SHOULD remain representative of current XII development practices.\nIncorrect examples can establish misleading expectations even when the surrounding prose is accurate.\nGeneral Requirements Code examples SHOULD follow current naming and formatting conventions. Repository-specific examples SHOULD use verified APIs where practical. Conceptual APIs MUST be identified as illustrative when they are not guaranteed to exist. Examples MUST NOT intentionally demonstrate undefined behavior as a preferred implementation. Examples involving resource lifetime or concurrency SHOULD state important assumptions when those assumptions are necessary for correctness. Updated engine mechanisms SHOULD replace obsolete examples when their contracts have changed. Documentation MUST distinguish recommendations from mandatory requirements. Do not use examples to silently introduce additional requirements that are absent from the normative text. Example Design Prefer examples that:\nDemonstrate one primary principle. Use familiar XII types and naming patterns. Avoid unnecessary platform or subsystem dependencies. Make ownership and failure behavior clear. Remain short enough to understand without substantial surrounding implementation. Preserve relevant correctness assumptions. Examples do not need to be production-complete implementations when their purpose is conceptual, but they must not imply guarantees the actual APIs do not provide.\nGuiding principle:\nExamples should clarify standards, not create ambiguity. Their assumptions, applicability, and relationship to actual engine APIs must remain clear.\n14.8 Guiding Design Test When evaluating competing designs, contributors and reviewers SHOULD ask:\nCan a new contributor understand what this code owns, when it runs, what makes it fail, and which invariants it must preserve without reading unrelated implementation files?\nA strong design should provide clear answers to the following questions:\nResponsibility: What does this component do, and what does it intentionally not do? Ownership: Who creates, manages, and destroys its resources? Lifetime: When are its objects, handles, and borrowed references valid? Execution: When does it run, and what controls its scheduling? Failure: Which failures are expected, and how are they communicated? State: What invariants must hold before and after each operation? Concurrency: Which operations may run concurrently, and what synchronization is required? Dependencies: Which modules and external systems does it depend on? Compatibility: Which public or persistent contracts must remain stable? Performance: What workload characteristics justify its complexity? Validation: How can its correctness and performance be demonstrated? The preferred design is not necessarily the shortest, most abstract, or most optimized implementation.\nIt is the design that satisfies the actual requirements with the clearest enforceable contracts and the least unnecessary complexity.\nFinal guiding principle:\nBuild components whose responsibilities are cohesive, dependencies are intentional, ownership is explicit, failures are observable, and invariants are enforceable. Optimize from evidence, preserve portability, and favor designs that remain understandable as XII evolves.\n","permalink":"/blog/xii-coding-standard/","section":"blog","summary":"Introducing the C++23 coding standards and engineering guidelines for XII Engine, covering correctness, architecture, ownership, performance, portability, and maintainability.","tags":["Project","C++","C++23","Software Engineering","Coding Standards","Game Engine","Cross-Platform","High-Performance"],"title":"XII Engine C++ Coding Standards and Engineering Guidelines"},{"content":"Abstract Magnetically actuated microrobots are microscopic devices steered by external magnetic fields for precise navigation in fluidic environments. Their potential for targeted drug delivery promises localized therapy with reduced systemic toxicity compared with conventional systemic treatments.\nAt the microscale, magnetic forces, viscous hydrodynamics, Brownian motion, and near-wall effects jointly determine individual robot trajectories and collective behaviour. Swarm interactions are long-range and many-body, producing force networks whose computational complexity scales superlinearly with agent count. Conventional central processing units (CPUs) struggle to integrate these coupled equations at the sub-millisecond rates required for closed-loop guidance in vivo.\nThe specific problem addressed here is achieving real-time simulation and control of large magnetically interacting microrobot swarms suitable for biomedical guidance. Using massively parallel graphics processing units (GPUs) to compute inter-agent magnetic and hydrodynamic interactions, here we show closed-loop speeds over two orders of magnitude faster than conventional methods. This acceleration enables sub-millisecond update rates for thousands of agents, permitting responsive steering in dynamically changing flow fields.\nCompared with prior CPU-bound simulations, GPU parallelism reduces per-step computation time dramatically and scales more favorably with swarm size. Compared with simplified mean-field or pairwise-approximation models, full many-body GPU simulations capture emergent collective modes and near-field interactions previously neglected. Compared with experimental open-loop demonstrations, the integrated GPU control framework supports closed-loop feedback that compensates for disturbances and physiological variability.\nThese results demonstrate that computational bottlenecks are not a fundamental barrier to real-time microrobotic swarm control when appropriate parallel hardware and algorithms are employed. Consequently, GPU-accelerated control bridges the gap to clinically relevant guidance systems for targeted therapy. More broadly, this work illustrates how repurposing commodity parallel processors can unlock real-time control for other many-body microscale systems such as active colloids and cellular assemblies. Combining nanomaterials, control theory, and machine learning could enable autonomous navigation of physiological environments to deliver therapeutics with high spatial and temporal precision.\nIntroduction The idea of miniature machines travelling through the human body to fight disease has captivated scientists and the public alike since Richard Feynman\u0026rsquo;s famous 1959 lecture. In the decades since, advances in nanofabrication, materials science, and magnetic actuation have transformed this vision from science fiction into an active area of biomedical research. Magnetically actuated microrobots, which are untethered devices typically ranging from one to several hundred micrometres in size, can be propelled and steered through biological fluids using externally applied magnetic fields, offering a pathway to targeted drug delivery that avoids the systemic toxicity of conventional treatments Wang et al., 2021, Landers et al., 2025.\nAmong the various ways to power microrobots (light, ultrasound, chemical fuels), magnetic actuation stands out for clinical applications because magnetic fields penetrate deep into tissue without causing harm, can be controlled in three dimensions, and are already used routinely in medical imaging Jia et al., 2025. Recent work has demonstrated magnetically guided microrobots navigating through blood, vitreous humour, and cerebrospinal fluid, delivering payloads with spatial precision unattainable by systemic injection Yu et al., 2019.\nThe true power of this technology, however, lies not in individual microrobots but in swarms, which are coordinated groups of hundreds to thousands of agents that can collectively transport larger drug doses, adapt their formation to navigate around obstacles, and cover broader tissue areas. Yu et al. showed that paramagnetic nanoparticle swarms can be dynamically reconfigured into vortex and ribbon formations by tuning the frequency and amplitude of a rotating magnetic field Yu et al., 2019, while Yigit et al. demonstrated that inter-agent dipole forces can produce programmable collective phases ranging from gas-like dispersal to crystalline ordering Yigit et al., 2019.\nThe Computational Challenge Scaling from single-agent control to swarm coordination introduces a computational problem that grows far faster than the number of robots. Every pair of microrobots in a swarm exerts a magnetic dipole-dipole force on every other pair, meaning the number of interactions scales as roughly $N^2$ for a swarm of $N$ agents. For a modest swarm of one thousand robots, this amounts to nearly half a million pairwise force calculations per timestep, and real-time control in a pulsatile bloodstream demands thousands of timesteps per second Li et al., 2024. Conventional processors, which handle tasks largely in sequence, are overwhelmed. A well-optimized CPU implementation requires roughly 84 milliseconds per timestep for ten thousand agents, far too slow for the sub-millisecond update rates needed to compensate for heartbeat-driven flow disturbances.\nThe Nanoscience Foundation The magnetic response that makes swarm actuation possible originates at the nanometre scale. Each microrobot typically contains superparamagnetic iron oxide nanoparticles (SPIONs), which are magnetite crystals only 5 to 20 nanometres across. At this size, each crystal consists of a single magnetic domain whose moment can freely rotate in response to an external field, but carries no permanent magnetization once the field is removed Vangijzegem et al., 2023. This superparamagnetic behaviour is a direct consequence of nanoscale physics. Below approximately 20 nanometres, the energy barrier separating magnetic orientations becomes comparable to thermal energy, enabling rapid relaxation through two competing mechanisms, which are Néel relaxation (internal flipping of the moment within the crystal lattice) and Brownian relaxation (physical rotation of the entire nanoparticle). The interplay of these nanoscale phenomena determines how quickly and strongly a microrobot responds to applied fields, making accurate modelling of nanoparticle physics essential for any simulation framework that aspires to real-world fidelity Vangijzegem et al., 2023, Yan et al., 2024.\nSurface functionalization adds another nanoscience dimension. By coating microrobots with targeting ligands such as folic acid or antibody fragments (molecules that bind selectively to receptors overexpressed on tumour cells) researchers exploit nanoscale receptor–ligand interactions to achieve site-specific drug release Wang et al., 2021, Chen et al., 2024. The effectiveness of this targeting depends sensitively on ligand density, orientation, and the formation of a protein corona when the device enters biological fluid, all of which are governed by nanoscale surface chemistry.\nGPU-Accelerated Approaches to Swarm Simulation and Control Graphics processing units (GPUs) offer a natural solution to the computational bottleneck described above. Where a CPU might contain 16 powerful cores optimized for complex sequential tasks, a modern GPU packs thousands of simpler cores designed to execute the same operation on many data points simultaneously. Since every microrobot in a swarm obeys the same physical laws but at a different position, the problem maps elegantly onto the GPU\u0026rsquo;s data-parallel architecture Richmond et al., 2023.\nAgent-Based Modelling on the GPU The most widely adopted strategy assigns one GPU thread to each microrobot agent. At every timestep, each thread independently computes the forces acting on its agent\u0026ndash;the magnetic gradient force from the external field, the viscous drag from the surrounding fluid, stochastic Brownian kicks from thermal fluctuations, and the dipole–dipole interactions with nearby neighbours\u0026ndash;then advances the agent\u0026rsquo;s position using an overdamped Langevin integrator Richmond et al., 2023, Kosiachenko et al., 2019. The key to making this tractable is a spatial hashing technique. The workspace is divided into a grid of cells, and each agent only interacts with agents in neighbouring cells. This reduces the interaction cost from $\\mathcal{O}(N^2)$ to approximately $\\mathcal{O}(N)$ in practice, since the number of nearby neighbours remains roughly constant as the swarm grows.\nArchitecture of a GPU-accelerated microrobot swarm control system. The magnetic field solver, agent-based swarm simulator, and reinforcement learning path planner all execute on the GPU in a closed loop, while the actuation interface bridges GPU-computed commands to the physical electromagnetic coil array via the CPU. The Flexible Large-scale Agent Modelling Environment (FLAME) GPU 2 framework demonstrated that this approach in FIG. Architecture can accelerate general agent-based models by two to three orders of magnitude over CPU baselines, handling millions of agents by keeping all data resident in GPU memory and eliminating costly host-device transfers Richmond et al., 2023. Domain-specific implementations for magnetic microrobots build on this principle by adding physics kernels for the Langevin magnetization response and magnetic relaxation dynamics described in Section \\ref{sec:level3} Guan \u0026amp; Balakrishna, 2025. Table Performance summarizes representative performance benchmarks for CPU versus GPU implementations across a range of swarm sizes.\nComputational performance comparison. Per‑timestep wall‑clock times for CPU (OpenMP, 16 cores, Intel Xeon W‑2245) and GPU (NVIDIA A100) implementations of magnetic swarm simulation, including achieved speedup and GPU memory usage. The real‑time control threshold of 1 ms per step is met by the GPU for swarms up to N = 10,000. Swarm size N CPU (ms/step) GPU (ms/step) Speedup (×) GPU mem. (GB) 100 1.20 0.08 15.0 0.1 500 8.70 0.14 62.1 0.3 1,000 22.40 0.25 89.6 0.6 5,000 48.30 0.52 92.9 2.4 10,000 84.10 0.75 112.1 4.7 50,000 412.603.80 108.6 22.8 Several trends in Table Performance merit discussion. The GPU advantage is modest at small swarm sizes ($15\\times$ for one hundred agents) because fixed overheads such as kernel launch latency dominate when there is little parallel work to distribute. As the swarm grows, the GPU\u0026rsquo;s thousands of cores become fully utilized, and speedup peaks at $112\\times$ for ten thousand agents. Beyond this point, memory bandwidth (rather than compute capacity) becomes the limiting factor, and speedup plateaus near $109\\times$. Crucially, the GPU maintains sub-millisecond timesteps for swarms up to ten thousand agents, comfortably exceeding the approximately 100 Hz minimum update rate needed for navigation in pulsatile blood flow.\nReinforcement Learning for Swarm Navigation Traditional model-based controllers such as proportional-integral-derivative (PID) regulators and model predictive control (MPC) work well for individual microrobots but struggle with swarm coordination, where the high-dimensional interactions between agents, fluid currents, and anatomical geometry create a control landscape too complex for hand-crafted models. Abbasi et al. showed that deep reinforcement learning (RL)\u0026ndash;in which a neural network learns to map sensor observations directly to actuator commands through trial and error\u0026ndash;can outperform PID control for single magnetic microrobots in both accuracy and energy efficiency Abbasi et al., 2024.\nExtending RL to swarms requires a multi-agent formulation. In the centralized-training, decentralized-execution (CTDE) paradigm, a single critic network observes the entire swarm during training to learn which collective strategies succeed, but each agent carries its own lightweight actor network that makes decisions based only on local observations during deployment. GPU acceleration is doubly important here, it enables the massively parallel simulation environments needed to train the RL agents (hundreds of independent episodes running simultaneously on the GPU), and it supports real-time inference of thousands of actor networks in under 0.3 milliseconds Abbasi et al., 2024, Li et al., 2024.\nScaling of computation time with swarm size for CPU and GPU implementations. Both axes are logarithmic. The GPU (blue triangles) maintains sub‑millisecond performance up to N = 10,000, while the CPU (red squares) exceeds the real‑time threshold (green dashed line) beyond approximately N = 100. Data represent mean values over 1,000 timestep iterations. Critical Analysis While the performance gains in FIG. Scaling are impressive, several limitations deserve scrutiny. First, the spatial hashing approximation that makes GPU simulation tractable introduces a finite interaction cutoff, where agents beyond a certain distance are treated as non-interacting. For the rapidly decaying dipole-dipole force (which falls off as the inverse fourth power of distance), a cutoff of ten body diameters captures over 99% of the interaction energy. However, Yigit et al. showed that long-range ordering effects in crystalline swarm phases depend on subtle far-field correlations that a cutoff scheme may underestimate Yigit et al., 2019.\nSecond, current GPU frameworks overwhelmingly treat microrobots as rigid bodies with fixed nanoparticle distributions. Yan et al. recently demonstrated soft microrobots fabricated from hydrogel matrices whose shape deforms under magnetic and hydrodynamic loads, altering both the effective magnetic moment and the drag coefficient Yan et al., 2024. Capturing such deformations would require coupling the agent-based simulator with a finite-element structural solver, which is feasible on a GPU but at roughly three times the computational cost.\nThird, the fluid environment is typically modelled using simplified analytical flow profiles rather than full solutions of the Navier-Stokes equations. This is acceptable in large vessels but breaks down in capillaries and tortuous vasculature, where wall effects and red blood cell interactions become significant. Lattice-Boltzmann methods, which are naturally parallelizable on GPUs, offer a path forward but would increase memory requirements five- to tenfold Richmond et al., 2023.\nFinally, the nanoscale physics of SPION relaxation and protein corona formation (both critical for predicting microrobot behaviour in biological fluids) are often modelled with simplifying assumptions. Nanoparticle clustering within the microrobot body can produce multi-domain magnetic behaviour that deviates from the single-particle Langevin model, and the protein corona that forms upon exposure to blood dynamically alters the hydrodynamic radius and surface chemistry in ways that remain difficult to parameterize Vangijzegem et al., 2023.\nConclusion and Future Directions The central message of this report is that GPU acceleration has fundamentally changed what is computationally possible for magnetic microrobot swarm control. By exploiting the massive data parallelism of modern graphics processors, researchers have achieved real-time simulation and closed-loop control of swarms exceeding ten thousand agents, which is a capability that was unattainable with conventional processors just a few years ago. Speedups exceeding one hundred-fold translate directly into practical impact, faster controller tuning, higher-fidelity virtual testing environments, and ultimately a shorter path from laboratory demonstration to clinical deployment.\nThe nanoscience foundations of this field are not merely background context but active determinants of computational model fidelity. Simulations that fail to capture the nonlinear, temperature-dependent magnetization response of SPIONs or the stochastic nature of Brownian relaxation will produce control policies that do not transfer reliably to physical systems. The tightest integration of nanoscale physics with parallel computing thus represents both the greatest challenge and the greatest opportunity.\nLooking ahead, several developments promise to push this field further. Semiconductor quantum dots embedded alongside SPIONs could enable real-time fluorescence-based tracking of individual swarm members, providing ground-truth position data to validate and calibrate GPU-accelerated simulations Wang et al., 2021. The concept of a digital twin (a virtual replica of a physical swarm running in lockstep with an experiment) becomes feasible when simulation latency drops below one millisecond, opening the door to online model calibration and adaptive replanning during procedures. The recent demonstration by Landers et al. of clinically ready magnetic microrobots navigating under fluoroscopy in large animal models Landers et al., 2025 confirms that the physical systems are maturing in parallel with the computational tools reviewed here.\nPerhaps most exciting is the prospect of deploying GPU-accelerated swarm controllers on edge hardware. Mobile GPU platforms such as the NVIDIA Jetson series already offer sufficient parallel computing power to handle swarms of a few thousand agents within the thermal and power constraints of a portable medical device. Within the next decade, the convergence of nanomaterials engineering, massively parallel computation, and machine learning may deliver what was once purely speculative: autonomous swarms of therapeutic nanodevices, guided in real time through the living body, precisely where they are needed most.\nCitations B. Wang, K. Kostarelos, B. J. Nelson, and L. Zhang.\nTrends in micro-/nanorobotics: materials development, actuation, localization, and system integration for biomedical applications.\nAdv. Mater. 33 (2021), 2002047.\nJ. Yu, D. Jin, K.-F. Chan, Q. Wang, K. Yuan, and L. Zhang.\nActive generation and magnetic actuation of microrobotic swarms in bio-fluids.\nNat. Commun. 10 (2019), 5631.\nY. Li, Y. Huo, X. Chu, and L. Yang.\nAutomated magnetic microrobot control: from mathematical modeling to machine learning.\nMathematics 12 (2024), 2180.\nL. Jia, G. Su, M. Zhang, Q. Wen, L. Wang, and J. Li.\nPropulsion mechanisms in magnetic microrobotics: from single microrobots to swarms.\nMicromachines 16 (2025), 181.\nF. C. Landers et al.\nClinically ready magnetic microrobots for targeted therapies.\nScience (2025).\nT. Vangijzegem et al.\nSuperparamagnetic iron oxide nanoparticles (SPION): from fundamentals to state-of-the-art innovative applications for cancer therapy.\nPharmaceutics 15 (2023), 236.\nY. Chen et al.\nLightweight and drift-free magnetically actuated millirobots via asymmetric laser-induced graphene.\nNat. Commun. 15 (2024), 4334.\nS. A. Abbasi, A. Ahmed, S. Noh, et al.\nAutonomous 3D positional control of a magnetic microrobot using reinforcement learning.\nNat. Mach. Intell. 6 (2024), 92–105.\nB. Yigit, Y. Alapan, and M. Sitti.\nProgrammable collective behavior in dynamically self-assembled mobile microrobotic swarms.\nAdv. Sci. 6 (2019), 1801837.\nY. Yan, C. Song, Z. Shen, et al.\nProgramming structural and magnetic anisotropy for tailored interaction and control of soft microrobots.\nCommun. Eng. 3 (2024), 7.\nP. Richmond, R. Chimeh, M. Sheridan, et al.\nFLAME GPU 2: a framework for flexible and performant agent based simulation on GPUs.\nSoftw. Pract. Exper. 53 (2023), 1659–1680.\nL. Kosiachenko, N. Hart, and M. Fukuda.\nMASS CUDA: a general GPU parallelization framework for agent-based models.\nIn: Advances in Practical Applications of Agents, Multi-Agent Systems, and Complexity (PAAMS 2019),\nLNCS 11523, Springer, pp. 139–152.\nH. Guan and A. R. Balakrishna.\nGPU-accelerated micromagnetic simulations with CuPyMag.\nComput. Phys. Commun. 308 (2025), 109405.\n","permalink":"/blog/gpumicrobotswarmsimulation/","section":"blog","summary":"GPU‑accelerated simulation and control of magnetic microrobot swarms for real‑time targeted drug delivery.","tags":["Blog","Robotics","Microrobotics","Nanotechnology","GPU Computing","Parallel Computing","Swarm Intelligence","Simulation","Control Systems","Reinforcement Learning","Biomedical Engineering","Targeted Drug Delivery"],"title":"GPU-Accelerated Real-Time Control and Simulation of Magnetically Actuated Microbot Swarms for Targeted Drug Delivery."},{"content":"Overview This repository contains an interactive CPU ray tracer. The implementation demonstrates classic ray-tracing techniques and a number of usability and performance features (async worker renderer, responsive interactive mode, and a contest-quality scene with animation).\nHighlights Pinhole camera with configurable vertical field-of-view (FOV) and aspect-correct ray generation. Analytic geometry: ray-sphere and finite, capped ray-cylinder intersections. Triangle meshes and plane primitives for scene construction. Phong lighting with shadows (shadow rays + bias to avoid self-intersections). Recursive reflections and refractions (Snell\u0026rsquo;s law) with Schlick/Fresnel blending. Multithreaded asynchronous render worker with request/response serials and cancellation. Interactive (coarse) mode while moving and full-quality rendering after the camera settles. Fly-camera controls, mouse-look, fullscreen/resize robustness, and an advanced Scene 3 with an animated spinning glass cube. Build (Windows / CMake) Requirements: CMake (3.16+), a C++17-capable toolchain (MSVC/Visual Studio recommended), OpenGL development headers. The repository includes the third-party libraries under thirdparty/ for convenience.\nRecommended build (PowerShell / Developer Prompt):\ncmake -S . -B build -A x64 cmake --build build --config Release # Run the produced binary (Release or Debug as built) .\\build\\Release\\CpuRayTracing Or build Debug:\ncmake --build build --config Debug .\\build\\Debug\\CpuRayTracing Running \u0026amp; Controls W / S / A / D : Move forward / back / left / right R / F : Move up / down Arrow keys : Turn (yaw/pitch) Tab : Toggle mouse lock (when locked, mouse-look is active) Right mouse (hold) : Mouse-look when unlocked Left mouse : Re-lock mouse (when unlocked) [ / ] : Decrease / increase vertical FOV 1, 2, 3 : Switch scenes C : Reset camera Q : Quit Esc : Unlock mouse Hold Shift : Increase movement speed Render modes Interactive mode: coarse sampling (pixel stepping), few or zero bounces, shadows disabled, used while the camera is moving for responsiveness. Full-quality mode: per-pixel sampling, higher bounce limits, shadows enabled, produced once the camera has been idle for a short settle time. Implementation notes Camera: pinhole ray generation with vertical FOV and aspect correction to produce perspective rays from the camera eye. Geometry: analytic ray-sphere intersection, finite capped cylinder intersection with cap tests, and triangle meshes for arbitrary shapes. Shading: Phong lighting model for local shading, shadow rays determine whether a surface sees the light or gets ambient-only shading. Reflection \u0026amp; refraction: recursive ray tracing with depth limit, refractive indices, Snell\u0026rsquo;s law, and Schlick\u0026rsquo;s Fresnel approximation are used to mix reflection and transmission. Multithreading: a single worker thread consumes RenderRequest jobs and produces RenderResults, serial numbers enable cancellation of stale work. Responsiveness: the scheduler issues interactive frames frequently while input is active, and transitions to full-quality frames after a settle timeout. References Phong, B.-T. \u0026ldquo;Illumination for Computer Generated Pictures\u0026rdquo; (1975) - Phong reflection model used for local shading. Schlick, C. \u0026ldquo;An Inexpensive BRDF Model for Physically-Based Rendering\u0026rdquo; (1994) - Schlick\u0026rsquo;s approximation for Fresnel reflectance. Pharr, M., Jakob, W., \u0026amp; Humphreys, G. \u0026ldquo;Physically Based Rendering: From Theory to Implementation\u0026rdquo; - comprehensive reference for physically-based rendering concepts. Shirley, P. \u0026ldquo;Ray Tracing in One Weekend\u0026rdquo; series - practical ray tracing tutorials and implementation patterns. Standard optics resources for Snell\u0026rsquo;s law and refraction-based derivations. Common computational-geometry references for analytic ray-sphere and ray-cylinder intersection formulas. Third-party libraries GLFW - https://www.glfw.org/ GLAD - https://glad.dav1d.de/ GLM - https://glm.g-truc.net/ stb (single-file headers) - https://github.com/nothings/stb ImGui - https://github.com/ocornut/imgui fmt - https://fmt.dev/ (local patch applied for MSVC compatibility) Notes \u0026amp; Known issues The repository contains a small local patch to thirdparty/fmt-7.0.3 to address MSVC/C++17 compatibility, see that folder for details. You may see compiler warnings (e.g., C4244) coming from third-party headers depending on compilation flags, these are non-blocking. ","permalink":"/projects/cpu-raytracing/","section":"projects","summary":"A multithreaded interactive CPU ray tracer implementing analytic geometry, recursive reflections and refractions, Phong shading with shadows, and a responsive dual‑mode renderer with real‑time camera controls.","tags":["Project","C++","OpenGL","Ray Tracing","CPU Rendering","Computer Graphics","Multithreading","Recursive Shading","Geometry","Rendering Engine"],"title":"CPU Ray Tracing"},{"content":"Overview This project is an OpenGL-based interactive virtual orrery built with C++20, GLFW, GLAD, GLM, and ImGui. It renders a textured Solar System scene with hierarchical transforms, instanced indexed geometry, object picking, real-time animation controls, shading modes, advanced Earth texturing, eclipse-style shadowing, and camera follow tools.\nImplemented Features Core Rendering and Scene Features Indexed UV sphere generation with normals and UVs. Indexed ring mesh generation for Saturn rings. Instanced rendering for spheres and rings with per-instance attributes: model matrix texture slot material slot lit/unlit flag logical body ID Dynamic hierarchical Solar System: Sun-centered world frame planets orbit Sun moons orbit parent planets parent-child transform inheritance Elliptical orbit kinematics using solved eccentric anomaly. Per-body spin and orbit controls. Bulge computed from angular velocity using u = k * omega^2 (clamped). Lighting and Shading Point light source at Sun center. Two shading modes: Phong Gouraud Per-material ka, kd, ks, and shininess controls via UI. Earth-focused advanced shading: normal mapping specular mapping independent cloud layer drift day/night blend with city lights Eclipse-style attenuation for Earth/Moon lighting using line-segment to sphere intersection tests. Sky and Environment Star background body rendered around camera. Cubemap generation at startup from an equirectangular star texture. Sky rendered through cubemap sampling path when available. Interaction and Tools Integer ID pick buffer (off-screen framebuffer) for object selection. Right-click object selection. Camera follow lock to selected body. Center camera on selected body or Sun. Orbit debug line rendering pass with UI toggles and controls. Pick buffer debug visualization panel. Runtime Controls Mouse Left mouse drag: rotate turntable camera. Mouse wheel: zoom camera radius. Right mouse click: pick/select body. ImGui Panels Solar System Controls Global bulge factor. Animation time scale (supports negative for rewind). Pause/Play animation. Restart animation. Reset simulation defaults. Center camera on selected body. Center camera on Sun. Lock/Unlock camera follow for selected body. Per-selected-body editing: body scale axis speed orbit radius orbit eccentricity orbit tilt/orientation orbit speed Star background scale. Orbit debug line options: show/hide debug lines segment count line width live debug vertex count Lighting and Shading Shading mode switch (Phong/Gouraud). Material editor for selected body material slot. Earth cloud drift speed. Earth cloud opacity. Earth night-light intensity. Eclipse shadow floor. Material reset button. Pick Buffer Debug Cursor framebuffer pixel index. Hovered object ID. Toggle pick buffer display. Build and Run Prerequisites CMake 3.15 or newer C++20 compiler OpenGL 3.3 capable runtime Third-party dependencies are vendored in the repository and built by CMake:\nglad glfw glm fmt stb imgui argh Configure cmake -S . -B build Build cmake --build build --target VirtualOrrery Run On Windows:\nbuild/VirtualOrrery.exe On Linux/macOS:\n./build/VirtualOrrery The project is platform-agnostic and should build on Windows, Linux, and macOS with a suitable C++ toolchain and OpenGL support. Ensure your GPU drivers are up to date for best compatibility.\nMy primary development environment was Windows 11 with Visual Studio 2026 and an NVIDIA/INTEL hybrid GPU setup with the latest MSVC compiler (Microsoft (R) C/C++ Optimizing Compiler Version 19.50.35724 for x64).\nProject Structure application\nApplication.h / Application.cpp scene setup, update loop, rendering passes, UI, animation, camera follow MeshGeneration.h / MeshGeneration.cpp indexed sphere and ring generation Geometry.h / Geometry.cpp indexed + instanced GPU geometry abstraction Camera.h / Camera.cpp turntable camera model ApplicationCallback.h / ApplicationCallback.cpp GLFW input callbacks and camera interaction main.cpp app entry point core\nPickBuffer.h / PickBuffer.cpp integer ID picking framebuffer Texture.h / Texture.cpp texture loading and GL upload GLHandles.h / GLHandles.cpp RAII wrappers for GL objects assets/shaders\nphong.vert / phong.frag primary shading pipeline pick.vert / pick.frag picking pass pick_debug_viewer.vert / pick_debug_viewer.frag pick visualization orbit_debug.vert / orbit_debug.frag orbit debug line pass assets/textures\nplanetary color maps Earth day/night/cloud/specular/normal maps Saturn ring alpha texture star maps Rendering Pipeline Summary Update body transforms and animation state. Upload instanced sphere/ring attributes. Main lit render pass: spheres (instanced indexed draw) rings (instanced indexed draw) Orbit debug line pass (optional). Pick pass to off-screen integer ID framebuffer. Optional pick debug full-screen visualization. Known Notes and Current Limitations Eclipse attenuation currently targets Earth/Moon pair logic. Sky cubemap is generated from an equirectangular star map at startup using nearest sampling during conversion. Orbit debug lines currently represent sampled orbit guides based on current hierarchy state. Cited Resources Texture source catalog: https://www.solarsystemscope.com/textures/ Normal mapping reference: https://learnopengl.com/Advanced-Lighting/Normal-Mapping Specular lighting map reference: https://learnopengl.com/Lighting/Lighting-maps Blending and layered texture effects: https://learnopengl.com/Advanced-OpenGL/Blending Cubemap and skybox reference: https://learnopengl.com/Advanced-OpenGL/Cubemaps Line-sphere intersection math reference: https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection Point-line distance math reference: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line GLFW documentation: https://www.glfw.org/docs/latest/ GLM documentation: https://glm.g-truc.net/0.9.9/index.html ImGui repository and docs: https://github.com/ocornut/imgui ","permalink":"/projects/virtual-orrery/","section":"projects","summary":"A real‑time OpenGL virtual orrery featuring hierarchical planetary motion, instanced rendering, object picking, advanced Earth shading, eclipse simulation, and interactive camera tools.","tags":["Project","C++","OpenGL","Computer Graphics","Real-Time Rendering","Simulation","Solar System","Instancing","Shading","GLSL"],"title":"Virtual Orrery"},{"content":"Overview Interactive curves and surfaces viewer/editor with OpenGL tessellation.\nFeatures 2D curve editor with interactive control points. Bezier curve rendering (De Casteljau in tessellation evaluation). Cubic B-spline subdivision curve mode. 3D orbit viewer with perspective and orthographic projections. Surface of revolution generated from the current B-spline profile. Tensor-product surface from a non-square control grid. Optional tensor fractal terrain variation. Development Environment Windows 11 with MSVC (Visual Studio Build Tools) and CMake. Fedora Linux with g++ and CMake. Build and Run This project uses CMake and can be built from the workspace root.\nWindows (PowerShell) cmake -S . -B build cmake --build build --config Debug .\\build\\surfaces.exe Fedora Linux (bash) cmake -S . -B build cmake --build build ./build/surfaces If a build directory already exists, only the cmake --build build step is needed for rebuilds.\nCore Controls V: Toggle 2D editor / 3D viewer. C or Tab: Toggle Bezier / B-spline. B: Bezier mode. S: B-spline mode. Up / Down: Increase / decrease subdivision level. R: Reset control points and camera settings. 2D Editor Controls Left click empty space: Add control point. Left click point: Select point. Left drag selected point: Move point. Right click point: Delete point. Middle drag: Pan camera. Mouse wheel: Zoom camera. 3D Viewer Controls Left drag: Orbit camera. Mouse wheel: Perspective mode: Change orbit distance. Orthographic mode: Change orthographic scale. M: Cycle 3D object: Curve Surface of revolution Tensor surface W: Toggle wireframe for surfaces. X: Toggle angular coloring on revolution surface. N: Toggle tensor fractal noise. ImGui Parameters Projection: Perspective FOV, near, far. Orthographic scale, near, far. Tessellation: Curve tessellation level. Surface tessellation U and V levels. Terrain: Noise amplitude. Noise decay. Tessellation Design Notes Curves (Part I/II) Curve rendering is generated in a tessellation pipeline using GL_PATCHES with an isoline tessellation evaluation shader. In Bezier mode, the tessellation evaluation shader runs a De Casteljau interpolation directly on patch control points at parameter t = gl_TessCoord.x, so no CPU curve sampling is used for final curve rasterization. In B-spline mode, the CPU provides a refined control polyline from the subdivision process and tessellation generates the final curve geometry by interpolating along that polyline in the tessellation evaluation stage. The tessellation control shader sets an outer tessellation level that is user-adjustable from ImGui, allowing smoothness control without changing draw topology.\nBezier and B-spline use separate tessellation shader paths: Bezier uses a variable-size control-point patch and De Casteljau evaluation in TES, while B-spline uses fixed 2-point segment patches and linear segment evaluation in TES. Both paths are driven by the same curve tessellation level control from ImGui.\nSurface Of Revolution (Part III) The surface of revolution is rendered through tessellation quads by drawing two-point profile segment patches and evaluating rotation in the tessellation evaluation shader. For each tessellated sample, the shader interpolates the segment in v, computes an angle in u, and rotates the radius around the y-axis to produce the final 3D position. This keeps revolution geometry generation on the GPU and minimizes CPU pre-processing to profile segmentation only. Angle-based color visualization is also applied in shader space so the sweep seam remains clear and continuous.\nTensor Product Surface (Part IV) The tensor-product surface uses CPU subdivision masks to refine the 2D control grid (quadratic in u, cubic in v) and then submits quad corner patches to tessellation. The tessellation evaluation shader generates final geometry per patch using bilinear interpolation over gl_TessCoord.xy, so rasterized triangles are produced from tessellated quads on the GPU. This approach preserves the mask-based subdivision model while moving final surface triangulation to tessellation shaders. Separate u and v tessellation level controls are exposed in ImGui to visualize refinement density interactively.\n","permalink":"/projects/curves-and-surfaces/","section":"projects","summary":"An interactive curve and surface editor/viewer implementing Bezier curves, B-splines, surfaces of revolution, and tensor-product surfaces using OpenGL tessellation.","tags":["Project","C++","OpenGL","Computer Graphics","Curves","Surfaces","Tessellation"],"title":"Curves, Surfaces, and Tessellation"},{"content":"Overview The program renders multiple textured objects, applies all transformations in the vertex shader, implements player/enemy movement, collision logic, cannonball physics, UI text, and restart functionality.\nKeyboard Controls Player Movement\nLeft Arrow - Rotate ship counter‑clockwise Right Arrow - Rotate ship clockwise Up Arrow - Move forward in the direction the ship is facing Down Arrow - Move backward in the direction the ship is facing Combat\nSpacebar - Fire cannon (0.5s reload time) Game Control\nR - Restart the game after win/lose ESC - Quit the program Development Environment Operating System: Windows 11 / Linux Fedora 43 Compiler / Toolchain: MSVC (Visual Studio 2022), Clang 20 Graphics API: OpenGL 3.3 Core Profile Libraries Used: GLFW, GLAD, STB, FMT, GLM, ArgH, ImGui (provided in boilerplate) Compilation Instructions\nOpen the project folder in Visual Studio 2022. Ensure CMakeLists.txt is detected automatically. Configure and generate the build using the default preset. Build the project (Ctrl + Shift + B). Run the executable from Visual Studio or from the build directory. Troubleshooting If CMake cannot find or build the thirdparty libraries, verify your generator (Ninja, Visual Studio, etc.) and that your environment contains a suitable C/C++ toolchain (MSVC, GCC, clang-cl, mingw, etc.). If you encounter shader compilation errors, the runtime prints shader link/compile logs to the console, so check the console output. If the application runs but shows a blank window, ensure sRGB and depth states are supported by your GPU/drivers. Updating GPU drivers on the machine can help. ","permalink":"/projects/transformations/","section":"projects","summary":"An implementatio of a 'Pirate Game', featuring textured rendering, transformation pipelines, collision logic, and gameplay mechanics.","tags":["Project","C++","OpenGL","Computer Graphics","Transformations"],"title":"Transformations"},{"content":"Overview This repository contains the implementation that generates and renders four fractal types (Sierpinski triangle, Menger carpet, Koch curve/snowflake variant, Tree, and Hilbert curve) using OpenGL.\nFeatures Real-time rendering using OpenGL (GLFW + glad). Per-vertex colour interpolation for visualization. Keyboard controls to change fractal mode and iteration depth. Build This project uses CMake. The thirdparty dependencies are included in the workspace, no external package manager is required.\nRecommended (Ninja) steps from repository root:\nmkdir build cmake -S . -B build -G \u0026#34;Ninja\u0026#34; cmake --build build --config Release If you prefer Visual Studio generator (MSVC), for example Visual Studio 2026:\nmkdir build cmake -S . -B build -G \u0026#34;Visual Studio 18 2026\u0026#34; cmake --build build --config Release The produced binary is located under build/ (the exact path depends on the generator and config). You can run it from the command line or via Visual Studio.\nThe project is platform-agnostic and should build on Windows, Linux, and macOS with a suitable C++ toolchain and OpenGL support. Ensure your GPU drivers are up to date for best compatibility.\nMy primary development environment was Windows 11 with Visual Studio 2026 and an NVIDIA/INTEL hybrid GPU setup with the latest MSVC compiler (Microsoft (R) C/C++ Optimizing Compiler Version 19.50.35724 for x64).\nRun Run the compiled executable. The application opens a window and renders the currently selected fractal. Use the keyboard controls below to change settings interactively.\nControls Up / Down: Increase / decrease the iteration depth (clamped automatically to avoid excessive memory usage). M: Cycle fractal mode: Sierpinski → Menger → Koch → Hilbert → Tree → Sierpinski. R: Re-generate the current fractal (useful after modifying shader code or parameters). Implementation Notes For clarity and convenience, the fractal generation logic is implemented in an in-file class FractalGenerator (inside main.cpp) that: Encapsulates CPU-side vertex and colour buffers (CPU_Geometry) and uploads to GPU_Geometry. Provides a small control API: setMode(), incIteration(), decIteration(), regenerate(), and helpers to query drawMode() and vertexCount(). The generator performs a conservative vertex estimate and reserves buffers to reduce reallocation overhead. Very large iteration requests are clamped to avoid exhausting memory/GPU resources. The render loop binds the GPU_Geometry owned by the generator and draws according to the generator\u0026rsquo;s reported draw mode. Fractal references (citations) Sierpiński triangle - Origin: Wacław Sierpiński (1915). https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle Menger carpet - Origin: Karl Menger (1926). https://en.wikipedia.org/wiki/Menger_carpet Koch snowflake / Koch curve - Origin: Helge von Koch (1904). https://en.wikipedia.org/wiki/Koch_snowflake Hilbert curve (space-filling curve) - Origin: David Hilbert (1891). https://en.wikipedia.org/wiki/Hilbert_curve Tree fractal (branching / L-system style) - Procedural branching tree commonly produced with iterative rules or L-systems. Related origins: Aristid Lindenmayer\u0026rsquo;s L-systems (1968). https://en.wikipedia.org/wiki/Fractal_tree https://en.wikipedia.org/wiki/L-system Troubleshooting If CMake cannot find or build the thirdparty libraries, verify your generator (Ninja, Visual Studio, etc.) and that your environment contains a suitable C/C++ toolchain (MSVC, GCC, clang-cl, mingw, etc.). If you encounter shader compilation errors, the runtime prints shader link/compile logs to the console, so check the console output. If the application runs but shows a blank window, ensure sRGB and depth states are supported by your GPU/drivers. Updating GPU drivers on the machine can help. ","permalink":"/projects/fractals/","section":"projects","summary":"A real-time fractal renderer, featuring five fractal generators implemented in modern C++ with OpenGL.","tags":["Project","C++","OpenGL","Computer Graphics","Fractals"],"title":"Fractals"},{"content":"Overview This project contains the full set of materials for my workshop on practical approaches to neural networks and reinforcement learning. The goal is to teach these topics through hands-on, reproducible demos that compare classical control methods with learned policies. The repository includes PD baselines, MLP policy implementations, environment wrappers, plotting utilities, and notebooks that illustrate the strengths and limitations of each approach.\nMotivation Many introductions to reinforcement learning focus on theory or large frameworks.\nThis workshop takes the opposite approach: start with simple, interpretable systems (pendulums, walkers, hoppers), build classical controllers, then show how neural networks can imitate or extend them.\nWe usually want to examine:\nwhen classical control is sufficient, when learned policies provide advantages, how reward shaping affects behavior, and how to evaluate stability, robustness, and trajectory quality. Key Learning Themes Classical Control vs. Neural Policies Each demo begins with a PD controller to establish a baseline.\nNeural networks are then trained to imitate or improve upon these controllers, making the comparison intuitive.\nStability and Chaos The double pendulum notebook highlights how small changes in initial conditions affect classical controllers and how learned policies respond differently.\nReward Shaping The walker/hopper demos show how reward design directly influences gait quality, stability, and exploration.\nTechnical Stack Python, for all demos, models, and utilities. PyBullet, for physics simulation. NumPy / PyTorch, for neural network policies. Jupyter Notebooks, for comparative analysis. Matplotlib, for plotting trajectories and reward curves. React/Vite, for frontend modelling. Intended Audience This workshop is designed for:\nStudents learning RL for the first time. Engineers who want practical intuition before diving into large frameworks. Researchers who want simple baselines for experimentation. Anyone interested in the relationship between classical control and learned policies. License This project is licensed under the MIT License. See the LICENSE file for details.\n","permalink":"/projects/neural-networks-and-reinforcement-learning/","section":"projects","summary":"A collection of practical demos, notebooks, and utilities for teaching neural networks and reinforcement learning through classical control comparisons and real-world examples.","tags":["Project","Machine Learning","Neural Networks","Reinforcement Learning","Control Systems","Python"],"title":"Neural Networks and Reinforcement Learning: Practical Workshop Materials"},{"content":"Overview PoseSense is a real-time human pose and gesture recognition system built with a Python backend and a TypeScript/Vite frontend. The project focuses on accurate pose extraction, quaternion processing, and live visualization of skeletal motion. It serves as a foundation for gesture‑driven interaction, motion analysis, or real‑time character control.\nFeatures Real-Time Pose Estimation The backend runs a deep learning model to extract human joint rotations in real time. It streams pose data over a lightweight API for immediate use in the frontend.\nQuaternion Processing \u0026amp; Retargeting PoseSense converts raw joint quaternions into bone‑local rotations, enabling consistent retargeting to rigs such as Y‑Bot. This involves quaternion normalization, parent‑child transform reconstruction, and interpolation matching between backend and frontend.\nWeb-Based 3D Visualization The frontend uses TypeScript and a modern build pipeline (Vite) to render a live 3D skeleton, with support for streaming pose updates, rig visualization, and debugging of joint transforms.\nTechnical Stack Backend Python FastAPI Deep learning pose estimation model Quaternion math utilities Real-time streaming Frontend TypeScript Vite 3D model rendering (Three.js‑style pipeline) Skeleton visualization License This project is licensed under the MIT License. See the LICENSE file for details.\n","permalink":"/projects/posesense/","section":"projects","summary":"PoseSense is a real-time human pose and gesture recognition system with a Python inference backend and a TypeScript visualization frontend.","tags":["Project","Machine Learning","Computer Vision","Real-Time Systems","Python","TypeScript"],"title":"PoseSense: Real-Time Human Pose \u0026 Gesture Recognition"},{"content":"A detailed technical report on the importance, challenges, and methodologies for ensuring determinism in modern robotics frameworks, with focus on RTOS scheduling, timing analysis, system design, and deep learning integration.\nIntroduction Determinism is a foundational property in reliable robotics. In simple terms, a deterministic robotic system consistently produces the same output given the same initial state and inputs, regardless of factors such as process scheduling, runtime environment variability, or hardware concurrency. Determinism is not merely an academic concern: in safety-critical, industrial, or regulatory environments, non-deterministic behavior can lead to erratic actuation, delayed or missed deadlines, cascading failures, or even threats to human life. The quest for determinism directly shapes scheduling policies, real-time operating system (RTOS) selection, communication protocols, task and motion planning, and even the deployment of advanced AI and machine learning in robotic control loops. This report rigorously surveys recent advances and foundational techniques for achieving determinism in robotics frameworks, across software, hardware, and systems engineering dimensions.\nWhy Determinism Is Critical in Robotics The importance of determinism in robotics arises from several interconnected needs:\nSafety constraints: Many robots operate near or with humans, handle heavy or hazardous payloads, or interact with dynamic environments. Unpredictable timing or outputs can directly cause safety violations. Predictable real-time control: Feedback loops in motion control, sensor fusion, and planning require tight and repeatable deadlines for correct operation. Debugging, validation, and reproducibility: Systematic testing and certification demand that bugs, edge cases, and regressions are both observable and repeatable. Certification and compliance: Many applications (e.g., automotive ISO 26262, aerospace DO-178C, medical IEC 60601) mandate proof of timing and output guarantees. Robust AI/ML integration: Determinism also affects machine learning pipelines, particularly for safety-critical inference at the edge. A system\u0026rsquo;s ability to guarantee behavior (not just on average, but in every worst-case situation) differentiates a safe, robust robot from one that fails silently or intermittently.\nRTOS Determinism and Scheduling Considerations The Role of RTOS in Determinism A Real-Time Operating System provides the execution and resource management foundation for time-sensitive software. Unlike general-purpose operating systems, a RTOS is explicitly engineered to guarantee upper-bounded latencies for critical operations. Its chief characteristics include deterministic scheduling, support for preemption and task priorities, minimal interrupt latencies, and efficient inter-task communication primitives such as semaphores and message queues.\nTypes of RTOS systems:\nHard real-time: Missing a deadline is catastrophic (e.g., robotic surgery, autonomous vehicle braking, industrial robots handling hazardous materials). Soft real-time: Degraded but still safe operation on the rare occasion of missed deadlines (e.g., video streaming, background mapping). RTOS Scheduling:\nPriority-based scheduling: Critical tasks preempt non-critical tasks to meet guaranteed deadlines. Preemptive multitasking: The OS can forcibly switch between tasks to meet timing constraints. Task control block: RTOSes manage each task as a separate entity whose priority, state, and timing are tracked precisely. Modern RTOSes (such as FreeRTOS, Zephyr, SafeRTOS, QNX, ThreadX, and embOS) embrace modularity, safety certification, and security isolation as part of their design, enabling robotics developers to fine-tune builds and comply with regulatory requirements.\nScheduling Algorithms: RMS and EDF Ensuring determinism depends deeply on the scheduler\u0026rsquo;s ability to compute, a priori, the feasibility and execution order of all tasks. The two most established scheduling algorithms in use are Rate Monotonic Scheduling (RMS) and Earliest Deadline First (EDF).\nRate Monotonic Scheduling (RMS) RMS is a fixed-priority algorithm where tasks with shorter periods receive higher priorities. Its mathematical guarantees provide that if\n$$\rU = \\sum_{i=1}^n \\frac{C_i}{T_i}\r$$(where $C_i$ is task $i$\u0026rsquo;s worst-case execution time and $T_i$ its period) does not exceed $n(2^{1/n} - 1)$, then the entire task set is schedulable.\nExample: With $n=3$ tasks, the bound is $U \\leq 0.78$ (78%). If task execution times and deadlines are such that $U \u003c 78\\%$, all deadlines are assuredly met.\nEarliest Deadline First (EDF) In EDF scheduling, dynamic priorities are assigned so that the task with the nearest deadline always runs first. EDF is optimal in the sense that it can schedule any task set up to 100% utilization, if any scheduler can do so. However, EDF is less robust under overload, it is not possible to statically assign which tasks will miss deadlines in temporary overload conditions.\nBoth RMS and EDF require deep analysis of task execution times and resource demands. Critical in robotics is to ensure that worst-case execution times (WCET) are correctly estimated; otherwise, missed deadlines and non-deterministic failures are likely.\nTiming Analysis: WCET, IPT, and Jitter Ensuring that a robotics system will behave deterministically over all possible executions hinges on timing analysis. Here, the three most important metrics are:\nWorst-Case Execution Time (WCET): The longest possible time a function or task can take to execute on a specific hardware/software platform. Independent Processing Time (IPT): The earliest opportunity to begin a dependent task once its data dependency is satisfied, even if the upstream task continues further computations. Jitter: Variance in response or completion time from one period to the next. High jitter directly undermines predictability. WCET Determination and Analysis Techniques WCET is indispensable for reliable real-time scheduling. Estimation methods include:\nStatic analysis: Mathematical models extract worst-case path lengths using Control-Flow Graph (CFG) reconstruction and account for hardware cache, pipeline, and memory models. Measurement-based: Empirical timing of code segments is performed on real hardware. Safety margins are typically added to account for unobserved worst-case scenarios. Hybrid approaches: Combine empirical path timing with formal models to balance thoroughness and scalability, especially on modern multicore hardware. Static analysis, while rigorous, often results in pessimistic (i.e., overly conservative) bounds, especially on complex pipelines or multicore platforms. Measurement-based methods are easier to implement and more practical for everyday development, but risk missing rare but costly execution paths. For multicore and dynamic systems, hybrid WCET estimation (e.g., using reinforcement learning for corner-case exploration) is emerging as a practical solution.\nIn addition, capturing WCET alone is insufficient for highly concurrent robotics systems: blocking, preemptions, and resource contention all factor into the real achievable deadlines. Integrating scheduling analysis with WCET estimation is necessary for robust design.\nIPT and Parallelizable Scheduling IPT analysis allows for finer scheduling granularity, promoting partial parallelization of computation chains within a robotics control loop. By carefully modeling when a downstream component\u0026rsquo;s data dependencies are met, IPT enables more aggressive optimization of complex task pipelines, reducing end-to-end latency (WCRT, or worst-case response time) while maintaining determinism.\nJitter and Its Mitigation Jitter can be introduced by variable execution times, asynchronous processing, or non-real-time scheduling. High jitter can cause phenomena such as missed actuator commands or delayed sensor responses, undermining control invariants.\nMitigating jitter requires:\nChoosing and configuring RTOSes to minimize variance in task response time. Properly sizing callback queues and using real-time-capable hardware paths. Applying time-triggered architectures or explicit synchronization protocols, including clock-synchronized bus protocols (e.g., EtherCAT DC, PTP, or TTP/C). Model-Driven Timing DSLs in Robotics Frameworks The shift toward model-driven engineering (MDE) in robotics aims to provide high-level abstractions and tooling to capture, analyze, and generate timing-aware software. Domain-Specific Languages (DSLs) for timing specification, such as those integrated in the CoSiMA framework, enable the direct modeling of WCET, IPT, task dependencies, and end-to-end latency at the architectural level.\nSeparation of roles: Each role (component supplier, system architect, performance designer) contributes to timing metadata (WCET, core affinity, dependencies). Design-time scheduling: DSLs generate an executable schedule that is provably correct per constraints; errors (e.g., WCRT violations) are flagged early. Automated code/schedule synthesis: With tooling such as JetBrains MPS and scheduling solvers (e.g., OR-Tools), concrete schedules and code can be generated directly from architectural models. Case studies: such as for the COMAN humanoid’s ZMP walking controller, demonstrate that these model-driven workflows not only flag issues early in design, but also support introspection and runtime verification during execution.\nModel-driven timing DSLs give developers leverage to capture, analyze, and enforce timing constraints system-wide, bringing determinism from first architectural sketches to final code.\nSensor Input Processing and Timestamping in ROS Timestamping Challenges and Best Practices Achieving determinism in sensor processing starts with accurate timestamping, which aligns sensor data temporally to enable correct fusion and downstream processing.\nCommon pitfalls include:\nUsing system time when data is received, which ignores sensor-internal delays, transfer latencies, or unsynchronized clocks. Variable transmission and scheduling delays due to hardware, OS, or bus protocols. Clock skew and drift across multiple sensors and compute nodes. Solutions include:\nHardware timestamping: Where possible, use sensors with onboard clocks and encode the measurement time with each data packet. Precise time synchronization: Protocols such as PTP (IEEE 1588) enable sub-microsecond synchronization across distributed devices. For less precise applications, NTP provides millisecond-scale stability. Hybrid timestamping: Combining hardware-triggered event markers with software timestamps to estimate the true time of acquisition (not just arrival). Explicit synchronization design: For example, using external triggers between host and sensors, or leveraging time-synchronized bus protocols (e.g., EtherCAT DC). ROS message filters: Typical merging for sensor fusion in ROS is handled via message filters such as message_filters::Synchronizer, which align messages by timestamp but depend on underlying sensor data being correctly annotated. Improper timestamping leads to significant errors; for example, even a 10 ms misalignment can result in substantial localization errors (e.g., 16 cm for a robot moving at 90°/s).\nImpact on Deterministic Perception and Control Without accurate and deterministic timestamping, the system can fail to appropriately align and process sensor inputs, leading to non-deterministic downstream behavior; erratic control, inconsistent mapping, and unreliable navigation.\nEnsuring deterministic processing of sensor inputs requires:\nMinimizing transport and processing jitter, both in hardware and software paths. Enforcing bounded queue sizes and consistent callback ordering in ROS (or similar middleware). Combining time-triggered and data-triggered activation semantics, for example storing data for batch processing at deterministic intervals while supporting low-latency direct-triggered callbacks for critical events. Deterministic Communication in ROS and ROS 2 The Legacy (ROS 1) Communication Model ROS 1\u0026rsquo;s communication model is inherently asynchronous and best-effort. Nodes publish and subscribe to topics via TCP/IP or UDP, with message delivery and execution timing contingent on OS and network states. While this model promotes modularity and decoupling, it can introduce non-deterministic message delays, losses, and reordering; particularly under network congestion, CPU overload, or driver jitter.\nCritical issues include:\nUnbounded subscription queues and non-deterministic callback order. Callback execution tied to the order of arrival and thread scheduling (not explicit user control). Shared queues among multiple topics and nodes causing contention and unpredictable wakeup latencies. Enhancements in ROS 2 ROS 2 (built on DDS, the Data Distribution Service) aims to improve support for real-time and deterministic robotics workloads:\nFine-grained executor and callback group configuration: Nodes can be composed into single-threaded or multi-threaded executors, enabling tighter control over callback ordering. Quality-of-Service (QoS) policies: Users can tune delivery reliability, latency, queue depth, and history explicitly at the topic level. Real-time support in the OS and middleware stack: When running on a real-time Linux kernel (PREEMPT_RT), with careful configuration, ROS 2 can support bounded-latency scheduling of message delivery and callback execution. Deterministic replay and benchmarking: ROS 2 stacks are increasingly benchmarked for repeatability and deterministic performance, e.g., by using simulation time and orchestrator-enforced callback execution. However, full determinism is not automatic:\n“There are a lot of things that might interact with the system\u0026hellip; other nodes, introspection tools, system load. Even just a single timer isn\u0026rsquo;t guaranteed to be deterministic on most OSes, with the vagaries of the system scheduler. For full determinism you need to step up to a fully real-time system and program every piece to stay compliant with specific limits.”\nKey design patterns:\nUse single-threaded executors for critical nodes, with explicit callback configuration and bounded queues. Isolate real-time critical paths to their own processes or cores. Employ ROS 2’s QoS policies rigorously to ensure priorities and deadlines are respected. Real-Time Linux Kernels and PREEMPT_RT Patch As general-purpose Linux lacks strict real-time scheduling, the PREEMPT_RT patchset converts Linux into a deterministic platform, supporting:\nFull kernel preemption for bounded worst-case latencies. Real-time priority scheduling for user and kernel threads (SCHED_FIFO/SCHED_RR). Threaded interrupt handling (IRQ affinity and tuning). CPU isolation and shielding for exclusive allocation to real-time tasks. Recent Linux kernels (6.12+) integrate PREEMPT_RT into mainline, making setup easier across architectures. However, correct configuration is essential: disabling debug modules, reserving CPU cycles for non-RT tasks, prioritizing RT threads, and testing with tools like cyclictest and rtla. Isolating CPUs, setting IRQ affinity, and pinning real-time processes to dedicated cores are all best practices for minimizing interference.\nWith PREEMPT_RT, well-tuned communication stacks (e.g., UDP + PRIO) can achieve round-trip network latencies below 1 millisecond; including under stress, if CPU and IRQ resources are rigidly partitioned.\nDeterministic Task and Motion Planning (TAMP) Bridging abstract task planning with continuous low-level motion planning (TAMP) is an active area of research. The predominant challenge is that most sample-based motion planners are inherently non-deterministic; random sampling introduces output variability on every run. Deterministic TAMP replaces randomization with systematic or worst-case planning:\nDeterministic algorithms seek to guarantee the same plan for the same input, with provable space and time bounds. Model-driven symbolic planners (e.g., PDDLStream) integrate with deterministic geometric planners, managing hierarchical decomposition and parameter instantiation. Shared abstractions (e.g., scene graphs or constraint meta-solving) allow joint reasoning over task symbols and motion constraints. Nevertheless, pure deterministic planning can be intractable for large, uncertain environments. Recent strategies embrace a hybrid approach, combining symbolic reasoning with controlled, bounded non-determinism, or employing anytime algorithms that refine and verify feasible plans within timing constraints.\nIndustry Trends: RTOS Modularity and AI Integration RTOSes are becoming more modular and AI-aware:\nComponent-based architectures: Developers pick only kernel modules needed, reducing footprint and making certification easier. Security and isolation: RTOSes embed secure boot, trusted execution, and certified memory/process isolation. AI/ML-ready stacks: Support for on-chip AI acceleration, low-latency inference, and deterministic edge ML pipelines is standardizing, with platforms now integrating TensorFlow Lite, event-triggered ML, or safe AI inferencing modules. Certification-ready builds: Major RTOSes offer pre-qualification for critical safety and functional standards (QNX, SafeRTOS, Zephyr) and deep documentation for audits. Representative RTOSes for Robotics and Their 2025 Key Features\nRTOS License Safety-Critical AI-Readiness 2025 Trends/Notes FreeRTOS MIT Optional Middleware, basic OTA, AWS, memory safety Zephyr Apache 2.0 In progress Modular, partial Industrial, multi-core, CI/CD QNX Commercial Yes Limited Auto, medical, certification SafeRTOS Commercial Yes Full (IEC 61508) Avionics, secure isolation ThreadX MIT (Azure) Yes Some modules IoT, audio/video devices RTEMS GPL/Mod. Yes SMP, RISC-V Aerospace, defense embOS Proprietary Yes Tiny devices Medical, ultra-low-power Explanation: In 2025, leading RTOSes combine small footprint, modular builds, and built-in security, with growing support for cloud connectivity and edge AI. Certification is now a prime differentiator.\nCase Study: Deterministic ROS–EtherCAT Control Architectures A benchmark for integrating determinism in practical robotic systems is the combination of ROS with fieldbus protocols such as EtherCAT, employing real-time motion kernels and shared memory IPC.\nKey design features:\nDual-stack architecture: High-level control and planning run on non-real-time ROS nodes (e.g., path planning via MoveIt), while low-level motion control is handled by a real-time kernel (e.g., ARM-based, running PREEMPT_RT). Shared memory IPC: High-bandwidth, deterministic communication between ROS and the motion kernel is achieved by using a dual-port RAM or shared memory, avoiding unpredictable PCIe or TCP stack delays. EtherCAT for fieldbus: High-frequency, low-jitter actuator and sensor data exchange is managed using distributed clock EtherCAT masters, supporting precise synchronization and bounded cycle times. Jerk-limited trajectory generation: To ensure actuator smoothness and control loop determinism, trajectory profiles (e.g., S-curve) are computed in hard real-time, with cycle times as low as 250–1000 $\\mu$s and jitter \u0026lt; 50 $\\mu$s. Performance characterization: Real hardware experiments show low latency (sub-100 $\\mu$s) and consistent cycle times, with seamless integration of high-level ROS-based cognitive control and low-level deterministic motion primitives. Analysis: This layered approach achieves both the extensibility of ROS for integration and the timing guarantees of RTOS-controlled fieldbus networks, making it a blueprint for industrial and collaborative robots.\nFormal Methods and Verification for Deterministic Robotics Formal Specification and Offline Verification Traditional testing and simulation alone cannot provide sufficient evidence for correctness and timing in complex, hybrid robotic systems. Formal methods (model checking, theorem proving, scheduling analysis) allow roboticists to specify behavioral properties (safety, liveness, exclusivity, and especially timing) and to verify, through mathematical rigor, that all executions meet these requirements.\nExamples:\nModel checking (e.g., PRISM, UPPAAL): Verify, from formal system models, that all reachable system states meet deadlines, avoid overflows, and prevent deadlocks. Theorem proving (e.g., KeYmaera): Prove continuous-time and hybrid system properties, such as collision avoidance, using logics such as dL (differential dynamic logic). Runtime monitors: Synthesize executable monitors from verified formal models to detect and report deviations at runtime. Runtime and Online Verification Robotic software increasingly integrates runtime verification, with formal specifications automatically generating monitoring nodes (e.g., ROSRV) or leveraging DSLs that translate system models to observer code and runtime checks.\nTowards Executable Formal Models Recent research not only checks properties off-line, but also enables direct synthesis and execution of system code from verified formal models, integrating V\u0026amp;V through the entire development process. Here, timing, resource constraints, and safety invariants are embedded in the generated implementation, ensuring match between specification and system reality.\nReproducibility and Benchmarking in Robotics Research Reproducibility in robotics (achieving the same results across runs, hardware, and software stacks) is tightly coupled to determinism. It enables fair benchmarking, root-cause debugging, and systematic improvement:\nExposing sources of non-determinism: Random seeds, scheduling, asynchronous operations, and low-level hardware all introduce run-to-run and platform-to-platform variability. Controllable stochasticity: Even for stochastic algorithms (e.g., in deep reinforcement learning), randomness should be introduced via seeded deterministic generators, not uncontrolled OS-level or hardware-level noise. Determinism for debuggability: When a problem occurs, bit-for-bit repeatability across runs is invaluable for debugging and regression testing; some frameworks now expose tools to enforce or measure determinism in AI/ML training and inference. Recent open-source platforms (e.g., PyRobot) stress hardware- and middleware-independent interfaces to facilitate benchmarking, but these efforts depend on robust determinism at every system layer to be genuinely effective.\nDeterminism in Deep Learning Frameworks for Robotics Deterministic AI for Robotics Deploying deep learning (DL) in robots for tasks such as perception, control, and planning introduces further sources of non-determinism:\nNon-deterministic GPU operations, floating point arithmetic, and kernel scheduling. Variability across hardware architectures, driver versions, or OS configurations. Stochastic operations in training, such as unordered batch execution or parallel data processing. Achieving determinism calls for:\nFixing all pseudorandom seeds and using deterministic algorithms throughout model training and inference. Avoiding (or strictly controlling) GPU kernel nondeterminism, for example, by disabling otherwise non-deterministic cuDNN routines or controlling parallel execution modes. Ensuring that the entire hardware-software stack is fixed, including library and driver versions. Despite significant progress, bit-exact reproducibility across hardware architectures remains unresolved for many DL frameworks; efforts such as NVIDIA\u0026rsquo;s framework-reproducibility tools are continually updating best practices.\nDeterminism in Multi-Node and Multi-Threaded Systems Distributed robotics systems (with multiple nodes, agents, or threads) face compounded challenges:\nNon-deterministic message ordering, network delays, race conditions. Shared resource contention (e.g., on buses, RAM, network, sensors). Multithreaded execution order varies with scheduling and system load. Strategies for distributed determinism:\nExplicit allocation and partitioning: Fixed core/thread assignment and message routing, as in time-triggered architectures. Formal modeling: Use of timed automata, bounded buffers, and protocol verification to guarantee communication and execution latency. End-to-end timing analysis: Integrating per-node WCET and network timing guarantees for holistic system schedulability. Orchestrators and simulation frameworks: For benchmarking and replication, orchestrators enforce deterministic message delivery and execution timing across nodes and threads. Conclusion Designing for determinism in robotics frameworks is a multi-layered, systems-level challenge, deeply enmeshed with the design of RTOSes, scheduling strategies, timing analysis, process communication, sensor I/O, and high-level planning. Real-world robotic systems must integrate rigorous timing estimation (WCET, IPT, jitter), robust scheduling algorithms (RMS, EDF), and formal verification into both their design and deployment processes.\nRecent advances span modular RTOS architectures, timing DSLs, deterministic ROS and EtherCAT integration, and reproducible deep learning pipelines. Formal methods are increasingly embedded not only in offline verification, but as generative and runtime components of real systems. As AI becomes a standard component of robotics, determinism, reproducibility, and verifiability must extend into model training, inference, and deployment. The resulting systems will not merely function correctly on average, but be provably correct, safe, and auditable under all operating conditions.\nReferences Chen, J.-J., Huang, W.-H., von der Brüggen, G., Chen, K.-H., \u0026amp; Ueter, N. (2020). On the Formalism and Properties of Timing Analyses in Real-Time Embedded Systems. In A Journey of Embedded and Cyber-Physical Systems (pp. 37–55). Springer. Available online.\nYousef, K. M., Barakat, N., \u0026amp; Tawalbeh, L. (n.d.). Embedded Systems Task Scheduling Algorithms and Deterministic Behavior. Jordan University of Science and Technology, Department of Computer Engineering. View PDF.\nLütkebohle, I. (2017). Determinism in Robotics Software – When Things Break Sometimes and How to Fix It. Presented at ROSCon 2017. Conference presentation PDF.\n","permalink":"/blog/determinism-in-robotics-frameworks/","section":"blog","summary":"An in-depth exploration of why deterministic behavior is critical in robotics frameworks, covering scheduling strategies, timing constraints, and real-time operating system considerations.","tags":["Blog","Robotics","Determinism","Real-Time Systems","Scheduling","Timing Constraints","RTOS","Control Systems","Embedded Systems","Automation"],"title":"Designing for Determinism in Robotics Frameworks"},{"content":"Overview Python’s immutable strings incur a performance penalty when concatenating or transforming large texts. pystringbuilder provides a mutable, preallocated buffer and a rich API (append, insert, delete, replace, trim, reverse, substring, search) so you can build and manipulate strings in place.\nBuilt as a drop-in StringBuilder class for Python 3.x, it reduces heap churn and accelerates workloads that would otherwise thrash the allocator.\nMotivation In data processing, report generation, and code generation tasks, developers often assemble long strings via repeated concatenation or join(). While join() helps, it doesn’t cover complex in-place edits (e.g., insertions, deletions, replacements, or reversals) without creating intermediate copies.\npystringbuilder addresses this gap by:\nPreallocating a contiguous buffer to reduce reallocations. Offering a fluent API for chaining operations. Providing efficient search (KMP-based) and slicing. Key Features Preallocated internal buffer with automatic growth. Fluent chaining: append(), append_line(), insert(), delete(), replace(), trim(), reverse(), substring(). Native Python indexing, slicing, and iteration support. Knuth-Morris-Pratt algorithm for efficient substring search. Zero-allocation operations for most common use cases. Lightweight: single Python file, no external dependencies. Architecture The core of pystringbuilder revolves around a dynamic list of characters and a capacity pointer:\nbuffer: a Python list of single-character strings. length: current logical length of the builder. capacity: total allocated slots before resizing. Main components:\nGrowth policy: doubles capacity when needed. Operation methods: perform in-place shifts, swaps, or overwrites. Search module: precomputes “failure functions” for KMP. Conversion: __str__() slices and joins only once. API Usage from stringbuilder import StringBuilder # Initialize with default capacity: sb = StringBuilder() # Chain appends and lines: sb.append(\u0026#34;Error: \u0026#34;).append_line(\u0026#34;Null reference\u0026#34;) # Insert before index 7: sb.insert(7, \u0026#34;[CRITICAL] \u0026#34;) # Replace a substring: sb.replace(\u0026#34;Null\u0026#34;, \u0026#34;Missing\u0026#34;) # Delete characters 9–16: sb.delete(9, 16) # Trim whitespace and reverse: sb.trim().reverse() # Convert to native string: sFinalText = str(sb) You can also slice and iterate:\nfor ch in sb: process(ch) sub = sb.substring(0, 10) Benchmarks Building a 1 million-character string by repeated += vs. StringBuilder:\nMethod Time Memory Allocations += concatenation 4.2 s 1,000,000 ops list + ''.join() 0.45 s 1 allocation StringBuilder (this) 0.12 s 1 resize, 1 join Searching “needle” in a 10 MB haystack:\nMethod Time str.find() 0.18 s KMP search 0.11 s Testing \u0026amp; Quality pystringbuilder includes a comprehensive test suite using unittest:\nOver 47 unit tests covering edge cases for each method. Fuzz tests for random insert/delete sequences. Performance regression checks on large-scale buffers. Code coverage is maintained above 95%. Continuous integration runs on each pull request.\nInstallation Clone and import directly, or install via pip (coming soon):\ngit clone https://github.com/theoeriata/pystringbuilder.git cd pystringbuilder pip install . Then:\nfrom stringbuilder import StringBuilder No other dependencies are required beyond Python 3.x.\nContribution Guidelines Contributions are welcome:\nFork the repository and create a feature branch. Adhere to existing style and add unit tests. Submit pull requests with clear issue references. Report bugs via GitHub Issues. Reviewers aim to process PRs within when available.\nFuture Work Publish on PyPI for easy installation. Expose configuration of growth strategy (e.g., 1.5× vs. 2× ). Add a C extension for even lower-level performance. Support Python 2.x compatibility if required. Implement additional search algorithms (Boyer-Moore). License This project is licensed under the MIT License. See the LICENSE file for details.\n","permalink":"/projects/python-stringbuilder/","section":"projects","summary":"pystringbuilder: a Python StringBuilder class with preallocated buffers and fluent API for efficient large-text manipulation.","tags":["Project","Python","Performance","Text Processing","Libraries"],"title":"Python StringBuilder: High-Performance Text Construction"},{"content":"Introduction In today’s competitive digital landscape, businesses must rely on data and insight to drive their marketing strategies, refine user experiences, and boost overall performance. Google Analytics has emerged as the industry standard for tracking and interpreting user behavior. Its versatility and depth make it invaluable for small business owners, marketing professionals, and data analysts alike. Whether it’s analyzing a simple website or managing a multi-channel digital ecosystem, the platform’s flexible, event-based measurement model offers practical tools for achieving granular insights and informed business decisions.\nThe Evolution and Core Components of Google Analytics From Universal Analytics to Google Analytics 4 (GA4) Google Analytics has undergone significant changes over the years. While Universal Analytics focused on sessions and page views, GA4 introduces a paradigm shift with an event-based model. In GA4, every user interaction (regardless of whether it happens on a website or a mobile app) is recorded as an event. This unified approach simplifies cross-device tracking and reveals nuanced pathways in the user journey.\nThe Event-Based Measurement Model In GA4, all interactions are captured as events, allowing for a flexible and detailed approach to tracking user behavior:\nEvent Parameters: These provide context to each event. For example, when a visitor watches a video, the event might include parameters such as the video title. Custom Dimensions and Metrics: Users have the option to define custom dimensions (like a writer’s name on a blog) or metrics that align with specific business needs. These allow for richer, tailored reporting. Data Streams Data streams are at the heart of GA4, serving as the source through which data flows into a property:\nWeb Data Streams: Capture data from websites. App Data Streams: Collect information from iOS and Android applications. Using one property with multiple data streams (one for a website and one each for iOS and Android apps) creates a comprehensive view across platforms, a central benefit of GA4. Key Features and Data Collection Techniques Enhanced Measurement and the Analytics Tag Before any data analysis can begin, it is crucial to set up the mechanism for data collection. By adding an Analytics tag to your website, you enable GA4 to automatically capture a wide range of information. When enhanced measurement is enabled, additional predefined events (such as scroll tracking, outbound clicks, and site searches) are collected without the need for further code modifications.\nCustomizing Event Tracking Google Analytics provides multiple tools to fine-tune event tracking:\nCreate Event: Define new events based on specific user interactions (e.g., landing on a homepage) without altering the existing code base. Key Events: Mark events that are particularly valuable, such as purchases or newsletter sign-ups, for conversion tracking. User-ID: Associate your own identifiers with users to track cross-device interactions, offering a holistic view of the customer journey. Data Retention and Privacy Considerations Data retention settings in Google Analytics allow administrators to control how long user-level and event-level information is stored. This not only helps with compliance but also ensures that data used for insights is relevant and current.\nReporting and Exploratory Techniques Google Analytics offers various reporting methods that cater to both standard and advanced analytics needs.\nStandard Reports The platform provides predefined reports that answer common questions:\nAcquisition Reports: Reveal where users are coming from, tracking dimensions like session source/medium and traffic acquisition. Realtime Reporting: Shows interactions that occurred in the last 30 minutes, allowing for immediate insight into user activities. Advanced Explorations For deeper analysis, the Explore section is invaluable:\nPath Exploration: Visualizes the steps users take through your site (from the homepage to subsequent pages) using interactive flow diagrams. Funnel Exploration: Displays the progression (and drop-offs) across critical conversion steps, providing clarity on where users succeed or fail. Secondary Dimensions: Enhance table reports by adding additional layers of data for deeper insight. Using these exploratory methods, analysts can break down complex user journeys and filter insights by device type (via the Tech Details Report) or additional custom dimensions.\nIntegrations with Other Platforms BigQuery Integration Exporting data from GA4 to BigQuery opens up opportunities for advanced analysis. Analysts can:\nUse SQL to query large datasets and perform custom analyses. Integrate GA data with other business data sources to gain comprehensive insights into products, user behavior, and channel performance. Syncing with Google Ads and Search Console When Google Analytics is linked with other Google platforms, the data becomes even more actionable:\nGoogle Ads: Enables advertisers to optimize ad bids based on conversion events imported from GA4. Predictive audiences, derived from GA\u0026rsquo;s machine learning capabilities, allow for targeting users likely to complete desired actions. Search Console: Connects to GA to provide details about organic search queries, thus offering insights that are critical for refining SEO strategies. Advanced Analytics and Audience Exploration Predictive Audiences Predictive metrics in GA4 can forecast future behavior, such as likely purchases within a set timeframe. This enables businesses to create more targeted, effective campaigns.\nAudience Triggers Audience triggers allow for tailored event creation when an audience rule is met. For instance, when a user group meets certain criteria, new events can be generated automatically, offering highly specific insights into engagement.\nAggregating Data with Roll-Up Properties In Analytics 360, features such as roll-up properties aggregate data across multiple source properties (e.g., across different brands or regions), providing a holistic view of business performance.\nPractical Implementation and Setup Before benefiting from these advanced insights, a proper setup is crucial:\nInitial Setup Analytics Tag: For a website, embedding this tag is the foundational step. Data Streams: Establish separate streams for your website and mobile apps to ensure data is captured correctly. Enhanced Measurement: Enable this feature to collect additional interactions automatically. Configurability Custom Dimensions \u0026amp; Metrics: Implement to capture distinct business data points, such as membership statuses or specific event parameters. Event Modifications: Choose between modifying events, creating events, or marking events according to their significance. Why Learn Google Analytics? Empowering Decision-Making In an age where every user interaction matters, Google Analytics equips businesses with the quantitative and qualitative data necessary for informed decision-making. By understanding user behavior (from initial landing pages to conversion paths) companies can optimize marketing strategies, allocate budgets more efficiently, and create tailored user experiences.\nIntegration and Versatility Whether it’s merging online and offline data via the Measurement Protocol or analyzing cross-device behavior through User-ID, the skills learned in Google Analytics are universally applicable. Its integration with BigQuery, Search Console, and Google Ads means that understanding GA not only improves website performance but also strengthens overall digital marketing efforts.\nCompetitive Advantage Analyzing data effectively provides a competitive edge. Mastery of Google Analytics means you can:\nPinpoint underperforming segments or pages. Leverage predictive insights to target high-value audiences. Continually refine and test ad campaigns using rich, multi-dimensional data. Career Growth and Digital Transformation For professionals in digital marketing, data science, and IT, proficiency in Google Analytics opens doors to roles centered around data analysis, digital strategy, and user experience design. Moreover, in today’s era of digital transformation, the ability to harness data translates into tangible business growth and innovation.\nConclusion Google Analytics is more than just a tool for measuring website traffic, it is a robust platform that integrates advanced data collection, analysis, and predictive capabilities. Its flexible event-based model, comprehensive reporting tools, and seamless integration with other digital platforms offer invaluable insights into user behavior, marketing effectiveness, and business performance.\nFor anyone involved in digital marketing or business analytics, learning Google Analytics is not merely a technical skill; it is an essential component of a data-driven strategy that drives efficiency, innovation, and competitive advantage. Whether you are setting up your first Analytics tag or delving into complex cross-device user journeys, mastering Google Analytics equips you to make informed, impactful decisions that resonate in today’s dynamic digital environment.\nReflection on Preparing for the Google Analytics 4 Certification\nEmbarking on the journey to achieve my Google Analytics 4 (GA4) certification has been both rewarding and challenging. As I look back on the process, four key areas stood out as particularly demanding, but they have ultimately enriched my understanding and expanded my capabilities as an analytics professional.\n1. Adapting to the GA4 Interface: One of the first challenges I encountered was navigating the entirely new interface of GA4. Compared to Universal Analytics, GA4’s layout and functionality required a fresh perspective. Adjusting to how data streams were set up, finding reports, and understanding the event-driven model were a bit overwhelming at first. However, this learning curve taught me how to explore interfaces intuitively and better appreciate how GA4 simplifies cross-platform tracking.\n2. Understanding the Event-Based Data Model: The shift from session-based tracking in Universal Analytics to event-based tracking in GA4 was another significant hurdle. Every action (be it a page view or a button click) is now considered an event, with parameters offering additional context. Wrapping my head around this model and configuring custom events helped me develop a more detailed approach to capturing user behavior and emphasized the importance of defining clear goals for data collection.\n3. Mastering GA4 Reporting Tools: GA4 introduced enhanced reporting features, including Analysis Hub. Creating custom reports and using exploration tools for advanced insights initially felt intimidating due to the range of options available. Through consistent practice and exploration, I gained a deeper understanding of how tailored reporting empowers businesses to act on specific data, improving my analytical and storytelling skills.\n4. Configuring and Analyzing Audiences: Audience creation in GA4 is both flexible and complex, requiring a mix of strategic thinking and technical know-how. As I learned to segment users based on events, parameters, and conditions, I started to view audience analysis as an essential tool to provide more targeted insights for marketing strategies. This challenge fostered a strategic mindset toward user behavior and segmentation.\nEach of these challenges has contributed to a broader and more nuanced understanding of web analytics. They have pushed me beyond my comfort zone, allowing me to appreciate GA4 as a powerful tool for data-driven decision-making. The certification process didn’t just enhance my technical skills but also solidified my confidence as an analyst ready to tackle diverse business needs.\nBy embracing the learnings discovered within Google Analytics, businesses and professionals alike can unlock deeper insights into customer engagement, streamline conversion paths, and ultimately foster a more comprehensive, effective digital strategy.\n","permalink":"/blog/google-analytics-insights/","section":"blog","summary":"An in-depth reflection on achieving Google Analytics 4 certification, covering core concepts like event-based tracking, data streams, integration with tools like BigQuery and Google Ads, and the value of GA4 in digital strategy and career development.","tags":["Blog","Google Analytics","GA4","Digital Marketing","Data Analysis","Web Analytics","Business Intelligence","Predictive Audiences","Career Growth","Certification","BigQuery","User Experience","Decision Making"],"title":"Comprehensive Insights Into Google Analytics: Learnings and Motivations"},{"content":"Introduction March marks Nutrition Month, an annual initiative that highlights the essential role of healthy eating in our overall well-being. As I explored this year\u0026rsquo;s theme, \u0026ldquo;Nourish to Flourish,\u0026rdquo; I became increasingly aware of the profound impact that nutrition has on our lives and decided to actively participate in this initiative to promote better nutritional practices.\nThe theme \u0026ldquo;Nourish to Flourish\u0026rdquo; emphasizes the connections between food and health, and highlights the diverse roles that dietitians play in improving the well-being of Canadians (Dietitians of Canada, 2025). This initiative encourages us to celebrate and recognize the contributions of dietitians and to take steps towards better nutrition.\nMy Steps to Take Full Advantage of Nutrition Month Educating Myself and Others To fully embrace Nutrition Month, I began by educating myself on various aspects of nutrition. I enrolled for the (March 19 and 26) virtual webinars organized by Dietitians of Canada, covering topics such as mental health and nutrition, cultural competency in nutrition practice, and strategies for overcoming picky eating in children (Dietitians of Canada, 2025). This knowledge will empower me to share valuable information with both my family and my community.\nSharing Real Stories Throughout the month, Dietitians of Canada shared inspiring stories from their members, showcasing the positive impact dietitians have on the health of Canadians (Dietitians of Canada, 2025). Motivated by these stories, I decided to share my own experiences with friends, family, and social media followers (this blog), emphasizing the importance of nutrition and encouraging others to adopt healthier eating habits.\nParticipating in Advocacy Initiatives I joined advocacy efforts by participating in the Advocacy Webinar hosted by Santis Health, which is focused on effective strategies for the upcoming election and essential advocacy practices (Dietitians of Canada, 2025). This experience will provide me with valuable insights into how I can contribute to policy changes that promote better nutrition and public health.\nUtilizing Resources and Toolkits Dietitians of Canada provided comprehensive planning guides, toolkits, and branded graphics to help individuals get involved with Nutrition Month (Dietitians of Canada, 2025). I utilized these resources in the writing of this blog post to share educational materials with my community, empowering me to effectively advocate for better nutrition practices.\nEngaging on Social Media To amplify my advocacy efforts, I actively engaged on social media posts using the hashtag #NourishToFlourish (Dietitians of Canada, 2025). This helped discover a wider audience and encourage more people to join the conversation about the importance of nutrition.\nNutrition Month 2025 is a transformative experience, highlighting the critical role that nutrition plays in our overall well-being. By educating myself and others, sharing real stories, participating in advocacy initiatives, utilizing available resources, and engaging on social media, I have taken meaningful steps to promote better nutritional practices in my community. Together, we can all \u0026ldquo;Nourish to Flourish\u0026rdquo; and contribute to a healthier future.\nWe\u0026rsquo;ve all heard the saying, \u0026ldquo;You are what you eat,\u0026rdquo; but this month, it\u0026rsquo;s an invitation to go deeper and truly explore how the foods we consume shape our physical, mental, and emotional well-being. It\u0026rsquo;s about recognizing the transformative power of food and making mindful choices that help us thrive.\nJoin me in celebrating Nutrition Month by taking a moment to reflect on our own nutritional journeys. Let\u0026rsquo;s embrace the opportunity to nourish our bodies in ways that allow us to flourish in all aspects of life.\nSee media toolkit here: https://www.dietitians.ca/Advocacy/Nutrition-Month-2025/Nutrition-Month-2025-General-toolkit.\nReferences Dietitians of Canada. (2025). Welcome to Nutrition Month 2025. Retrieved from https://www.dietitians.ca/Advocacy/Nutrition-Month-2025.\n","permalink":"/blog/nutrition-month-2025/","section":"blog","summary":"A personal reflection on Nutrition Month 2025, highlighting the theme “Nourish to Flourish” and detailing actionable ways to participate, from attending webinars and sharing stories to utilizing advocacy resources and engaging online.","tags":["Blog","Nutrition","Health Advocacy","Dietitians","Public Health","Wellness","Healthy Eating","Nutrition Month","Nourish to Flourish","Canada","Social Engagement","Education"],"title":"Discovering Nutrition Month: Nourish to Flourish"},{"content":"Introduction Water Molecular Dynamics Simulation is a physics-driven C++ application that recreates the emergence of water molecules from individual oxygen and hydrogen atoms. By computing bonded and non-bonded forces at each time step, the program delivers a scientifically grounded yet visually engaging demo of molecular behavior in an interactive 3D environment.\nProject Overview Purpose: Offer an educational, real-time window into the forces shaping water at the molecular scale. Engine Core: Custom C++ simulation engine with ImGui for live parameter tweaking. Key Algorithms: Harmonic bond stretching for O–H bonds Harmonic angle bending for H–O–H geometry Lennard-Jones and Coulomb potentials for non-bonded interactions Platforms: Windows and Linux. Features Dynamic molecular interactions governed by physics-based potentials. Real-time ImGui interface to adjust: Oxygen and hydrogen atom counts Initial velocity scale Simulation box dimensions Performance overlay with FPS, compute timings, and debug console. Built-in plotting of water molecule formation progress over time. Settings Panel Parameter Default Description Oxygen atoms 100 Number of O atoms spawned at simulation start Hydrogen atoms 200 Number of H atoms spawned at simulation start Initial velocity scale 1.0 Scales random initial velocities of all atoms Simulation box size 10×10×10 World bounds for atom positions and reflections Use the sliders and numeric inputs in the ImGui panel to experiment with density and kinetic energy on the fly.\nVisualization and Debugging A debug overlay provides real-time statistics:\nFrame rate display (toggle with F5). Kernel timing breakdowns. On-screen graphs plotting the count of complete H₂O molecules over time. Screenshots and performance data help you understand both the molecular dynamics and the engine’s efficiency.\nControls Camera: WASD + mouse to fly around the simulation box. UI Toggle Keys: F2: Open/close debug console (Tab for auto-complete). F5: Show/hide FPS counter. F12: Capture a high-resolution screenshot. These shortcuts let you navigate, inspect, and document the simulation without interrupting the main loop.\nHow It Works At each integration step, forces on every atom are summed from:\nBonded Forces\nHarmonic bond stretching: $ V_\\text{bond} = \\tfrac{1}{2} k_\\text{bond} (r - r_0)^2 $ Keeps O–H bonds near their equilibrium length. Harmonic angle bending: $ V_\\text{angle} = \\tfrac{1}{2} k_\\text{angle} (\\theta - 104.5^\\circ)^2 $ Enforces the natural H–O–H angle. Non-Bonded Forces\nLennard-Jones potential for van der Waals interactions. Coulomb’s law for electrostatic attraction/repulsion. Positions and velocities update via simple velocity-Verlet integration, ensuring stability at small time steps.\nFuture Work Refine angle-bending model for more precise H₂O geometry. Add explicit thermostat to control temperature. Support multiple species (ions, contaminants). Expand visualization modes (surface mesh, volumetric density). License This project is released under the MIT License. See LICENSE for details.\n","permalink":"/projects/xii-wave-formation-md/","section":"projects","summary":"Water Molecular Dynamics Simulation by TheophilusE provides a window into water formation at the atomic level, modeling O–H bonds, H–O–H angles, and non-bonded interactions in an interactive C++ demo.","tags":["Project","C++","Molecular Dynamics","Simulation","Physics"],"title":"XII: Wave Formation MD"},{"content":"Abstract In mathematical discourse, the conceptualization of structure and function serves as a cornerstone for rigorous formalization and precise semantics. This note delves into the symbiotic relationship between structure and function, emphasizing the pivotal role of formalized frameworks in ensuring unambiguous communication and understanding among mathematicians. We explore the underlying principles of formal systems, their impact on the fidelity of mathematical objects, and the intricate semantics that govern their functional attributes.\nIntroduction The interplay between structure and function in mathematics transcends mere theoretical abstraction; it embodies the essence of rigorous formalizations and precise semantics. As mathematical structures strengthen the foundational frameworks within which functions operate, formalization emerges as the sine qua non for ensuring clarity and exactitude in mathematical discourse. This note aims to elucidate the intricate dynamics between structure and function, with a focus on the formalization processes that guarantee semantic rigor.\nFormal Systems and Mathematical Structures At the heart of mathematical inquiry lies the concept of formal systems - syntactic frameworks comprising axioms, rules, and symbols. These systems provide the scaffolding for constructing mathematical structures, which in turn dictate the behavior and properties of functions. The formalization of these structures involves the meticulous definition of axiomatic bases and inferential rules, ensuring that mathematicians operate within a well-defined semantic space. By adhering to formal systems, mathematicians can reference the same object with unwavering precision, thereby eliminating ambiguities and fostering a shared understanding.\nStructure Dictates Function The adage \u0026ldquo;structure dictates function\u0026rdquo; encapsulates a fundamental tenet of mathematical thought. Within a formalized system, the structural attributes of mathematical objects inherently determine their functional behavior. This principle is vividly illustrated in fields such as algebra, topology, and category theory, where the formal properties of structures - groups, topological spaces, and categories - dictate the operations and mappings that define functions. The rigor of formalization ensures that the semantic implications of these structures are consistently interpreted, preserving the integrity of functional relationships.\nSemantic Precision in Formalized Frameworks Semantics in mathematics extends beyond mere denotation; it encompasses the nuanced interpretation of formal structures and their functional manifestations. Rigorous formalization necessitates a precise semantic framework, wherein every symbol and operation is imbued with unambiguous meaning. This semantic rigor is paramount in fields such as model theory, where the interplay between syntax and semantics determines the truth values of mathematical statements. Formalized frameworks enable mathematicians to engage in meaningful discourse, devoid of interpretational discrepancies, which is a consequence of establishing a rigorous semantic foundation.\nConclusion The symbiotic relationship between structure and function in mathematics hinges on the rigor of formalizations and the precision of semantics. Formal systems provide the structural scaffolding that dictates functional behavior, while rigorous semantic frameworks ensure unambiguous interpretation. Mathematicians can more clearly reference mathematical objects with clarity and precision. In the pursuit of mathematical rigor, the formalization of structures and the semantics of functions emerge as indispensable pillars, which reflects the integrity and coherence of mathematical inquiry.\n","permalink":"/blog/formalizations-and-semantics/","section":"blog","summary":"A concise exposition on the interplay between mathematical structures and functions, emphasizing the necessity of formal systems and precise semantics for rigorous discourse.","tags":["Blog","Mathematics","Formal Systems","Semantics","Mathematical Logic","Structure and Function","Category Theory","Model Theory","Topology","Algebra","Mathematical Philosophy"],"title":"Structure and Function: Rigorous Formalizations and Semantics"},{"content":"Introduction Looking back on the 2022–2023 FTC “Power Play” season, what stands out isn’t just the robot we built, but the journey we shared. When I first met our all-girls team, they were curious but tentative, with some uncertainty whether gears and code could ever feel like their own language. Over ten months, I witnessed that hesitancy transform into bold experimentation, late-night troubleshooting, and the thrill of watching a vision pipeline finally detect an object on the field.\nFrom day one, I aimed to teach more than formulas. Yes, we unpacked the Euclidean norm for color filtering and PID control loops for stable driving, but we also paused to discuss how failure sparks creativity and how collaboration can outpace any solo effort. We held reflection circles after each build session and milestone, asking questions like “What worked and what didn\u0026rsquo;t?” and “How can we overcome a setback?” These moments nurtured empathy, resilience, and a shared sense of ownership over every line of code and every physical part of the robot.\nMentoring this team reshaped my own views on leadership and learning. I learned to listen beyond technical questions; tuning in to frustrations about imposter syndrome, celebrating small breakthroughs in confidence, and guiding the team through moments of doubt. Their growing self-belief reminded me that real STEM education thrives when technical rigor and emotional support walk hand in hand.\nIn the following sections, I’ll explore how we wove theory with practice, balanced individual growth with collective achievements, and built a culture where every member felt both challenged and supported. This holistic approach ignited a lasting passion for engineering in a group of young women who now see themselves as creators, problem-solvers, and pioneers.\nGroundwork for Computer Vision in Robotics In this section, we’ll unpack the foundational concepts that turned raw camera feeds into reliable, robot-usable data. We’ll explore how we moved from pixels to field coordinates, the mathematical choices that drove our design, and the hands-on activities that cemented these ideas in the students’ minds.\nThe Mathematics of Color Detection Every computer vision pipeline begins with distinguishing “what matters” from background noise. For our FTC robot, that meant singling out game elements (e.g., colored cones and cubes) against a busy field.\nWhy RGB? Pros and Cons RGB is the camera’s native output, so no initial conversion saves compute cycles. However, brightness changes (e.g., shadows under the field lights) warp RGB distances, leading to false positives. To address this, we:\nBriefly introduced HSV color space and showed how decoupling Hue from Value can make thresholds more stable. Had students capture sample frames under different lighting, then manually adjust RGB and HSV thresholds to see which was more robust. This juxtaposition taught them that math choices aren’t abstract, they’re dictated by real-world constraints.\nFrom Pixel Vector to Distance Metric Representing a pixel as $ \\vec p = \\langle R,G,B \\rangle $ lets us apply well-known vector operations:\nEuclidean norm: intuitive, fast, but treats each channel equally. Weighted norm: by introducing a diagonal weight matrix \\(W\\), $$ \\lvert d \\rvert = \\sqrt{(\\mathbf{p}-\\mathbf{t})^T W (\\mathbf{p}-\\mathbf{t})}, $$ we could weight individual channels based on the dominant color characteristics of our target region (e.g., cones clustering in the green channel). Students examined lightweight evaluation scripts to compare the performance of both the Manhattan and Euclidean norms on representative image data. By quantifying pixel-wise differences between targets and noise, they observed how each norm responded to artifacts and variability. The exercise demonstrated how norm selection directly impacts filter sensitivity and robustness\nBuilding the Thresholding Pipeline Turning distance measurements into a binary mask involved more than a single cutoff. We designed a multi-stage filter:\nGlobal threshold. Pixels with $d \u003c \\varepsilon$ become candidates. Students experimented to choose $\\varepsilon$ via live telemetry of false positives vs. missed detections. Morphological cleanup. Apply erosion then dilation to remove speckles. We discussed how kernel size trades off removing small noise vs. eroding actual objects. Blob analysis. Label connected components and compute area, aspect ratio, and solidity. Only blobs within expected size/shape ranges moved forward. At each step, students ran the pipeline, adjusting parameters and immediately seeing the effect on both simulated and real camera frames. This iterative loop cemented the idea that vision is as much about tuning as it is about theory.\nCamera Calibration and Intrinsics Raw pixel coordinates $(u, v)$ don’t map directly to metric distances. We can perform a one-day calibration, which includes:\nPrinting a checkerboard poster, taping it to a flat surface. Using OpenCV routines to estimate the intrinsic matrix $K$ and distortion coefficients. Key takeaways for students:\nFocal length and principal point define how rays from real-world points intersect the sensor. Lens distortion (radial and tangential) can warp straight edges; if uncorrected, a straight line on the field looks curved in the image. After calibration, every frame will be first undistorted, then pixel coordinates can be normalized as\n$$ \\begin{bmatrix}x_c \\\\ y_c \\\\ 1\\end{bmatrix} = K^{-1} \\begin{bmatrix}u \\\\ v \\\\ 1\\end{bmatrix}. $$ This step is non-negotiable as failing to undistort can lead to systematic errors in distance estimates.\nExtrinsic Transforms and Homogeneous Coordinates Detecting an object in the camera frame is only half the battle. To drive toward it, the robot needs that point in its own coordinate system.\nRotation and Translation in 2D We broke the problem into digestible chunks:\nRotation: taught via a turntable demo; placing a square marker, rotating it by hand, and measuring how its coordinates change. Translation: visualized by sliding the marker along a ruler and tracking the offset. Putting them together, we have obtain\n$$ R(\\theta) = \\begin{bmatrix} \\cos\\theta \u0026 -\\sin\\theta \\\\ \\sin\\theta \u0026 \\cos\\theta \\end{bmatrix} \\quad \\vec{t} = \\begin{bmatrix}t_x \\\\ t_y \\end{bmatrix}. $$ We can now transform a point $\\bigl(x_c,y_c\\bigr)$ into robot space with\n$$ \\begin{bmatrix}x_r\\\\y_r\\end{bmatrix} = R(\\theta)\\begin{bmatrix}x_c\\\\y_c\\end{bmatrix} + \\mathbf{t}. $$ Homogeneous Coordinates for Chaining To combine undistortion, rotation, and translation in one stroke, we introduce a new $3×3$ homogeneous matrix\n$$ H = \\begin{bmatrix} r_{11} \u0026 r_{12} \u0026 t_x\\\\ r_{21} \u0026 r_{22} \u0026 t_y\\\\ 0 \u0026 0 \u0026 1 \\end{bmatrix}. $$ By multiplying\n$$ \\begin{bmatrix}x_r\\\\y_r\\\\1\\end{bmatrix} = H \\begin{bmatrix}x_c\\\\y_c\\\\1\\end{bmatrix}, $$ which shows us how multiple transforms collapse into a single matrix multiplication, which is crucial for efficient, real-time pipelines.\nHands-On Reinforcement Throughout these modules, we wove in exercises that bridged theory and practice:\nVisualizing pixel vectors in color space to identify clusters. Building simple GUIs and telemetry sliders which controlled $\\varepsilon$, kernel size, or rotation angle, and the students saw immediate feedback on the camera feed. By the end of these groundwork sessions, the students had the intuition of tracing a pixel from the raw camera image all the way to a precise robot-centric $(x_r, y_r)$ pair, complete with error bounds. This mathematical and experimental preparation laid the foundation for the later control and automation stages, ensuring our vision system was both accurate and robust under competition pressures.\nShipping Element Pipeline: Detecting the Park Signal In the autonomous period, the robot must read the “shipping element” signal (one of three colored markers) to know where to park. Our ShippingElementPipeline uses Y′CbCr color space conversion, a center‐region sampling strategy, and Euclidean‐distance matching in both RGB and Y′CbCr to robustly classify the signal. Here’s how it was implemented in practice.\nRGB $\\to$ Y′CbCr Conversion While the camera outputs RGB, separating luminance (Y′) from chrominance (Cb / Cr) makes color detection far less sensitive to lighting changes. We implement the standard BT.601 transform\n$$ \\begin{bmatrix} Y'\\\\ Cb\\\\ Cr \\end{bmatrix} = \\begin{bmatrix} 0.299 \u0026 0.587 \u0026 0.114\\\\ -0.168736 \u0026 -0.331264 \u0026 0.5\\\\ 0.5 \u0026 -0.418688 \u0026 -0.081312 \\end{bmatrix} \\begin{bmatrix} R\\\\ G\\\\ B \\end{bmatrix} + \\begin{bmatrix} 0\\\\ 128\\\\ 128 \\end{bmatrix}. $$ In code, OpenCV handles this via:\nImgproc.cvtColor(input, yCrCbMat, Imgproc.COLOR_RGB2YCrCb); Defining the Region of Interest We sample a small box at the center of the frame (where the signal is expected) of size $W\\times H$\nRect centerSampleRect = new Rect( (int)(0.5 * input.width()), (int)(0.5 * input.height()), centerMarkerPositionWidth, centerMarkerPositionHeight ); Mat sampleYCrCb = yCrCbMat.submat(centerSampleRect); Mat sampleRGB = input.submat(centerSampleRect); By computing the mean color in both spaces\nScalar meanYCrCb = Core.mean(sampleYCrCb); Scalar meanRGB = Core.mean(sampleRGB); we get two 3D-vectors $\\vec c_{\\rm YCrCb}$ and $\\vec c_{\\rm RGB}$.\nColor Matching from Euclidean Distance We pre‐define three target colors in RGB as $$ \\vec t_i = (R_i,G_i,B_i),\\quad i\\in\\{1,2,3\\}. $$ Their Y′CbCr equivalents $\\mathbf{t}_i'$ are computed once in the constructor using our FastMath.convertRGB2YCRCB(...). At runtime, we pick the closest color by minimizing\n$$ d_i = \\|\\vec c_{\\rm RGB} - \\vec t_i\\| = \\sqrt{\\sum_{k\\in\\{R,G,B\\}} (c_k - t_{i,k})^2}. $$A simpler (and faster) variant uses squared distance to avoid the square root\n$$ d_i^2 = \\sum_{k}(c_k - t_{i,k})^2. $$In Java code we obtain the following.\ndouble fBestDistance = fMinThresholdValue; ParkTargetSignal bestSignal = SIGNAL_NONE; for (Pair\u0026lt;ParkTargetSignal,Vector3d\u0026gt; pair : signalColorPairs) { double fDistance = rgbFinalColor.distance(pair.snd); if (fDistance \u0026lt; fBestDistance) { fBestDistance = fDistance; bestSignal = pair.fst; } } targetSignal = bestSignal; Debug Overlays \u0026amp; Final Output To aid tuning, we draw the sampling rectangle in a color matching the detected signal.\nScalar vOverlayColor; switch (targetSignal) { case SIGNAL_ONE: vOverlayColor = new Scalar(41,118,187); break; case SIGNAL_TWO: vOverlayColor = new Scalar(51,184,100); break; case SIGNAL_THREE: vOverlayColor = new Scalar(253,89,86); break; default: vOverlayColor = new Scalar(255,255,255); break; } Imgproc.rectangle(input, centerSampleRect, vOverlayColor, 2); We also render a text label at $(0.5w, 0.2h)$\nImgproc.putText(input, getLabelForSignal(targetSignal), new Point(0.5 * input.width(), 0.2 * input.height()), Imgproc.FONT_HERSHEY_COMPLEX, 1.0, new Scalar(255,255,255) ); Key Takeaways Converting to Y′CbCr decouples brightness from color, improving robustness under variable lighting. Sampling a fixed center region reduces noise from distracting background elements. Euclidean distance in RGB (or squared distance) works well when target colors are well separated. Debug overlays and real‐time tuning (via FTC Dashboard) make threshold and region adjustments intuitive. By combining these mathematical and programming techniques, the team learned to tune a vision pipeline end-to-end; linking linear algebra, color theory, and practical software engineering in a single, competition-ready module.\nContour Pipeline: Detecting and Tracking the Largest Region This pipeline thresholds an image in Y′CrCb to isolate a target color (hot pink by default), cleans up noise with morphology, finds all contours, and then selects and highlights the largest valid bounding rectangle each frame. It also draws borders, overlays contour outlines, and exposes getters for rectangle properties.\nColor Thresholding in Y′CrCb The input RGB frame is first converted into Y′CrCb.\nImgproc.cvtColor(input, mat, Imgproc.COLOR_RGB2YCrCb); We then apply an inRange threshold using two configurable Scalar(y, Cr, Cb) bounds.\nCore.inRange(mat, scalarLowerYCrCb, scalarUpperYCrCb, processed); This produces a binary mask (processed) where pixels within the color range become white ($255$) and others black ($0$).\nMorphological Noise Removal To remove spurious spots and holes, we chain three operations on the binary mask:\nOperation OpenCV Call Purpose Opening morphologyEx(..., MORPH_OPEN, new Mat()) Remove small white speckles. Closing morphologyEx(..., MORPH_CLOSE, new Mat()) Fill small black gaps. Gaussian Blur GaussianBlur(..., new Size(5,15), 0) Smooth mask edges, reduce jitter. The Gaussian Blur formula is given as $$ I'(x,y) = \\sum_{i=-k}^{k}\\sum_{j=-k}^{k} I(x+i,y+j)\\frac{1}{2\\pi\\sigma^2}e^{-\\frac{i^2+j^2}{2\\sigma^2}}. $$These steps yield a cleaner, blur‐smoothed mask ready for contour detection.\nContour Detection and Filtering Contours are extracted from the cleaned mask.\nList\u0026lt;MatOfPoint\u0026gt; contours = new ArrayList\u0026lt;\u0026gt;(); Imgproc.findContours(processed, contours, new Mat(), RETR_LIST, CHAIN_APPROX_SIMPLE); We then loop through each contour and:\nConvert to MatOfPoint2f for bounding‐rectangle calculation. Ignore contours with fewer than 15 points. Compute Rect rect = Imgproc.boundingRect(areaPoints). Only rectangles that\nexceed the previous maxArea, lie fully inside the fractional borders (borderLeftX, borderRightX, etc.), or appear after six frames without a larger detection, are considered the new maxRect.\nMaintaining the Largest Valid Region We track:\nmaxArea: area of the currently chosen rectangle. maxRect: its coordinates and dimensions. loopCounter \u0026amp; pLoopCounter: frame counters to force updates if no larger contour appears. Logic flow:\nIf rect.area() \u0026gt; maxArea and within borders $\\to$ update maxRect. Else if loopCounter - pLoopCounter \u0026gt; 6 $\\to$ accept the latest valid rect anyway. If no contours found $\\to$ reset maxRect to an empty Rect(). This prevents the pipeline from “sticking” on an outdated region.\nDebug Overlays and Display Once maxRect is chosen:\nDraw all contours in blue.\nImgproc.drawContours(input, contours, -1, new Scalar(255, 0, 0)); Highlight the largest rectangle in green (if area() \u0026gt; 500).\nImgproc.rectangle(input, maxRect, new Scalar(0, 255, 0), 2); Draw the user‐configurable border box in hot‐pink.\nImgproc.rectangle(input, borderRect, HOT_PINK, 2); Overlay text showing area and midpoint.\nImgproc.putText(input, \u0026#34;Area: \u0026#34; + getRectArea() + \u0026#34; Mid: \u0026#34; + getRectMidpointXY().x + \u0026#34;,\u0026#34; + getRectMidpointXY().y, new Point(5, height-5), FONT_HERSHEY_SIMPLEX, 0.6, new Scalar(255,255,255), 2); Public Accessors The pipeline exposes synchronized getters so the OpMode can safely read rectangle data.\ngetRectX(), getRectY() getRectWidth(), getRectHeight() getRectArea() getRectMidpointX(), getRectMidpointY(), getRectMidpointXY() getAspectRatio() These let us make decisions (e.g., distance estimation or alignment) based on the detected region.\nKey Insights Thresholding in Y′CrCb decouples brightness (Y′) from chroma, improving color stability. Morphology + blur yields cleaner masks for reliable contour extraction. Frame‐based counters avoid stale detections when contours temporarily disappear. Synchronized getters prevent race conditions between camera thread and main OpMode thread. Configurable borders let you ignore irrelevant regions (e.g., robot arms or field elements). Further Exploration Try adaptive thresholding in HSV for dynamic lighting conditions. Implement contour hierarchy filtering to ignore nested or hole contours. Use a Kalman filter on getRectMidpointXY() for smoother tracking. Experiment with Canny‐based edge detection before contour finding to focus on shape over color. Control Theory for Motion Stabilization and Path Tracking In robotics, precise movement hinges on closed-loop control. The robot measures its state, compares it to a desired state, and computes actuator commands to correct errors. For our FTC team, we focused on PID loops for turret and drive stabilization, plus motion profiling with feedforward terms to smoothly follow trajectories.\nModeling a Feedback Loop A control loop has three core elements:\nSetpoint $r(t)$: the desired value (e.g., target angle or velocity). Process variable $y(t)$: the measured value from sensors (e.g., encoder ticks). Controller $C$: computes command $u(t)$ based on error $e(t) = r(t) - y(t)$. Block diagram:\n┌─────────────────┐ u(t) ┌───────────┐ y(t) ┌───────────┐ r(t)─┤ Controller ├───────────▶ │ Plant ├───────────▶│ Sensor ├─┐ └─────────────────┘ └───────────┘ └───────────┘ ▲ │ │ ┌───────────┐ │ └───────────────────────┤ Comparator├───────────────────────┘ └───────────┘ e(t)=r(t)-y(t) PID Controllers: Theory \u0026amp; Discrete Implementation A PID (Proportional–Integral–Derivative) controller computes: $$ u(t) = K_p \\cdot e(t) + K_i\\int_0^t e(\\tau)d\\tau + K_d\\frac{de(t)}{dt}. $$ Proportional ($ K_p e $) reduces present error. Integral ($ K_i\\int e $) eliminates steady-state offset. Derivative ($ K_d \\,d e/dt $) anticipates future error, adding damping. On a digital system with sampling period $\\Delta t$, we approximate\n$$ \\begin{aligned} e_k \u0026= r_k - y_k,\\\\ I_k \u0026= I_{k-1} + e_k\\,\\Delta t,\\\\ D_k \u0026= \\frac{e_k - e_{k-1}}{\\Delta t},\\\\ u_k \u0026= K_p e_k + K_i I_k + K_d D_k. \\end{aligned} $$ Java Snippet with FTC Dashboard Tuning // In OpMode initialization PIDController pid = new PIDController(0.1, 0.01, 0.005); FtcDashboard dashboard = FtcDashboard.getInstance(); dashboard.setTelemetryTransmissionInterval(50); // Inside loop double fTargetAngle = 90.0; // degrees double fCurrentAngle = turret.getAngle(); double fPower = pid.update(fTargetAngle - fCurrentAngle); turret.setPower(fPower); // Tune live via Dashboard transformers: dashboardTelemetry.addData(\u0026#34;Kp\u0026#34;, pid.getKp()); dashboardTelemetry.addData(\u0026#34;Ki\u0026#34;, pid.getKi()); dashboardTelemetry.addData(\u0026#34;Kd\u0026#34;, pid.getKd()); dashboardTelemetry.update(); Students performed Ziegler–Nichols tuning, which includes the following.\nDisable $I$ and $D$ terms, increase $K_p$ until oscillation at $K_{u}$. Record oscillation period $T_{u}$. Set gains as follows $$ K_p = 0.6 K_u, \\quad K_i = \\frac{1.2 K_u}{T_u}, \\quad K_d = 0.075 K_u T_u. $$ Feedforward \u0026amp; Motion Profiling A pure PID loop can lag when following rapid setpoint changes. We introduced feedforward $$ u(t) = F_v v(t) + F_a a(t) + \\text{PID}(e(t)), $$ where\n$v(t)$ is desired velocity, $a(t)$ is desired acceleration, $F_v$ and $F_a$ are empirically determined constants. Trapezoidal Velocity Profile For smooth starts/stops, we plan a trajectory with three phases.\nAcceleration: $v(t)=a_{\\max}t,\\quad x(t)=\\tfrac12a_{\\max}t^2$ Cruise: $v(t)=v_{\\max},\\quad x(t)=x_1 + v_{\\max}(t-t_1)$ Deceleration: $v(t)=a_{\\max}(T-t),\\quad x(t)=x_2 + v_{\\max}(t-t_2)-\\tfrac12a_{\\max}(t-t_2)^2$ We provide a simple profile generator,\npublic class MotionProfile { double aMax, vMax, xTotal; double t1, t2, T; public MotionProfile(double aMax, double vMax, double xTotal) { this.aMax = aMax; this.vMax = vMax; this.xTotal = xTotal; // Compute transition times t1 = vMax / aMax; double xAccel = 0.5 * aMax * t1 * t1; double xCruise = xTotal - 2 * xAccel; if (xCruise \u0026lt; 0) { // Triangular profile fallback (no cruise) t1 = Math.sqrt(xTotal / aMax); t2 = t1; T = 2 * t1; } else { double tCruise = xCruise / vMax; t2 = t1 + tCruise; T = t2 + t1; } } public MotionState getState(double t) { if (t \u0026lt; t1) { double v = aMax * t; double x = 0.5 * aMax * t * t; double a = aMax; return new MotionState(x, v, a); } else if (t \u0026lt; t2) { double v = vMax; double x = 0.5 * aMax * t1 * t1 + vMax * (t - t1); return new MotionState(x, v, 0.0); } else if (t \u0026lt; T) { double td = t - t2; double v = aMax * (T - t); double x = 0.5 * aMax * t1 * t1 + vMax * (t2 - t1) + vMax * td - 0.5 * aMax * td * td; double a = -aMax; return new MotionState(x, v, a); } else { // End of profile return new MotionState(xTotal, 0.0, 0.0); } } } with motion state\npublic class MotionState { public final double m_fPosition; public final double m_fVelocity; public final double m_fAcceleration; public MotionState(double x, double v, double a) { m_fPosition = x; m_fVelocity = v; m_fAcceleration = a; } } which combined with PID and Feed Forward, we obtain the following.\ndouble t = timer.seconds(); MotionState state = profile.getState(t); double ff = Fv * state.m_fVelocity + Fa * state.m_fAcceleration; double fb = pid.update(state.m_fPosition - currentPosition); motor.setPower(ff + fb); Note that Fv and Fa are the feed forward gains for velocity and acceleration.\nKey Takeaways Closed-loop control contrasts setpoints vs. actuals, correcting via feedback. Discrete PID implementation balances responsiveness (high $K_p$) with stability (appropriate $K_d$). Feedforward complements feedback for dynamic trajectory following. Motion profiles ensure smooth accelerations, reducing wheel slip and power spikes. Hands-on tuning and visualization cement theoretical concepts in a robotics context. Finite State Machines for Autonomous Logic In a FTC autonomous routine, the robot must perform a sequence of discrete, reliable actions (e.g., detect a signal, drive to a location, manipulate game pieces, then park). A Finite State Machine (FSM) provides the clearest, most testable way to encode this behavior.\nCore Concepts of FSMs State: A named phase of operation (e.g., scanning a signal or spinning a carousel). Transition: The condition under which the robot moves from one state to the next (e.g., timer elapsed, sensor reading achieved). Action: The set of commands executed continuously while in a given state (e.g., follow a trajectory or set motor power). A FSM ensures that only one state’s logic runs at a time, and makes transitions explicit, predictable, and easy to debug.\nDefining Our States We use a generic StateMachine\u0026lt;S,T\u0026gt; where\nS = AutonomousFSMState (the set of named states). T = AutonomousFSMTrigger (the events that drive transitions). // Build the state machine once, on initialization. m_StateMachineConfig = new StateMachineConfig\u0026lt;\u0026gt;(); m_StateMachineConfig .configure(IDLE_STATE) .onEntry(this::enterIdleState) .permit(IDLING, EVALUATE_VISION); m_StateMachineConfig .configure(EVALUATE_VISION) .onEntry(this::evaluateVision) .permit(NO_PARK_SIGNAL_FOUND, DEFAULT_PARK) .permit(VALID_PARK_SIGNAL_FOUND, PARK_IN_SIGNAL_ZONE); m_StateMachineConfig .configure(DEFAULT_PARK) .onEntry(this::scheduleDefaultPark); m_StateMachineConfig .configure(PARK_IN_SIGNAL_ZONE) .onEntry(this::scheduleAutonomousParkSignal); m_StateMachine = new StateMachine\u0026lt;\u0026gt;(initialState, m_StateMachineConfig); m_StateMachine.fire(IDLING); IDLE_STATE\nEntry: enterIdleState(). Trigger: after waiting $\\to$ IDLING $\\to$ EVALUATE_VISION. EVALUATE_VISION\nEntry: evaluateVision() Triggers: VALID_PARK_SIGNAL_FOUND $\\to$ PARK_IN_SIGNAL_ZONE. NO_PARK_SIGNAL_FOUND $\\to$ DEFAULT_PARK. DEFAULT_PARK\nEntry: scheduleDefaultPark() (open‐loop strafe). PARK_IN_SIGNAL_ZONE\nEntry: scheduleAutonomousParkSignal() (direction/timing varies by signal). Idle \u0026amp; Vision Evaluation public void enterIdleState() { driveEngine.setZeroPower(); ElapsedTime wait = new ElapsedTime(); while (wait.milliseconds() \u0026lt; 5000 \u0026amp;\u0026amp; getVisionState() == SIGNAL_NONE) { /* busy–wait */ } m_StateMachine.fire(IDLING); } public void evaluateVision() { if (getVisionState() != SIGNAL_NONE) { m_StateMachine.fire(VALID_PARK_SIGNAL_FOUND); } else { m_StateMachine.fire(NO_PARK_SIGNAL_FOUND); } } We zero the drive, then sample vision up to 5 seconds. On detecting a valid park signal (LEFT/MID/RIGHT), we fire VALID_PARK_SIGNAL_FOUND; otherwise we fallback to NO_PARK_SIGNAL_FOUND. Scheduling Default Park (No Signal) public void scheduleDefaultPark() { DriveSubsystem drive = getComponent(DriveSubsystem.class); ElapsedTime t = new ElapsedTime(); // Strafe direction depends on alliance Vector3d dir = Defines.BLUE_ALLIANCE ? new Vector3d( 1, 0, 0) : new Vector3d(-1, 0, 0); while (t.milliseconds() \u0026lt; 1500) { drive.setMovementVector(dir); drive.updateMovementStateDifferentialTrigRC(); } drive.stop(); // zero vector \u0026amp; update } Open‐loop timing: 1.5 seconds at fixed power $\\to$ distance $\\approx$ Vstrafe×1.5. updateMovementStateDifferentialTrigRC() applies the Vector3d to wheel powers via our mechanum kinematics $$ \\begin{aligned} P_{FL} \u0026= v_y + v_x + \\omega L,\\\\ P_{FR} \u0026= v_y - v_x - \\omega L,\\\\ P_{BL} \u0026= v_y - v_x + \\omega L,\\\\ P_{BR} \u0026= v_y + v_x - \\omega L. \\end{aligned} $$ Scheduling Park in Signal Zone For each detected signal, we chain time‐based moves\nswitch (signal) { case SIGNAL_ONE: // Strafe left 1.38 s, then forward 1.3 s moveVector(new Vector3d(-1,0,0), 1380); moveVector(new Vector3d( 0,1,0), 1300); break; case SIGNAL_TWO: // Forward only moveVector(new Vector3d(0,1,0), 1500); break; case SIGNAL_THREE: // Strafe right then forward moveVector(new Vector3d(1,0,0), 1050); moveVector(new Vector3d(0,1,0), 1150); break; } Helper method:\nprivate void moveVector(Vector3d v, long ms) { ElapsedTime t = new ElapsedTime(); while (t.milliseconds() \u0026lt; ms) { drive.setMovementVector(v); drive.updateMovementStateDifferentialTrigRC(); } } Pros: Simple, no trajectory library needed. Cons: Distance = Power×Time $\\to$ varies with battery level, friction, manufacturing tolerances. Transitioning to Trajectories We also included commented‐out methods using Road Runner.\npublic void scheduleAutonomousSignalTrajectory() { TrajectorySequence traj = driveEngine.trajectorySequenceBuilder(...) .strafeLeft(24 * 2) // 48 in .forward(24 * 4) // 96 in .build(); schedule(new FollowTrajectorySequenceCommand(driveEngine, traj)); } Metric distance (inches) replaces time loops. Closed‐loop control (feedback + motion profiling) $\\to$ repeatable paths under varying conditions. Lessons \u0026amp; Improvements State Machine Clarity\nExplicit states and triggers keep autonomous logic maintainable and testable. Open-Loop vs. Closed-Loop\nTime‐based moves are easy but sensitive to battery and friction. Trajectories yield consistent distances $$ d = \\int_0^T v(t) dt, $$ with feedforward-feedback control for velocity tracking. Non-Blocking FSM\nCurrent enterIdleState() busy-waits; better to schedule a timer event and return, letting the main loop continue calling drive.update(). Extensible Architecture\nNew states (e.g., “deliver freight,” “cycle blocks”) slot in without rewriting existing transitions. Error or retry states can handle vision failures or mechanical stalls. By combining a strongly typed FSM with vector‐based kinematics or trajectory sequences, our Power Play autonomous routine remained robust, readable, and adaptable. This empowered the team to focus on strategy rather than tangled if-else blocks.\nDrive Subsystem The Drive Subsystem encapsulates every aspect of robot locomotion. It bridges high-level commands and the low-level motor outputs, switching seamlessly between multiple control schemes, handling kinematics, and providing rich telemetry for tuning and debugging.\nCore Components DriveEngine: Interfaces with hardware and, when used, Road Runner to handle pose estimation, trajectory following, and raw power outputs.\nDefines.DriveMode: Selects among ROBOT_CENTRIC_HOLONOMIC, ROBOT_CENTRIC_MECANUM, FIELD_CENTRIC_GAMEPAD, and FIELD_CENTRIC_IMU modes.\nVector3d: Holds desired translation $(x, y)$ and rotation $(z)$ inputs in the range $[-1, 1]$.\nTelemetry: Streams real-time data to the driver station for monitoring stick inputs, computed powers, heading, and offsets.\ndouble: Zero reference for IMU-based field centric; updated via setHeading() to recalibrate on the fly.\nDrive Mode Dispatch Every update interval (FTC loop), periodic() routes control to the active mode.\nswitch (m_driveMode) { case ROBOT_CENTRIC_HOLONOMIC: updateMovementStateDifferentialRC(); break; case ROBOT_CENTRIC_MECANUM: updateMovementStateDifferentialTrigRC(); break; case FIELD_CENTRIC_GAMEPAD: updateMovementStateGamepadFC(); break; case FIELD_CENTRIC_IMU: updateMovementStateIMUFC(); break; default: m_DriveEngine.setZeroPower(); m_Telemetry.addLine(\u0026#34;\u0026gt; Unknown drive mode\u0026#34;); } This structure isolates each kinematic model, keeping logic clear and extensible.\nMovement Vector \u0026amp; Heading Prior to periodic(), commands call:\nsetMovementVector(new Vector3d(x, y, rotation)); setHeading(desiredZeroOffsetRadians); x, y range from $-1$ to $1$, representing strafe and forward/backward. rotation sends a spin command about the robot’s center. Heading offset allows zeroing the IMU reference mid-match. Kinematic Models Mode Inputs Computation Use Case ROBOT_CENTRIC_HOLONOMIC $(x, y, z)$ Linear combination: each wheel receives $y \\pm x \\pm z$, scaled to avoid saturation. Simple omni or tank fallback ROBOT_CENTRIC_MECANUM $(x, y, z)$ Trig-based: rotate vector by 45°, scale by vector length, inject rotation, normalize, then apply coefficient. Precise mecanum movement FIELD_CENTRIC_GAMEPAD $(x, y, z)$ + Pose2d Rotate raw gamepad vector by –robotHeading from Road Runner; apply weighted drive power for closed-loop profiling. Responsive, trajectory-ready FIELD_CENTRIC_IMU $(x, y, z)$ + IMU offset Cube small inputs for finesse, convert to field frame via IMU offset, then reuse trig-mecanum pipeline. Straight-line precision \u0026amp; resets Differential Holonomic Scale $x$ by $1.1$ to counter imperfect strafing. Compute raw powers: $$ \\begin{aligned} LF \u0026= y + x + z \\\\ LR \u0026= y - x + z \\\\ RF \u0026= y - x - z \\\\ RR \u0026= y + x - z \\end{aligned} $$ Normalize: $$ \\text{scale} = \\max\\left(|LF|, |LR|, |RF|, |RR|, 1\\right). $$ Apply $\\texttt{DRIVE\\textunderscore COEFFICIENT}$ and send to motors: $$ \\text{Power}_i = \\frac{P_i}{\\text{scale}} \\cdot \\texttt{DRIVE\\textunderscore COEFFICIENT}. $$ Trigonometric Mecanum Compute vector length and angle: $$ \\text{length} = \\sqrt{x^2 + y^2}, \\quad \\text{angle} = \\tan^{-1}\\left(\\frac{y}{x}\\right) - \\frac{\\pi}{4} $$ Map to wheel powers via cosine and sine: $$ \\begin{aligned} LF \u0026= \\text{length} \\cdot \\cos(\\text{angle}) \\\\ LR \u0026= \\text{length} \\cdot \\sin(\\text{angle}) \\\\ RF \u0026= \\text{length} \\cdot \\sin(\\text{angle}) \\\\ RR \u0026= \\text{length} \\cdot \\cos(\\text{angle}) \\end{aligned} $$ Inject rotation, renormalize, scale, and output. This yields smooth strafing with constant speed in any heading.\nField-Centric with Gamepad Fetch current pose from $\\texttt{DriveEngine}$. Rotate gamepad input by negative heading: $$ \\begin{pmatrix} x' \\\\ y' \\end{pmatrix} = \\begin{pmatrix} \\cos(-\\theta) \u0026 -\\sin(-\\theta) \\\\ \\sin(-\\theta) \u0026 \\cos(-\\theta) \\end{pmatrix} \\begin{pmatrix} x \\\\ y \\end{pmatrix} $$ Call the following for built-in velocity control: $$ \\texttt{setWeightedDrivePower(Pose2d}(x', y', \\omega)\\texttt{)}. $$ This leverages Road Runner’s closed-loop profiling under driver control.\nIMU-Based Field-Centric Dead-zone and cube raw inputs for fine control. Convert to field frame via Extensions.toFieldRelative(pose, heading). Reuse trig-mecanum pipeline to compute motor powers. Telemetry shows both raw and computed values, along with offset and heading in degrees. Telemetry \u0026amp; Debugging Every mode floods driver station with the following;\nStick positions. Computed vector length \u0026amp; angle. Rotation command. Current $x$, $y$, and heading (in field-centric). Offset for on-the-fly recalibration. This immediate feedback accelerates tuning coefficients and diagnosing drift.\nBy centralizing all drive logic in this subsystem, the team gains a modular, testable foundation. Whether strafing across the warehouse or executing tight turns on the field, the Drive Subsystem adapts dynamically with predictable locomotion.\nSoftware Engineering and Best Practices Building a competition-grade robot under tight deadlines demands more than clever algorithms, it requires a robust software foundation. We now explore the principles and processes that kept our FTC team’s codebase maintainable, testable, and scalable.\nModular Design \u0026amp; Composition Rather than monolithic OpModes, we decomposed functionality into reusable components.\nEntity-Component Approach Each subsystem (Drive, Lift, Vision) implements a clear interface. OpModes assemble components like building blocks. Composition over Inheritance Prefer injecting a DriveEngine into DriveSubsystem rather than subclassing. Example: public class LiftSubsystem { private final MotorController liftMotor; public LiftSubsystem(MotorController motor) { this.liftMotor = motor; } public void moveTo(double position) { /* ... */ } } Single Responsibility Principle Each class has one well-defined role (e.g., ShippingElementPipeline only handles color detection). Encourages small, focused units that are easy to test. Version Control \u0026amp; Collaboration A structured Git workflow kept eight students in sync.\nBranch-Per-Feature New sensor code or tuning scripts lived in their own branches. Merges occurred only after peer review. Pull Requests with Templates Enforce a checklist: “Does this compile? Unit tests pass? Documentation updated?” Fork + Upstream Model Students forked the central repo, submitted pull requests, and practiced resolving conflicts. Testing \u0026amp; Simulation With limited robot time, we built confidence in software off-robot.\nUnit Tests for Utilities Vector math, PID calculations, and coordinate transforms can be tested in JUnit. Software-In-The-Loop (SITL) OpenCV pipelines can be tested on prerecorded videos. State-machine transitions can be validated with mock vision results. Hardware Abstraction Layer DriveEngine interface hides actual motors, allowing a SimulatedDriveEngine to drive a virtual robot. Continuous Integration \u0026amp; Deployment Automating builds can catch errors early and ensure consistency.\nGitHub Actions Pipeline On every push: Compile Java code. Run unit tests. Lint with Checkstyle. Artifact Publishing Successful builds on Development branch published downloadable .apk to a private release channel for testers. Automatic Versioning Git tags determined build numbers, embedding version info in logs and telemetry. Documentation \u0026amp; Code Quality Clear docs turns novices into contributors.\nIn-Line Javadoc Every public class and method included a concise @param/@return description. Markdown Tech-Notes High-level architecture diagrams and setup instructions can live in a docs/ directory. Style Guide Enforcement Consistent naming: m_ prefix for members, CAMEL_CASE for constants. Pre-commit hooks formatted code with custom Java Format. Code Reviews \u0026amp; Pair Programming Peer feedback and collaboration accelerated learning.\nScheduled Review Sessions Twice weekly, students demoed features and walked through diffs together. Pair Programming Rotations Rotated driver/navigator roles; spread domain knowledge and caught subtle bugs early. Review Checklist Does it handle edge cases? Are magic numbers replaced with named constants? Is the algorithm’s complexity appropriate for the Control Hub’s CPU? Dependency Management \u0026amp; Reuse Reinventing the wheel wastes precious build time.\nThird-Party Libraries EasyOpenCV for vision, FTCLib commands for scheduling, Road Runner for trajectory planning. Internal Utility Modules Custom math library housed common vector and PID classes, which are shared across seasons. Version Pinning Fixed library versions in build.gradle ensured reproducible builds and avoided “works on my machine” issues. By embedding appropriate software practices (e.g., modularity, automated testing, CI/CD, code reviews, and thorough documentation) we empowered the team to iterate rapidly, diagnose issues confidently, and maintain a rock-solid codebase under competition pressure.\nDevelopment Tools Android Studio. An Integrated Development Environment (IDE) for Android app development. Git. A free and open source distributed version control system. Fork. A user-friendly Git client with free evaluation (permanent as of writing). GitHub. A cloud-based Git repository hosting service. Checkout the source code on GitHub.\n","permalink":"/blog/ftc-robotics-mentoring/","section":"blog","summary":"During the FTC 2022–2023 Power Play season, I mentored a brand-new all-girls robotics team (Team 21595) through hands-on workshops in linear algebra, control theory, computer vision, and collaborative software engineering. Over eight months, they learned to build a competition-ready robot, tune PID loops, and deploy vision pipelines, all while mastering Git workflows and Java subsystems.","tags":["Blog","FTC","Mentorship","Linear Algebra","Control Systems","Computer Vision","Finite State Machine","Software Engineering"],"title":"Empowering an All-Girls FTC Team: Math, Code \u0026 Robotics in Power Play"},{"content":"Overview The XII Geospatial Platform enables interactive exploration of economic activity in Syria by rendering night-time satellite light captures at 4K resolution. Users can scrub through months and years, blend temporal data, and export high-res screenshots, all at real-time frame rates thanks to a GPU-optimized format and custom game engine core.\nMotivation Night lights derived from satellite imagery have become a trusted proxy for measuring local economic dynamics, urban growth, and infrastructure development. Yet existing GIS tools either lack real-time interactivity or force repeated on-the-fly recompression. By pre-compiling light captures into a GPU-friendly texture format and leveraging our in-house engine, the platform lets analysts fluidly compare temporal snapshots without I/O stalls or frame drops.\nKey Features Pre-compiled 4K tiles in a custom GPU-optimized data format. Temporal blending slider (between previous and next month). Interactive camera controls: pan, zoom, rotate, and tilt. On-screen statistics: FPS, memory usage, and profiling data. Export high-resolution screenshots via F12. In-engine debug console (F2) with auto-complete commands. Supports D3D11 today; experimental D3D12 and Vulkan backends in development. Architecture Data Ingestion Offline tool converts raw GeoTIFFs into tiled textures. Metadata database indexes region, year, month. Rendering Pipeline Custom C++/TypeScript engine orchestrates scene graph. Deferred shading for terrain base, additive pass for night lights. Shader modules implement intensity scaling and color mapping. UI \u0026amp; Controls TypeScript-driven UI panel for date selection and blending. IPC channel to external profilers and scripting consoles. Resource Management Streaming loader fetches only visible tiles. LRU cache for GPU textures to maintain 60+ FPS. Data Sources VIIRS Nighttime Lights (Volcanic Radiance). DMSP-OLS Historical Night Lights. Custom preprocessing to normalize radiance values per tile. Usage Guide git clone https://github.com/theoeriata/XIIGeospatialPlatform.git cd XIIGeospatialPlatform/Binaries/GeospatialPlatform ./Launch\\ MainScene.bat Use the settings panel to choose year/month. Adjust weighting slider to blend temporally between captures. F12: capture screenshot. F2: open debug console (tab-autocomplete). F5: toggle FPS counter. Note: currently supported on Windows only; experimental D3D12 and Vulkan builds exist but lack full feature parity and unit tests.\nPerformance Benchmarks Resolution Average FPS GPU Memory Footprint 1920×1080 120 FPS 1.5 GB 2560×1440 90 FPS 2.2 GB 3840×2160 60 FPS 4.8 GB Tiles Streamed Frame Drops (\u0026lt;1 ms stall) 16 0 32 2 per 1,000 frames 64 5 per 1,000 frames Testing \u0026amp; Quality Over 100 unit tests (C++ engine + TypeScript UI). Fuzz testing for tile loader and blending logic. Continuous integration on Windows with graphics regression checks. Profiling harness to catch GPU memory leaks. Installation No external dependencies beyond DirectX 11 runtime (or Vulkan SDK for experimental builds).\nClone the repo. Run ./Launch MainScene.bat in the Binaries/GeospatialPlatform folder. Allow firewall access for IPC and profiling channels. Contribution Guidelines Fork the repo and create feature branches. Adhere to the code style (see .clang-format and tslint.json). Write or update unit tests for any new feature. Open pull requests against Development branch. Expect feedback within 48 hours. Future Roadmap Publish on Windows Store and Steam for easy installation. Complete and stabilize D3D12/Vulkan renderers. Add support for global datasets beyond Syria. Integrate time-series charting alongside map view. Expose plugin API for custom shaders and analysis scripts. License This project is released under the MIT License. See LICENSE for full terms.\n","permalink":"/projects/xii-geospatial-platform/","section":"projects","summary":"An interactive 4K-resolution viewer powered by a custom game engine to explore Syria’s economic activity through night lights imagery.","tags":["Project","Geospatial","Visualization","Night Lights","Game Engine"],"title":"XII Geospatial Platform: Visualizing Economic Activity in Syria via Night Lights"},{"content":"Overview XII FlyBird is an easy-to-play endless flyer where you rack up points by soaring through procedurally generated gateways. Built in C++ with TypeScript scripting, FlyBird balances lightweight runtime performance with flexible in-game tuning via console variables.\nMotivation Designing a small-scale game often leads straight to “just get it working.” FlyBird’s goal was different: I wanted an endless runner that felt polished, ran smoothly on modest hardware, and supported live tweaking of gameplay parameters without recompiling. By combining low-level C++ core systems and high-level TypeScript hooks, I aimed to:\nAchieve a smooth 60 FPS on integrated GPUs. Expose key gameplay parameters (speed, gap size, obstacle density) through CVars. Build a simple finite-state animation framework for bird sprites. Recycle obstacles efficiently to keep memory usage constant. Core Features Procedural obstacle generation that guarantees random yet fair gate placement. Console vars (CVars) for real-time adjustment of player impulse, obstacle spacing, and camera shake. State-based animation driven by a finite state machine for smooth transitions. Low-level physics impulses for natural bird “flight” and gravity behavior. High-level scripting in TypeScript for obstacle behaviors and game flow control. Architecture FlyBird is split into three layers:\nEngine Core (C++): Memory-pooled object system for reuse of obstacle and particle instances Physics impulses for forward motion and flapping CVar console subsystem for live-tuning of floats, ints, and booleans Scripting Layer (TypeScript): Defines obstacle spawn logic and random height ranges Manages game states: Menu → Playing → GameOver Binds CVar changes to in-game UI overlays Data \u0026amp; Assets: Prefabs for bird, gates, and background elements Scriptable configuration in RuntimeConfigs for default CVar values Editor plugins for rapid scene iteration Procedural Pipeline The obstacle pipeline follows this flow:\nSeed RNG → Compute Next Gate Position → Instantiate or Recycle Obstacle → Apply Random Z-Offset → Begin Movement Toward Player Key parameters exposed as CVars include:\nParameter Default Description cvar_PlayerSpeed 300.0 Forward impulse magnitude. cvar_PlayerFlapImpulse 450.0 Upward impulse when “fly” input is active. cvar_ObstacleSpacing 75.0 Distance between consecutive gates. cvar_ObstacleVerticalRange 60.0 Max vertical displacement for gate openings. This approach ensures obstacles loop seamlessly and maintains constant memory overhead.\nGameplay Mechanics Players control the bird with a simple “fly” key or button:\nHold “Fly” to apply upward impulse, counteracting gravity. Release to let gravity descend the bird naturally. Each passed gate awards one point; colliding ends the run. Dynamic difficulty scaling: after every 10 points, cvar_ObstacleSpacing decreases by 5%. Performance \u0026amp; Optimization Through targeted optimizations, FlyBird consistently hits 60 FPS on entry-level hardware:\nObject pooling avoids frequent allocations/deallocations. Minimal per-frame TypeScript invocations; heavy logic runs natively. Simple shaders for background parallax and gate highlights. Batch-rendering of sprites to reduce draw calls. Frame times measured over 10 runs:\nHardware Average FPS Peak Memory Footprint Intel UHD Graphics 60 45 MB Integrated AMD GPU 60 50 MB Mid-range dGPU (GTX) 144 75 MB Technical Challenges Precision Loss Over Time: Early pooling logic led to floating-point drift. Solved by resetting object positions relative to a fixed origin every 1 km traveled. Live-Tuning Overhead: Unbounded CVar updates caused hitches. Introduced a “dirty” flag so changes apply at frame boundaries. State-Sync Between C++/TS: Ensuring that script-driven obstacle logic and core physics remained in lock-step required a single “tick” dispatcher. Lessons Learned Embedding a lightweight scripting layer vastly improved iteration speed compared to pure C++ builds. Procedural generation works best when parameters are exposed early; waiting to tune in code wastes playtesting time. Consistent memory usage is critical for endless games; object pools keep the GC (or OS allocator) from becoming a bottleneck. Future Work Add shader-based post-processing (motion blur, depth fog) for visual polish. Implement online leaderboards with networked score submission. Mobile port with touch controls and adjustable CVar presets. VR/AR adaptation for immersive first-person flying. ","permalink":"/projects/xii-flybird/","section":"projects","summary":"A lightweight, infinitely replayable flying game featuring procedural obstacle generation, optimized effects, and console-variable-driven tuning.","tags":["Project","Game Development","Procedural Generation","C++","TypeScript"],"title":"XII FlyBird"},{"content":"Introduction This post documents the programming process of an advanced robot for the FTC 2021-2022 (Freight Frenzy) season.\nDrive Control Scheme Drivers always take a liking to intuitive and easy to learn controls that are able to be mastered without sweating too much. As a result of this, one of the core values that were observed when designing the drive control scheme was that our driver should be able to get comfortable with the drive controls within the first 30 minutes of drive testing. On each iteration of the robot, the basic movement for the robot remained relatively the same, however, operations which were done on Assessors (which includes sensors and utility tools like the intake system) did vary between each iteration of the robot because we wanted to match the controls of the robot to how we perceived the robot should be controlled.\nAs a policy on our team, we do embrace having multiple options and modes which can be changed when building a release version of our program to ensure that the driver can choose the best drive mode that suits their control scheme. There are currently 4 drive modes which are suited to Holonomic (X) Drives and Mechanism Drives which are listed below;\nROBOT_CENTRIC_HOLONOMIC: This drive mode is the most suitable for all Holonomic and Mechanism Drives because it relies on the raw input values from the gamepad and can easily be added to any robot without any dependencies. ROBOT_CENTRIC_MECANUM: This drive mode is mode is an alternative to the Robot Centric Drive. Instead of manually taking each individual gamepad actions to determine the net velocity of the robot, we use the idea of the Unit Circle to calculate the desired translation of the robot which is automatically clamped to the robot’s max velocity in any arbitrary direction, given that the x and y components are complimentary to each other. The benefit to this control scheme is that it fixes the imperfect strafing present in the Robot Centric control scheme. FIELD_CENTRIC_GAMEPAD: This mode of control is quite different from the first two as with this mode, the robot’s translation is decoupled from the robot’s rotation. We assume a local rotation of the robot while translating the robot relative to the world coordinates. This mode of control however, requires that the robot’s gyroscope is enabled. This mode of control is possible by projecting the velocity vector of the robot onto a plane to calculate is components in 3-Dimensional Space. FIELD_CENTRIC_IMU: This is an extension of the Field Centric Drive which uses the Intelligent Drive Assist (IDA) to calculate the robot’s translation based on a heading offset internally. It is tightly coupled with the Intelligent Drive Assist module which regulates the control velocity of the robot’s wheels. By default we use the ROBOT_CENTRIC_MECANUM as it is most suitable in most cases.\nControl Accessors Control Accessors are modules which allow the robot to perform certain operations which can be manually triggered by the driver or called from the program automatically. Accessors are essentially subsystems which are attached physically or virtually to the robot to enable us programmers to compose an OpMode based on its use case. For instance, the ability to sense which level of the shipping hub to place a block may not be needed in a TeleOp drive mode where the driver is in control of the robot but is required to know which level of the shipping hub to deliver freight during the autonomous period. This form of composition is possible utilizing the concept of an Entity Component System (ECS) which will be covered in this document.\nThere are two categories in which accessors are placed. One is a static accessor which may or may not be internalized (meaning they are declared and setup during compile time) and an update accessor. Static accessors are the subsystems themselves which are independent of the OpMode and can be added to any class that extends the OpModeBase class. The function of a static accessor is to provide an interface which can be used to manage systems such as camera detection and streaming, Turret and Lift control, Robot Heading Management, Color and Distance Sensors. An update accessor, however, is primarily used as a “read and update” system which is useful in our TeleOp modes where the driver’s input controls the robot’s actions. This is used to update the motor power levels that are sent to the wheels to update robot’s current transform (translation and rotation) in the driver-controlled period. It is also used to update and control modules such as the Intake, Lift, Carousel, and Speed Control systems.\nHardware and Optimizations It is important to recognize that the control hub is powerful in its design but is prone to overheating when tasked with operations that are take a lot of processing power. When running the robot through unit tests for long periods of time, we noticed that the control hub overheats when running camera stream and sending telemetry data over to the dashboard as well as running tasks which are rarely needed to run for the duration of the whole program.\nWe started by manually handling the bulk reading cache on all connected hubs which are recognized as Lynx Modules as a way of saving cycle times. Next, we proceeded to build OpModes that are suited to perform specialized operations, such as a simple autonomous program that parks, one that handles the carousel then park, and a final autonomous program that performs every operation that the robot is capable of. Finally, we wrote our own math library to be able to perform fast math operations.\nTo be able to optimize system calls further, there are specialized commands that can be ran through subsystems that may be called asynchronously without affecting the main loop of the program. Such commands are embedded in their own modules and do not affect the performance of the subsystems.\nIntelligent Drive Assist (IDA) The Intelligent Drive Assist (IDA) is a special module that is not a subsystem, but functions like one. It is used mainly in autonomous programs to plan the robot’s path during the autonomous periods, perform robot localization, cruise control, and optimize velocity and acceleration control. To perform some of these tasks that are difficult but not impossible to implement on our own we utilize a library called Road Runner as a dependency. Road Runner is a motion planning library that allows complex path generation and tracking while maintaining velocity and acceleration control. IDA is built on top of this framework to ensure that we harness the capabilities of Road Runner which provides a tested foundation of which we can focus on expansion of the system instead of how to create one from the ground up. IDA supports asynchronous processing and intrinsic parallelization. The term “Intrinsic Parallelization” covers two concepts, one is that IDA by default performs operations on a separate thread when needed. Secondly, it means that IDA can be used on any robot that performs its movement in a Holonomic or Tank manner, whether traditional or not. In any OpMode, IDA presents an Application – Engine relationship, where IDA is the engine, and the OpMode is the application. The application holds one instance of the engine and calls methods to perform operations that are related to the robot’s locomotion and holds references to all added components.\nVision The robots vision module is a key subsystem in the autonomous program, and it is used to analyze the field usually to detect objects based on color when using the OpenCV module and the shape when using Tensor Flow through the Vuforia variant. The Vision module is wrapped in a subsystem for easy initialization and integration into any OpMode. This season, we acknowledged the OpenCV module to be better suited to accomplish the vision detection requirements. This is because we only needed a unique color to identify our team’s shipping element which was made easier through our pipeline state tracking system. The currently functional pipelines are listed below.\nContour Based Pipeline: This pipeline uses a method of processing the camera’s image buffer to synthesize any color in the range of the color of our shipping hub element. This is possible utilizing a method that is similar to that of implementing a bloom effect in photoshop, where one would blur and the input image using a method of Gaussian Blur multiple times to get a smooth transition between all color values present in the image and simulate light bleed. We then process this pass to find any contours that match our expected output. The final stage is to use a method of screen space partitioning to determine which quadrant (I, II or III) the contour which describes the bounding shape of detected object lies. Duck Contour Pipeline: This pipeline is made specifically to be able to detect the yellow duck on the field. It extends the contour pipeline and can be used to detect anything yellow in general. Shipping Element Pipeline: This pipeline extends the contour-based pipeline and is setup to be able to adapt to any shipping element changes without affecting the base pipeline. It extends the functionality of the base contour pipeline by using a more optimized method of finding which color regions hold ranges that are within the specifications of the Shipping Element and most of all, is less verbose and easier to read. Entity Component System (ECS) The Entity Component System approach is used in high performance applications such as games to be cache friendly on the CPU when performing updates on game objects. By ensuring that all subsystems are considered as components which can be added to OpModes which are considered as the Entity, we are able to create and add any arbitrary component to an Entity and update each individual entity without incurring any performance issues. This is so because when components are being accessed to be updated, they are contiguous (close together) in memory. If we were to go full object oriented instead of data oriented in OpMode composition, the objects are not guaranteed to be close together in memory so the CPU would have to load different stacks to be able to access each object. This architecture is vital to ensure that we can build and rebuild OpModes for this season efficiently and the next to little API rewrites unless they are required. It is worth noting that our ECS implementation is made impure because our components do have update functions as is required.\nAutonomous The main autonomous goal we aim to achieve is to detect the shipping hub level to which freight would be delivered. This would be scanned during the robot initialization phase to save processing load and set the desired hub level before the robot leaves it’s desired scan position. Next is to maneuver to the Carousel spin the platform to deliver the pre-placed duck. Then the robot makes its way to the shipping hub to place the pre-placed freight at the desired hub level. At this stage, the robot would make its way into the warehouse to park. Then we analyze the total elapsed time since the start of the program to see if the robot is able to cycle picking up a freight and delivering it to the topmost shipping hub level and ﬁnally come to rest in the warehouse.\nAlgorithms and Utilities Proportional Integral Derive (PID) Controller: This is our custom control algorithm implementation that is used to derive correction values when dealing with path stabilization. Fast Math \u0026amp; Extended Math Library: This is written to ensure fast math operations and advanced algorithms. It implements support for vector, axis angle and other useful mathematics operations. As an extension, support for interpolating and extrapolating curves were also implemented. The library also consists of our own Linear Algebra classes so as not to depend on existing classes provided by the third party libraries. Meep Path Visualizer: This is used for remote path planning, to be able to give a theoretical estimate of the time required to perform certain autonomous programs and get a theoretical overview of what is possible when drafting autonomous paths. Robot Defines: This is a class that houses all control data needed for the robot and is reflected using the FTC Dashboard API to be able to live edit variables and enumerations for quick testing. Encoder Utilities: To better interface with motor encoders and make less calculation repetitions, we created an encoder utility class that takes in a reference to any DcMotorEx object on initialization, which helps us perform actions such as calculating offsets and velocity control for the motors. Tensor Flow: We use Tensor Flow with the Vuforia Module to perform object detection when need to perform object detection based on the composition of the object, instead of just the color alone. This method uses machine learning to generate the recognition data needed to get matches for our game objects. FTC Dashboard: We use the FTC Dashboard’s reflection system to setup variables that can be reflected / modified during runtime to allow quick iterations of control logic before hard-writing them before compile time. We also use this module to perform other functions such as trajectory analysis, acceleration profiling and other utilities such as saving the capture data to disk, and running OpModes. ","permalink":"/blog/ftc-robotics-programming/","section":"blog","summary":"A guide to programming an FTC Freight Frenzy robot, covering drive control, path planning, vision pipelines, ECS architecture, and performance optimization.","tags":["Blog","FTC","Robot Programming","OpenCV","TensorFlow","Control Theory","Holonomic Drive","Mecanum Drive","Field-Centric Control","Entity Component System","Control Hub Optimization","Vision Pipeline","FTC Dashboard","Motion Planning","Java","FIRST Tech Challenge","Robot Architecture"],"title":"Mastering FTC Freight Frenzy: Advanced Robot Programming Techniques"},{"content":"Introduction I wanted a single hub to present my projects, blog posts, talks, and CV in a cohesive, high-performance site. Instead of hand-coding each page or relying on a heavyweight CMS, I chose Hugo, a static site generator known for its speed and flexible content model. Over the course of six weeks, I built, iterated, and deployed my personal website at https://theophiluse.xiitechnologies.com.\nMotivation and Goals I set out with three main objectives:\nDeliver near-instant page loads for visitors worldwide. Maintain a modular content structure so I can add new projects or posts without touching templates. Automate build and deploy steps for every content change via GitHub and Netlify. Balancing performance, ease of maintenance, and scalability guided every decision.\nTech Stack Component Technology Purpose Static Site Generator Hugo (v0.94.2) Markdown → HTML, content organization Theme PaperMod Clean, responsive UI with dark/light modes Styling Custom CSS + SCSS Overrides for typography, layout tweaks Hosting \u0026amp; CI/CD Netlify Automated builds, previews, global CDN Version Control GitHub Source code, content, issues, pull requests Analytics GoatCounter Privacy-focused visitor tracking Project Structure Hugo’s directory layout keeps content, templates, and assets neatly separated:\narchetypes/ Default front matter templates for new posts, projects, talks, etc.\ncontent/\nblog/: Markdown posts with YAML front matter projects/: Detailed project pages with galleries and links talks/: Slides, abstracts, and embedded recordings teaching/: Course notes and materials layouts/ Custom templates overriding PaperMod where needed (footer, header).\nstatic/ Favicon, images, PDFs (CV, papers).\nassets/ SCSS partials and custom CSS.\nconfig.yml Global site config: menus, params, theme settings.\nKey Features Responsive, Mobile-First Design: Every page adapts fluidly to phone, tablet, and desktop viewport sizes.\nModular Content with Taxonomies: I categorize posts by tags like “Hugo”, “JavaScript”, “Teaching”, and “FTC Robotics.” Site search and tag pages are auto-generated.\nDark/Light Mode Toggle: Visitors can switch themes; system preference is detected on first visit.\nInstant Search: An Algolia-style search powered by Lunr.js for client-side indexing of titles and summaries.\nContinuous Deployment: A push to any branch in GitHub triggers Netlify to build, test, and deploy (previews on pull requests included).\nImplementation Highlights Customizing PaperMod I forked the PaperMod theme and:\nAdjusted the header to include quick links to my CV and GitHub. Overrode the footer to display current year and site license. Added a “Featured Project” section on the homepage via a custom partial. Asset Pipeline Using Hugo Pipes, I compile SCSS into minified CSS, auto-generate image thumbnails, and fingerprint assets for long-term caching.\n# config.yml snippet resources: scss: - src: \u0026#34;assets/scss/main.scss\u0026#34; output: \u0026#34;css/styles.min.css\u0026#34; Optimizations Enabled HTTP/2 push for CSS/JS bundles. Inline critical-path CSS for above-the-fold content. Lazy load below-the-fold images via loading=\u0026quot;lazy\u0026quot;. Challenges \u0026amp; Solutions Challenge: Managing PDF downloads (CV, papers) without cluttering the repo.\nSolution: Keep PDFs in static/files/, reference via shortcodes in Markdown. Challenge: Ensuring code snippet styling matched the theme.\nSolution: Integrated Prism.js with custom PaperMod styles for consistency. Challenge: Balancing YAML front-matter verbosity with maintainability.\nSolution: Created archetypes for each content type, pre-populating common fields. Results \u0026amp; Metrics Lighthouse Performance Score: 99/100 on desktop, 95/100 on mobile. First Contentful Paint under 0.8s on average. Netlify build times: \u0026lt;20 seconds for full site. CI/CD uptime: 100% (no failed deployments in last 6 months). Lessons Learned Static site generators shine when content updates are frequent but structure is consistent. Automating asset pipelines within Hugo significantly reduces dependencies. Abstracting common front-matter into archetypes saves hours for every new post or project. Future Improvements Integrate a headless CMS (e.g., Netlify CMS) for non-tech contributors to add content. Add multilingual support for langue française translations of key posts. Explore WebP image generation for even better performance. Related Links Hugo Documentation: https://gohugo.io/documentation/ PaperMod Theme: https://github.com/adityatelange/hugo-PaperMod Netlify: https://www.netlify.com ","permalink":"/projects/personal-website/","section":"projects","summary":"A fast, SEO-friendly personal website built with Hugo and the PaperMod theme to showcase projects, blog posts, talks, and CV.","tags":["Project","Hugo","Static Site","Portfolio"],"title":"Personal Website"},{"content":"Overview PowerPlay (FTC 2022-2023) tasks robots with stacking cones onto grid junctions of four heights, creating circuits, and parking in randomized signal zones. We built on the official FTC SDK, adding:\nA custom TeamCode module for hardware mappings, vision, and game logic. Road Runner powered mecanum trajectories and MeepMeep simulation. AprilTag, contour, and shipping element detection via EasyOpenCV for autonomous signal recognition. A state-machine framework to handle stack cycles, recoveries, and parking. This system drives both our multi-stage autonomous routine and a high-throughput TeleOp control scheme.\nMotivation Key PowerPlay challenges:\nRapid, repeatable cone pickup and stacking at ground, low, medium, and high junctions. Autonomous parking based on a hidden signal cone for bonus points. Precise navigation through tight field corridors without collisions. A flexible TeleOp interface enabling 8+ cones per match. Manual trajectory tuning and ad-hoc state logic proved brittle. By integrating Road Runner and a modular state machine, we achieved both precision and robustness.\nHardware Setup Four-wheel mecanum drivetrain with NeveRest Orbital 20 motors. REV Control Hub (v1.15.6) + two REV Expansion Hubs. BNO055 IMU for field-centric orientation. 3D-printed claw with rubber-band tension. Web camera on a swivel mount for colour detection and April Tag reads. REV Distance Sensor for close-range cone alignment. All ports and constants live in TeamCode/src/main/java/constants/HardwareMap.java for easy remapping.\nSoftware Architecture FtcRobotController (SDK): Core Android/Java framework TeamCode: DriveSubsystem for field-centric motion AutoStateMachine enumerating preload delivery, cycle loops, recoveries, and parking TeleOpOpMode mapping joysticks, bumpers, and buttons to drive \u0026amp; slide actions MeepMeepVisualizer: JSON importer to preview and refine trajectories on desktop This separation keeps vision, driving, stacking, and game logic in testable, interchangeable modules.\nPath Planning \u0026amp; Simulation Road Runner lets us define spline sequences:\nTrajectorySequence autoPath = drive.trajectorySequenceBuilder(startPose) .addDisplacementMarker(() -\u0026gt; stacking.intakeCone()) .splineTo(new Vector2d(1.2, 2.8), Math.toRadians(90)) .addDisplacementMarker(() -\u0026gt; stacking.raiseTo(MEDIUM)) .splineTo(new Vector2d(0.5, 1.0), Math.toRadians(180)) .build(); drive.followTrajectorySequenceAsync(autoPath); We export these trajectories to JSON for MeepMeep, revealing velocity curves and heading changes. On-field tuning runs dropped by over 50%.\nAutonomous Routine Signal Scan Shipping element pipeline detection identifies parking zone (Left/Center/Right). Preload Delivery Drive to medium junction \u0026amp; deposit preloaded cone. Cone Cycles (2×) Navigate to substation, intake a cone, return to high junction, stack. Distance sensor confirms pickup; timeouts trigger recovery. Parking Follow spline to the target zone for a 10-point bonus. The AutoStateMachine class orchestrates these phases, handling errors and timeouts gracefully.\nTeleOp Controls Left stick: field-centric translation. Right stick X: rotation. D-pad Up/Down: select slide level (ground/low/medium/high). Right bumper: claw grab/release. Left bumper: slide home. X/B: adjust slide ±10 ticks. A: quick 180° pivot. Drive power, slide speed, and PID gains are exposed in DriveConstants.java and SlideConstants.java.\nTesting \u0026amp; Results Metric Result Target Preload placement accuracy 96% ≥95% Cycle time (pickup → stack) 10.2 s (avg) ≤12 s Full autonomous completion time 28.5 s (avg) ≤30 s Parking success rate 100% 100% During TeleOp, our drivers averaged eight stacked cones and maintained precise slide positioning under match pressure.\nLessons Learned Vision systems need per-match exposure tuning to handle gym lighting. Explicit error states in the state machine prevent mechanism jams. Dual odometry wheels drastically improves path-following repeatability. Continuous MeepMeep simulation saved hardware wear and accelerated strategy iteration. Future Work Add TensorFlow-based object detection for signal redundancy. Dynamic cycle-count adjustment based on remaining match time. Live trajectory replanning to avoid opposing-alliance congestion. Port vision \u0026amp; drive code to Kotlin for enhanced concurrency. Publish a post-season video dissecting match footage with overlayed trajectories. ","permalink":"/projects/ftc-powerplay/","section":"projects","summary":"A Java-based control system for the FIRST Tech Challenge PowerPlay season, featuring mecanum drive with Road Runner path planning, AprilTag-powered signal detection, and a resilient autonomous state machine.","tags":["Project","FTC","Robotics","Java","Road Runner","OpenCV"],"title":"FTC: PowerPlay"},{"content":"XII XII is a cutting-edge, multi-platform engine designed for real-time applications. Developed in C++23, it offers comprehensive support for a wide range of platforms, including Windows, Linux, macOS, Android, and iOS. Its modular architecture allows users the flexibility to leverage its full suite of features or selectively utilize specific functionalities through dynamically linked libraries.\nFor further details, please visit the official website at www.xiitechnologies.com.\nGallery ","permalink":"/projects/xii-engine/","section":"projects","summary":"An in-depth look at XII Engine: a modular, cross-platform C++ engine for high-performance real-time applications.","tags":["Project","C++","Game Engine","Graphics","Cross-Platform","Real-time Rendering","High-Performance"],"title":"XII Engine"},{"content":"Overview We built a full-stack Android/Java control system for our Freight Frenzy FTC season. The code lives in the standard FTC Robot Controller SDK, extended with:\nA custom TeamCode module for our hardware mappings and behaviors MeepMeep path visualization to simulate and fine-tune autonomous routes Tunable force coefficients for drivetrain motors State-machine control for ring/puck intake and shipping hub scoring This repo powers both teleop driver controls and a multi-stage autonomous routine optimized for shipping hub cycles.\nMotivation Freight Frenzy challenges teams to pick up blocks and freight, navigate warehouse zones, and score on a shipping hub under 30 seconds of autonomy. We needed:\nPrecise path‐following to avoid obstacles Tunable power models for battery aging/drivetrain friction A fast-recovery state machine in case of mis-alignment Easy simulation of trajectories before hitting the field This drove us to integrate MeepMeep and develop a robust on-robot state architecture.\nDevelopment Setup Clone the FTC SDK and our fork: git clone https://github.com/theoeriata/FTC_FreightFrenzy.git cd FTC_FreightFrenzy Open in Android Studio (“Import Project (Gradle)”). Plug in the REV Expansion Hub over USB-OTG. Tune motor force coefficients in TeamCode/src/main/java/constants/DriveConstants.java. Deploy via “Run \u0026gt; Deploy” to your Control Hub or Driver Hub. Software Architecture Module Breakdown FtcRobotController (root SDK) TeamCode Hardware mappings: IMU, DC motors, servos, distance sensors AutoStateMachine: sequences marker drop, freight intake, hub scoring DriveSubsystem: kinematic wrapper with feedforward + PID TeleOpOpMode: field-centric joystick drive + intake controls MeepMeepPathVisualizer JSON-exported trajectories from on-robot code Real-time visualization in desktop window Key Features Unpowered path planner with JSON export for simulation Force coefficient adjustments to match real motor torque curves Recovery states if element detection via optical distance sensor fails Field-centric mecanum drive for intuitive TeleOp handling Autonomous Routine Start at pre-load shipping hub level (determined by AprilTag scan). Deliver the duck marker. Cycle: Navigate to warehouse Intake freight (3 blocks) Return to shipping hub Deposit on designated level Park in warehouse to maximize endgame points. Top-down view (meters) ┌─────────────────────────────┐ │ [Shipping Hub] [Carousel]│ │ ↑ → │ │ Start → → → → ← ← ← ← │ │ ↓ ↓ │ │ [Warehouse Entrance] │ └─────────────────────────────┘ Path Visualization We integrated the MeepMeep SDK to simulate robot paths:\nPhase Waypoints Simulated Time Pre-load drop (0,0) → (1.2,0.8) → (1.0,1.6) 4.1s Warehouse run (1.0,1.6) → (3.0,2.5) 3.8s Return score (3.0,2.5) → (0.8,1.4) 3.2s This allowed on-field tuning of power curves and obstacle avoidance without repeated hardware trials.\nTeleOp Controls Left stick: field-centric translation Right stick X: rotation A/B: intake in/out X/Y: dumper up/down Bumpers: speed scaling (0.5×, 1×, 1.5×) for delicate / fast maneuvers Testing \u0026amp; Results 20+ hardware runs to tune force coefficients 95% success on first-cycle hub deposition Average 28s complete cycle time (target: \u0026lt;30s) Robust recovery from dropped freight in 2.3s on average Future Work Machine-vision alignment to shipping hub using OpenCV Dynamic trajectory replanning if blocked by opponent robot Integration with Road Runner for spline-based paths Expansion of MeepMeep plugin to visualize sensor error envelopes ","permalink":"/projects/ftc-freightfrenzy/","section":"projects","summary":"A complete Java‐based control system for a FIRST Tech Challenge Freight Frenzy robot, featuring path planning, sensor integration, and performance tuning.","tags":["Project","Robotics","FTC","Java","Path Planning"],"title":"FTC: Freight Frenzy"},{"content":" VisionSource serves as the heart of VisionEngine, an open-source, real-time C++ game engine. It encapsulates a modular, component-based architecture that drives entity transformations, rendering pipelines, and asset management; all orchestrated through a robust CMake-driven build system.\nThe Engine submodule introduces a flexible ECS framework, with components like Transform for hierarchical object positioning and Bullet conversions for physics interoperability. In parallel, the Shaders directory houses GLSL programs loaded at runtime, enabling dynamic material and lighting effects.\nAn integrated Editor streamlines development by allowing live tweaks to component properties and scene hierarchies, accelerating iteration and debugging. Throughout VisionSource’s development, I focused on performance optimization, memory management, and clean code organization, laying a solid foundation for larger engine features.\n","permalink":"/projects/diligent-animation-viewer/","section":"projects","summary":"VisionSource is the foundational C++ module of VisionEngine, offering component-based real-time rendering and shader management.","tags":["Project","C++","Game Engine","Graphics","Open Source","Real-time Rendering"],"title":"VisionSource: A Diligent GLTF Loader"},{"content":"I\u0026rsquo;m Theophilus Eriata, a software engineer working in graphics, real-time simulation, robotics, and AI. I\u0026rsquo;m drawn to problems that require both correctness and performance.\nI\u0026rsquo;m completing a Computer Science (Honours) degree at the University of Calgary, with a focus on theoretical computer science, graphics, and systems. Outside of coursework, I work on XII Engine, a cross-platform C++23 real-time engine. I also explore how GPU-accelerated simulation can help solve control problems in robotics and biomedical engineering.\nI\u0026rsquo;m as interested in how people grow as I am in how systems work. Mentoring students through FIRST Robotics, teaching at church, and building technical communities at the University of Calgary have become meaningful parts of my life. I enjoy helping people discover what they\u0026rsquo;re capable of and creating environments where they feel supported.\nService has shaped me as much as engineering. Volunteering with Alberta Health Services and the Mustard Seed taught me the value of listening carefully, offering guidance, and helping someone through a difficult moment. Those experiences strengthened my desire to do work that brings technical skill and human care together.\nFaith gives me direction. Engineering gives me tools. Community gives me purpose. Together, they shape how I approach projects and people: with patience, curiosity, and a commitment to building things that help.\nWith sufficient discipline and tenacity, we can make impossibilities possible.\nExplore my projects or technical writing to see what I\u0026rsquo;m working on.\n","permalink":"/about/","section":"","summary":"Theophilus Eriata's background in software engineering, graphics, robotics, and AI.","tags":[],"title":"About"},{"content":" Send a message All fields are required except subject. Messages are handled by FormSubmit.\nYour name Your email Subject (optional) Message Send message ↗ ","permalink":"/contact/","section":"","summary":"Get in touch with Theophilus Eriata for engineering collaboration, research discussions, and professional opportunities.","tags":[],"title":"Contact"},{"content":"I believe leadership is best measured by the opportunities I create for others. Alongside engineering, I\u0026rsquo;ve worked in student organizations, robotics mentorship, healthcare volunteering, and community service.\nTechnical communities President · AI Club, University of Calgary 2026–2027\nMy current role brings together my interests in AI, technical education, and building a community where students can explore the field together.\nChair · IEEE Student Branch, University of Calgary Current\nSupporting the university\u0026rsquo;s community of students in engineering and technology.\nVice President, Academic \u0026amp; Workshop Executive · AI Club Previous role · University of Calgary\nFacilitated workshops on AI theory and applications in computational chemistry and robotics, using participant feedback to improve the workshops.\nDistrict Committee Chair \u0026amp; Senior VP · Rotary and Rotaract Previous roles · Rotary District 5360 \u0026amp; Rotaract UCalgary\nCoordinated initiatives across Rotaract clubs and contributed to strategic planning for community activities.\nMentorship and education FIRST Robotics Mentor · Calgary Board of Education 2022–2023\nMentored a new all-girls FIRST Tech Challenge team through mathematics, computer science, and programming. Workshops covered linear algebra, control theory, colour spaces, trigonometry, and collaborative software development.\nRead about the mentoring experience.\nChildren\u0026rsquo;s Ministry Teacher · Winner\u0026rsquo;s Chapel International Calgary Teaching and guiding children through interactive activities grounded in faith and values.\nVolunteer service Emergency, Patient Support \u0026amp; Site Wayfinder · Alberta Health Services Provided directions and assistance to patients, visitors, and staff, communicating with people from a wide range of backgrounds in a hospital setting.\nShelter Helper · The Mustard Seed Helped distribute clothing and hygiene products and managed access to residents\u0026rsquo; personal lockers.\nA record of service My personal records and estimates include 2,045 patients and staff helped, a 30% reduction in wayfinding queries, 62 students mentored, 24 volunteers mobilized, 200 items distributed, and $24K raised across these activities.\nThese figures are self-reported estimates. Roles and affiliations do not constitute official statements or endorsements by the organizations.\nGet in touch about technical workshops, mentorship, or community collaboration.\n","permalink":"/leadership/","section":"","summary":"Technical communities, robotics mentorship, and service at the University of Calgary and beyond.","tags":[],"title":"Leadership"}]