--- name: classic-native-change description: Implement and validate Classic Atrinik C17 and CMake changes across client, server, and libatrinik. Use for native code, shared APIs, build integration, or Classic editor packaging; use the protocol and runtime specialists when those contracts are involved. --- # Classic native changes Use this workflow for the maintained Classic implementation. The MIT replacement stack has different interfaces. This skill supplies development procedure, not permission to use shared machines, publish changes, or alter another task's state. ## Establish the working graph Read the checkout's root and affected component `AGENTS.md`, `CONTRIBUTING.md`, and build instructions. Their current rules govern the work. When an `atrinik` MCP connection is available, select the Classic profile and a specific component for guidance and source queries. Read returned resources; compare their commit with local Git before using the result. Narrow incomplete searches or follow their cursor. Use local files for a different revision or uncommitted changes. MCP availability grants neither write access nor runtime authority. Identify the physical owner before editing: `client/` is the SDL3 game client, `server/` owns simulation and persistence, `libatrinik/` provides reusable native APIs, and `protocol/` supplies command identities. `editor/` packages an external Gridarta build; it is not another native game target. Content, sound, and resources have separate owners. Use one owned monorepo worktree for coordinated Classic component changes. Resolve a full, isolated profile derived from `classic` through the workspace's current profile/worktree procedure. All Classic roles must select the same physical root; retain the other required providers. Inspect the selection from the wrapper root: ```sh ./atrinik profile show PROFILE --json ./atrinik topology show PROFILE ``` Replace `PROFILE` with that concrete profile. Do not default to the replacement stack or solve dependency drift by copying code between modules. Existing delivery bindings, build-plan fencing, and resource ownership checks remain in force. ## Implement against the relevant contract Put generally reusable APIs in `libatrinik/`; keep game policy in its consumers. For an API change, establish who allocates, frees, may mutate, and may retain each value, how errors are signaled, and what concurrent calls may do. Follow the repository's formatting, error-handling, and CMake target conventions. Trace callers as well as declarations. In particular: - For client changes, consider SDL resource lifetimes, image/transparency rules, input consumption, render invalidation, and isolated user configuration. Read the current map-rendering contract when visibility or lighting is affected. - For server changes, trace object/map ownership, persistence transactions, plugin cleanup, and save/load identity. Preserve rollback and failure behavior; initialized account/player data is not a disposable source fixture. - For library changes, validate public ownership and lifetime rules in both consumers. Pathfinding also has an independent build/install interface; exercise separate contexts, search limits, failures, and deterministic outcomes as relevant. - For wire changes, use [classic-protocol-change](../classic-protocol-change/SKILL.md) before updating either endpoint. Change authored inputs: schema, lexer definition, CMake source list, shader source, or generator. Regenerate their outputs using the owning module's procedure. Keep ignored build artifacts out of commits; do not hand-fix generated bytes to make a check pass. If a committed generated binding changes, include its source change. Sibling protocol/library sources must remain the inputs for integrated builds. When dependency selection or packaging changes, check release locks, embedded dependencies, offline source packages, and installed consumers as applicable. The root CMake graph must not instantiate shared dependencies twice. ## Prove the affected behavior Choose focused regression cases from the bug or new behavior, including relevant error and cleanup paths. For substantial native logic, run the module's documented coverage procedure and applicable sanitizer checks. Fix new compiler warnings and sanitizer findings; a build that emits them is not adequate validation. Run wrapper builds from the wrapper root for the affected dependency closure. For a shared library change this normally includes all three commands: ```sh ./atrinik build libatrinik --profile PROFILE --test ./atrinik build server --profile PROFILE --test ./atrinik build client --profile PROFILE --test ``` Use current wrapper build planning/fencing where required. A client-only change need not rebuild unrelated server logic; a shared API or protocol change must include its consumers. Run additional standalone, install/export, dependency, or packaging checks required by the touched module. Follow current CPU-worker/cache isolation rules for application builds. For editor packaging, use its local `AGENTS.md`: syntax and ShellCheck, then a disposable Gridarta fixture to verify Gradle failure propagation, expected JAR, dated artifact, and stable symlink. An editor packaging task does not authorize downloading or modifying an external checkout. For behavior that needs a running game, use [classic-runtime](../classic-runtime/SKILL.md). State the expected observation and record what happened; successful startup alone does not prove gameplay, rendering, audio, or persistence correctness. From the Classic root, complete its required history and whitespace checks: ```sh python3 tools/verify_import_history.py git diff --check ``` Report changed behavior, selected worktree/profile, tests and observations, remaining limitations, and owned-resource cleanup. Refresh local guidance when ownership or supported interfaces change. Respect Classic's source license and attribution; this MIT skill does not relicense the code it helps maintain.