Create an Agent SKILL.md fallback for an existing find-XXXX finder that relies on a fragile discovery foundation — above all LLM_DECOMPILE (patterns C/D/E), which matches the decompiled shape of a predecessor function against a stored reference and breaks when a symbol is inlined or de-inlined in a way the reference does not cover. The generated fallback coexists with the preprocessor and runs only when it returns failure, recovering every target robustly by decompiling the predecessor and following the inline/de-inline boundary with semantic anchors. Use when a finder broke on a game update, or you want to durably backstop one before it does. The recipe generalizes to any finder foundation. Triggers: create agent skill fallback, add SKILL.md fallback, robust fallback for finder, backstop LLM_DECOMPILE finder, final guarantee skill
Given a target find-XXXX finder whose preprocessor uses a fragile foundation (most often LLM_DECOMPILE),
author .claude/skills/find-XXXX/SKILL.md — the Agent fallback that ida_analyze_bin.py runs only when the
preprocessor returns failure. The preprocessor stays as-is; this skill adds a durable backstop beside it.
This is the robustness-oriented sibling of /convert-finder-skill-to-preprocessor-scripts (that skill turns a
SKILL.md into a preprocessor; this one gives a preprocessor a SKILL.md fallback).
When to Use
A find-XXXX finder failed on a new game version because a member/vfunc/function was inlined or
de-inlined relative to what its LLM_DECOMPILE reference expects (classic symptom: one target in a
multi-target -decompiles skill can no longer be found, aborting the module).
You want to pre-emptively backstop an LLM_DECOMPILE-based finder (patterns C/D/E) whose correctness hinges
on a predecessor keeping a fixed decompiled shape.
The recipe also applies to xref-string / found_call / index-based finders — the foundation differs, but the
same "self-contained, skip-existing, anchor semantically, follow the callee" method holds.
Do not use this to replace a working preprocessor. The fallback is a safety net; the preprocessor remains
the fast path.
The one constraint that drives the whole design
When the preprocessor fails, agent_runner.run_skill launches the agent with a prompt of only
/{skill_name} (see _build_claude_command, profile sig-finder). The skill's expected_yaml_paths is used
only for post-run missing-file verification (_missing_expected_outputs / _result_failure_reason) — it is
never injected into the prompt, and the missing list is not fed back to the agent on retry.
Consequence: the fallback SKILL.md you generate MUST be fully self-contained. It must enumerate every
output, gate each by platform, and tell the agent to skip outputs whose YAML already exists (the
preprocessor may have written most of them before failing; earlier fallback skills may have written others).
Inputs to gather (read these before writing anything)
For target finder find-XXXX in module <module> (server, engine, networksystem, …):
Preprocessorida_preprocessor_scripts/find-XXXX.py — the source of truth for what to find:
TARGET_FUNCTION_NAMES, TARGET_STRUCT_MEMBER_NAMES, TARGET_GLOBALVAR_NAMES and any
*_WINDOWS / *_LINUX variants → the output symbols and their platform gating.
LLM_DECOMPILE (and _WINDOWS/_LINUX) → the predecessor reference each target is mined from
(references/<module>/<predecessor>.{platform}.yaml).
FUNC_VTABLE_RELATIONS → which targets are vtable-related (vtable_name).
GENERATE_YAML_DESIRED_FIELDS → the exact fields and kind for each target (this tells you whether a
target is a struct member, an indirect-vcall vfunc, a real vfunc, a regular func, or a global var — see the
kind table below).
any FUNC_XREFS (string/gv anchors) — extra fingerprints you can reuse.
configs/<GAMEVER>.yaml skill entry — expected_output / expected_output_windows / expected_output_linux
(authoritative output list per platform), expected_input (the predecessor YAML), platform,
prerequisite.
Reference YAMLsida_preprocessor_scripts/references/<module>/<predecessor>.{platform}.yaml — the
disasm_code + procedure carry the annotations ; 0xNN = Class::member and
; 0xNN = Class::vfunc / // NNNN = 0xNN = … at each access/call site. These annotations are the semantic
fingerprints you translate into the fallback's anchors. Also collect any real-world helper or alternate
inline/de-inline reference YAML that materially helps locate the targets.
Source-owned output YAMLsbin_artifacts/<gamever>/<module>/<target>.{platform}.yaml — the
authoritative offsets, vfunc indices, and signature styles the finder currently produces. Mine these for
the reference values in the inventory table. Resolve an exact configured <gamever>; these files are tracked
Git truth. Document their values as update- and platform-specific references, never as fixed answers.
Cross-check every value across the reference annotation AND the ground-truth YAML; where they disagree, trust
the ground-truth YAML and note the discrepancy (references occasionally mis-annotate — see the decoy caution).
Workflow
Step 1 — Build the output inventory
From the preprocessor .py + config entry, list every output as (symbol, kind, platform, predecessor, desired-fields). Determine kind from GENERATE_YAML_DESIRED_FIELDS using this table:
func_name, func_sig, func_va, func_rva, func_size (no vtable fields)
/generate-signature-for-function
/write-func-as-yaml
global variable
gv_name, gv_va, gv_sig, gv_inst_*
/generate-signature-for-globalvar
/write-globalvar-as-yaml
The indirect-vcall kind is easy to miss: its YAML has no func_va and its vfunc_sig is the signature of
the call instruction itself (e.g. FF 90 A0 00 00 00 = call qword ptr [rax+0A0h]), not of any target
function body. Treat it as such — do not try to resolve a concrete implementation address.
Step 2 — Confirm platform gating and the predecessor
From the config entry, record which outputs are cross-platform vs expected_output_windows /
expected_output_linux, and the predecessor(s) from expected_input. A symbol that is de-inlined into a
separate function on one platform but inlined on the other is common (that asymmetry is often why the finder
is fragile).
Step 3 — Extract per-target fingerprints
For each target, read its access/call site in the reference disasm_code + procedure and write down:
the semantic anchor: the nearest stable landmark — a string literal, a magic constant, a named
global/interface call, a distinctive helper — that identifies the site independent of address;
the this-relative offset (members) or call displacement (indirect vcalls) or vtable index;
the reference value from the source-owned bin_artifacts/ YAML (both platforms where they differ).
Step 4 — Write the fallback SKILL.md
Create .claude/skills/find-XXXX/SKILL.md from the template below. Fill every placeholder; keep only the
target kinds that actually occur. The output filename MUST equal the finder's skill name so
agent_runner.run_skill finds it.
The generated fallback MUST contain ## Realworld Function References near the top, before the background.
List one exact repo-relative YAML path per bullet for every platform-relevant predecessor and useful
inline/de-inline helper or variant. Spell out .windows.yaml and .linux.yaml paths separately; do not use
{platform} shorthand in this section, because the agent must be able to open each reference directly. State
that addresses and offsets are reference-build values that still require verification against the current
binary.
Step 5 — Validate
uv run python -m unittest discover -s tests -b (guard; a pure-doc addition should not affect tests).
Re-read the generated SKILL.md against the inventory: is every output listed, platform-gated, and mapped to a
sig-gen + writer skill with correct params?
Run the fallback itself in the real IDA/MCP environment, bypassing both old-version reuse and preprocessing:
The selected game version must contain the module binary and every configured expected_input. Seed a
checkout-external scratch artifact root from bin_artifacts/<gamever>/, remove the target outputs only in that
scratch copy, and pass it with -artifactdir (plus tracked bin_artifacts as -oldartifactdir). Never delete or
rewrite tracked expected artifacts merely to force the test. A run that says all outputs already exist and skips
the skill is not a valid Agent-Skill-only test.
Require the log to show Agent Skill only mode: enabled (-skip_pp), Skipping preprocess: <skill_name> (-skip_pp), and Starting agent skill: <skill_name>, followed by Success and summary Failed: 0. Verify
that every expected YAML for the tested platform was produced and parses as a non-empty mapping.
Do not report the fallback as complete unless this end-to-end Agent-Skill-only test passes. If the real IDA/MCP
environment is unavailable, report the validation as blocked rather than substituting a preprocessor run or a
ground-truth-only review.
Step 6 — Commit Changes to dev
After the end-to-end fallback test has produced and verified its YAMLs, copy the validated canonical target outputs
from the isolated root into bin_artifacts/<gamever>/<module>/, include their computed downstream closure, and run the
repository artifact contract. Record the required one-line memory pointer
per the repo workflow. Ensure the delivery branch is dev; never commit directly to main. If the local dev
branch exists, switch to it. Otherwise, switch to main first and create dev from main:
if git show-ref --verify --quiet refs/heads/dev; then
git switch dev
else
git switch main
git switch -c dev
fi
If any branch switch fails, stop and report the error. Review git status --short, then explicitly stage the new
fallback skill, source-owned artifact closure, and that memory update; never use git add -A:
Do not call /create-pr, push the branch, or open a pull request unless the user separately requests it. Finish by
reporting the commit hash, tested game version, unittest result, and Agent-Skill-only result.
Fallback SKILL.md template
Fill placeholders <...>; drop sections for kinds that do not apply.
---
name: find-XXXX
description: |
Final-guarantee fallback for the find-XXXX preprocessor. Recovers <one-line summary of the targets> in CS2
<module binaries> by decompiling <PREDECESSOR> and following de-inlined callees when a target is no longer
accessed directly. Use when the deterministic/LLM preprocessor (ida_preprocessor_scripts/find-XXXX.py) could
not resolve every target because a symbol was inlined or de-inlined in a way the LLM_DECOMPILE references do
not cover.
Trigger: <every target symbol, comma-separated>
disable-model-invocation: true
---
# Find XXXX (final-guarantee fallback)
Recover every symbol the find-XXXX preprocessor produces, in CS2 `<binary.dll>` / `<libbinary.so>`, using IDA
Pro MCP tools. This is the Agent fallback: it runs only when the preprocessor returned failure — which almost
always means a target's access pattern **moved** across the inline/de-inline boundary.
## Realworld Function References
Read the platform-relevant real-world YAMLs before searching in IDA. Treat their addresses and offsets as
reference-build values only; verify every result against the current binary.
- `ida_preprocessor_scripts/references/<module>/<predecessor>.windows.yaml`
- `ida_preprocessor_scripts/references/<module>/<predecessor>.linux.yaml`
- `ida_preprocessor_scripts/references/<module>/<relevant-helper-or-variant>.windows.yaml`
- `ida_preprocessor_scripts/references/<module>/<relevant-helper-or-variant>.linux.yaml`
Spell out each existing path literally and drop non-applicable placeholders or platforms.
## Background — <PREDECESSOR> and what it wires up
<2–5 sentences: what the predecessor does and which targets it touches, in source terms. Note that all member
accesses are relative to `this` (arg1: rcx/rsi on Windows, rdi/rbx on Linux) — the key to robustness.>
## Robustness principle — follow the de-inline boundary
For every target: (1) look for its access pattern inside `<PREDECESSOR>`; (2) if absent, it was de-inlined —
enumerate the functions `<PREDECESSOR>` calls, decompile the plausible ones, and search there (the helper
receives `this` as its first argument, so the same `this + offset` reappears; recurse a level or two);
(3) conversely a target the reference expected in a separate function may have been inlined back into
`<PREDECESSOR>`. Anchor each target by its semantic fingerprint (string / constant / neighboring call), never by
a fixed address or containing function.
## Output inventory
`struct_name` is `<STRUCT>` where applicable. Offsets/indices are **reference values from build <gamever> —
verify against the binary, do not assume**.
| # | Output symbol | Kind | Windows | Linux | Writer skill |
|---|---------------|------|---------|-------|--------------|
| 1 | `<symbol>` | <kind> | `<value or "inlined — skip">` | `<value or "inlined — skip">` | `/write-...-as-yaml` |
| … | | | | | |
Platform gating: <list cross-platform vs windows-only / linux-only outputs>.
## Step 0. Skip targets already produced
For each output, if `<name>.<platform>.yaml` already exists in the active artifact module directory and parses to a
non-empty mapping, skip it — the preprocessor or an earlier fallback wrote it. Use the analyzer-reported artifact
module directory; never derive the artifact path from the binary directory.
`/get-func-from-yaml` also reports existence for functions/vfuncs.
## Step 1. Load and decompile the predecessor
**ALWAYS** Use SKILL `/get-func-from-yaml` with `func_name=<PREDECESSOR>` to get its `func_va`. If it errors,
**STOP** and report to user. Then:
```
mcp__ida-pro-mcp__decompile addr="<PREDECESSOR.func_va>"
```
Note the `this` register and keep the list of called functions for the de-inline search.
## Step 2…N. Resolve each target
<One subsection per target (or per cluster sharing a call site). For each: the semantic anchor, the
this-relative offset / call displacement / vtable index, the reference value, and how to handle de-inline. Note
any decoys.>
## Signatures and YAML output
<Per kind, the sig-gen skill + writer skill + exact params — copy from the kind table. E.g. struct members →
/generate-signature-for-structoffset → /write-structoffset-as-yaml with struct_name/member_name/offset/size=None/
offset_sig/offset_sig_disp; indirect vcalls → /generate-signature-for-vfuncoffset → /write-vfunc-as-yaml with
func_addr=None, func_sig=None, vfunc_sig, vtable_name, vfunc_offset, vfunc_index=offset/8.>
## Failure handling
- Predecessor YAML missing → **STOP** and report.
- A required target unresolved even after following callees → resolve the rest, then **STOP** and report exactly
which output(s) failed so the user can extend the references.
- Never emit a platform-gated symbol on the wrong platform.
## Output YAML filenames
Written under the active artifact module directory, one per symbol: `<symbol>.windows.yaml` / `<symbol>.linux.yaml`.
Robustness principles (the heart of a good fallback)
Anchor semantically, not positionally. A fixed address or "it's in function F" breaks on the next update.
A string literal, a magic constant (e.g. an FNV seed 0x811C9DC5), a named interface call, or a distinctive
helper survives.
this + offset is stable across the inline boundary. Struct offsets are relative to the class pointer
(arg1). Whether the access is inlined in the predecessor or de-inlined into a helper that receives this,
the same this + offset appears — so members are recoverable either way.
Follow the callee (the core move). If a target isn't in the predecessor, enumerate the predecessor's
calls, decompile them, and recurse. This is what makes the fallback a guarantee rather than a re-run of the
fragile reference match. Cover the inline-back case too.
Know the indirect-vcall shape. For call qword ptr [reg + disp] on an interface pointer, the output is
vtable_name + vfunc_offset = disp + vfunc_index = disp/8 + a vfunc_sig pinning the call instruction;
there is no func_va. Resolve the interface from the receiver global's type.
Watch for decoys. References sometimes annotate two nearby offsets with the same member name. Trust the
source-owned bin_artifacts/ YAML. (Real example: CEntitySystem::m_eNetworkSerializationMode is the DWORD at
0xBBC, set from the mode param and re-read at the SetNetworkSerializationContextData call — not the byte
flag at 0xBDA, even though the reference labels both.)
The offset is the must-have; the signature is best-effort. For struct members, offset is the required
output; offset_sig is for relocation and may legitimately be omitted (write offset only) when a unique
signature can't be found — especially for members whose access spans a function boundary.
Checklist
Read the target preprocessor .py, its config entry, its reference YAMLs, and its bin_artifacts/ source-owned
output YAMLs.
Output inventory lists every symbol with kind + platform + predecessor + reference value.
Fallback SKILL.md filename equals the finder's skill name.
## Realworld Function References lists exact, directly openable repo-relative YAML paths for each
relevant platform and inline/de-inline helper or variant; it contains no {platform} shorthand.
SKILL.md is self-contained: enumerates all outputs, gates by platform, has the Step-0 skip-existing step.
Each target has a semantic anchor and the follow-the-callee instruction; decoys are called out.
Each kind is mapped to the correct sig-gen + writer skill with correct params (indirect vcalls use
func_addr=None/func_sig=None + vfunc_sig).
Values cross-checked against bin_artifacts/ Git truth.
Real Agent-Skill-only test passed with uv run ida_analyze_bin.py -gamever <gamever> -oldgamever none -modules=<module> -debug -skip_pp -skill=<skill_name>; the log proves preprocessing was skipped, the
Agent Skill actually started, all expected YAMLs were produced, and the summary reports Failed: 0.
The current branch is dev (created from main when it did not already exist).
New SKILL.md, computed bin_artifacts closure, and memory pointer are explicitly staged and committed.
/create-pr was not called; no push or PR was performed without a separate user request.
Worked example — find-CEntitySystem_Init-decompiles
The canonical output of this workflow lives at
.claude/skills/find-CEntitySystem_Init-decompiles/SKILL.md (commit a31fdf4). Study it as the reference.
Fragile foundation:ida_preprocessor_scripts/find-CEntitySystem_Init-decompiles.py mines 11 targets by
LLM_DECOMPILE off one predecessor, CEntitySystem_Init
(reference references/server/CEntitySystem_Init.{platform}.yaml).
What broke: on Windows 14168, CEntitySystem_InitEntityMaterialAttributes was de-inlined out of
CEntitySystem_Init, so the m_EntityMaterialAttributes access left the predecessor and the LLM match
failed, aborting the module.
What the fallback covers: all 11 targets — 7 cross-platform struct members, 2 indirect-vcall vtable
offsets (INetworkMessages_SetNetworkSerializationContextData @0xA0/idx20,
IFlattenedSerializers_CreateFieldChangedEventQueue @0x118/idx35), Linux-only
CEntitySystem_ProcessEntityRegistration, and Windows-only m_EntityMaterialAttributes (@0x2070) — each
anchored by a fingerprint (the "string_t_table" call, the CUtlScratchMemoryPool::Init(_, 0x400, …) call,
the FNV material-hash loop, …) and recoverable whether inlined or de-inlined.
It demonstrates every section of the template: directly openable real-world function references, the
inventory table, Step-0 skip, follow-the-callee, the indirect-vcall shape, the 0xBBC-vs-0xBDA decoy note,
and the offset-is-must-have caveat for the field-change trio.
1---2name: create-agent-skill-fallback3description: Create an Agent SKILL.md fallback for an existing find-XXXX finder that relies on a fragile discovery foundation — above all LLM_DECOMPILE (patterns C/D/E), which matches the decompiled shape of a predecessor function against a stored reference and breaks when a symbol is inlined or de-inlined in a way the reference does not cover. The generated fallback coexists with the preprocessor and runs only when it returns failure, recovering every target robustly by decompiling the predecessor and following the inline/de-inline boundary with semantic anchors. Use when a finder broke on a game update, or you want to durably backstop one before it does. The recipe generalizes to any finder foundation. Triggers: create agent skill fallback, add SKILL.md fallback, robust fallback for finder, backstop LLM_DECOMPILE finder, final guarantee skill4---56# Create an Agent SKILL.md Fallback for a Finder78Given a target `find-XXXX` finder whose preprocessor uses a fragile foundation (most often **LLM_DECOMPILE**),9author `.claude/skills/find-XXXX/SKILL.md` — the **Agent fallback** that `ida_analyze_bin.py` runs only when the10preprocessor returns failure. The preprocessor stays as-is; this skill adds a durable backstop beside it.1112This is the robustness-oriented sibling of `/convert-finder-skill-to-preprocessor-scripts` (that skill turns a13SKILL.md *into* a preprocessor; this one gives a preprocessor a SKILL.md *fallback*).1415## When to Use1617- A `find-XXXX` finder failed on a new game version because a member/vfunc/function was **inlined or18 de-inlined** relative to what its LLM_DECOMPILE reference expects (classic symptom: one target in a19 multi-target `-decompiles` skill can no longer be found, aborting the module).20- You want to **pre-emptively** backstop an LLM_DECOMPILE-based finder (patterns C/D/E) whose correctness hinges21 on a predecessor keeping a fixed decompiled shape.22- The recipe also applies to xref-string / found_call / index-based finders — the foundation differs, but the23 same "self-contained, skip-existing, anchor semantically, follow the callee" method holds.2425Do **not** use this to replace a working preprocessor. The fallback is a safety net; the preprocessor remains26the fast path.2728## The one constraint that drives the whole design2930When the preprocessor fails, `agent_runner.run_skill` launches the agent with a prompt of **only31`/{skill_name}`** (see `_build_claude_command`, profile `sig-finder`). The skill's `expected_yaml_paths` is used32**only** for post-run missing-file verification (`_missing_expected_outputs` / `_result_failure_reason`) — it is33**never** injected into the prompt, and the missing list is not fed back to the agent on retry.3435Consequence: the fallback SKILL.md you generate MUST be **fully self-contained**. It must enumerate every36output, gate each by platform, and tell the agent to **skip outputs whose YAML already exists** (the37preprocessor may have written most of them before failing; earlier fallback skills may have written others).3839## Inputs to gather (read these before writing anything)4041For target finder `find-XXXX` in module `<module>` (`server`, `engine`, `networksystem`, …):42431. **Preprocessor** `ida_preprocessor_scripts/find-XXXX.py` — the source of truth for *what* to find:44 - `TARGET_FUNCTION_NAMES`, `TARGET_STRUCT_MEMBER_NAMES`, `TARGET_GLOBALVAR_NAMES` and any45 `*_WINDOWS` / `*_LINUX` variants → the output symbols and their **platform gating**.46 - `LLM_DECOMPILE` (and `_WINDOWS`/`_LINUX`) → the **predecessor** reference each target is mined from47 (`references/<module>/<predecessor>.{platform}.yaml`).48 - `FUNC_VTABLE_RELATIONS` → which targets are vtable-related (`vtable_name`).49 - `GENERATE_YAML_DESIRED_FIELDS` → the **exact fields and kind** for each target (this tells you whether a50 target is a struct member, an indirect-vcall vfunc, a real vfunc, a regular func, or a global var — see the51 kind table below).52 - any `FUNC_XREFS` (string/gv anchors) — extra fingerprints you can reuse.532. **`configs/<GAMEVER>.yaml` skill entry** — `expected_output` / `expected_output_windows` / `expected_output_linux`54 (authoritative output list per platform), `expected_input` (the predecessor YAML), `platform`,55 `prerequisite`.563. **Reference YAMLs** `ida_preprocessor_scripts/references/<module>/<predecessor>.{platform}.yaml` — the57 `disasm_code` + `procedure` carry the annotations `; 0xNN = Class::member` and58 `; 0xNN = Class::vfunc` / `// NNNN = 0xNN = …` at each access/call site. **These annotations are the semantic59 fingerprints** you translate into the fallback's anchors. Also collect any real-world helper or alternate60 inline/de-inline reference YAML that materially helps locate the targets.614. **Source-owned output YAMLs** `bin_artifacts/<gamever>/<module>/<target>.{platform}.yaml` — the62 **authoritative** offsets, vfunc indices, and signature styles the finder currently produces. Mine these for63 the reference values in the inventory table. Resolve an exact configured `<gamever>`; these files are tracked64 Git truth. Document their values as update- and platform-specific references, never as fixed answers.6566Cross-check every value across the reference annotation AND the ground-truth YAML; where they disagree, trust67the ground-truth YAML and note the discrepancy (references occasionally mis-annotate — see the decoy caution).6869## Workflow7071### Step 1 — Build the output inventory7273From the preprocessor `.py` + config entry, list every output as `(symbol, kind, platform, predecessor,74desired-fields)`. Determine `kind` from `GENERATE_YAML_DESIRED_FIELDS` using this table:7576| Kind | Tell-tale desired fields | Sig-gen skill | Writer skill |77|------|--------------------------|---------------|--------------|78| struct member | `struct_name, member_name, offset, offset_sig[, size, offset_sig_disp]` | `/generate-signature-for-structoffset` | `/write-structoffset-as-yaml` |79| indirect vcall (`call [reg+disp]`, no body) | `vfunc_sig, vfunc_offset, vfunc_index, vtable_name` and **no** `func_va`/`func_sig` | `/generate-signature-for-vfuncoffset` | `/write-vfunc-as-yaml` (`func_addr=None`, `func_sig=None`) |80| real vtable vfunc (has a body) | `func_va/func_sig` **and** `vtable_name/vfunc_offset/vfunc_index` | `/generate-signature-for-function` | `/write-vfunc-as-yaml` (+ vtable fields) |81| regular function | `func_name, func_sig, func_va, func_rva, func_size` (no vtable fields) | `/generate-signature-for-function` | `/write-func-as-yaml` |82| global variable | `gv_name, gv_va, gv_sig, gv_inst_*` | `/generate-signature-for-globalvar` | `/write-globalvar-as-yaml` |8384The indirect-vcall kind is easy to miss: its YAML has **no `func_va`** and its `vfunc_sig` is the signature of85the *call instruction itself* (e.g. `FF 90 A0 00 00 00` = `call qword ptr [rax+0A0h]`), not of any target86function body. Treat it as such — do not try to resolve a concrete implementation address.8788### Step 2 — Confirm platform gating and the predecessor8990From the config entry, record which outputs are cross-platform vs `expected_output_windows` /91`expected_output_linux`, and the predecessor(s) from `expected_input`. A symbol that is de-inlined into a92separate function on one platform but inlined on the other is common (that asymmetry is often *why* the finder93is fragile).9495### Step 3 — Extract per-target fingerprints9697For each target, read its access/call site in the reference `disasm_code` + `procedure` and write down:98- the **semantic anchor**: the nearest stable landmark — a string literal, a magic constant, a named99 global/interface call, a distinctive helper — that identifies the site independent of address;100- the **`this`-relative offset** (members) or **call displacement** (indirect vcalls) or **vtable index**;101- the **reference value** from the source-owned `bin_artifacts/` YAML (both platforms where they differ).102103### Step 4 — Write the fallback SKILL.md104105Create `.claude/skills/find-XXXX/SKILL.md` from the template below. Fill every placeholder; keep only the106target kinds that actually occur. The output filename MUST equal the finder's skill name so107`agent_runner.run_skill` finds it.108109The generated fallback MUST contain `## Realworld Function References` near the top, before the background.110List one exact repo-relative YAML path per bullet for every platform-relevant predecessor and useful111inline/de-inline helper or variant. Spell out `.windows.yaml` and `.linux.yaml` paths separately; do not use112`{platform}` shorthand in this section, because the agent must be able to open each reference directly. State113that addresses and offsets are reference-build values that still require verification against the current114binary.115116### Step 5 — Validate117118- `uv run python -m unittest discover -s tests -b` (guard; a pure-doc addition should not affect tests).119- Re-read the generated SKILL.md against the inventory: is every output listed, platform-gated, and mapped to a120 sig-gen + writer skill with correct params?121- Run the fallback itself in the real IDA/MCP environment, bypassing both old-version reuse and preprocessing:122123 ```bash124 uv run ida_analyze_bin.py -gamever <gamever> -oldgamever none -modules=<module> -debug -skip_pp -skill=<skill_name>125 ```126127 The selected game version must contain the module binary and every configured `expected_input`. Seed a128 checkout-external scratch artifact root from `bin_artifacts/<gamever>/`, remove the target outputs only in that129 scratch copy, and pass it with `-artifactdir` (plus tracked `bin_artifacts` as `-oldartifactdir`). Never delete or130 rewrite tracked expected artifacts merely to force the test. A run that says all outputs already exist and skips131 the skill is **not** a valid Agent-Skill-only test.132- Require the log to show `Agent Skill only mode: enabled (-skip_pp)`, `Skipping preprocess: <skill_name>133 (-skip_pp)`, and `Starting agent skill: <skill_name>`, followed by `Success` and summary `Failed: 0`. Verify134 that every expected YAML for the tested platform was produced and parses as a non-empty mapping.135136Do not report the fallback as complete unless this end-to-end Agent-Skill-only test passes. If the real IDA/MCP137environment is unavailable, report the validation as blocked rather than substituting a preprocessor run or a138ground-truth-only review.139140### Step 6 — Commit Changes to `dev`141142After the end-to-end fallback test has produced and verified its YAMLs, copy the validated canonical target outputs143from the isolated root into `bin_artifacts/<gamever>/<module>/`, include their computed downstream closure, and run the144repository artifact contract. Record the required one-line memory pointer145per the repo workflow. Ensure the delivery branch is `dev`; never commit directly to `main`. If the local `dev`146branch exists, switch to it. Otherwise, switch to `main` first and create `dev` from `main`:147148```bash149if git show-ref --verify --quiet refs/heads/dev; then150 git switch dev151else152 git switch main153 git switch -c dev154fi155```156157If any branch switch fails, stop and report the error. Review `git status --short`, then explicitly stage the new158fallback skill, source-owned artifact closure, and that memory update; never use `git add -A`:159160```bash161git add -- .claude/skills/find-XXXX/SKILL.md bin_artifacts/<gamever>/<module>/<target-yamls> <memory-pointer-path>162git diff --cached --name-only163```164165Stop if the staged-path list contains anything unrelated to this task. Commit only the staged task changes using166the repository commit format:167168```bash169git commit -m "feat(skills): add find-XXXX fallback" -m "Co-Authored-By: Codex <codex@openai.com>"170```171172Do not call `/create-pr`, push the branch, or open a pull request unless the user separately requests it. Finish by173reporting the commit hash, tested game version, unittest result, and Agent-Skill-only result.174175---176177## Fallback SKILL.md template178179Fill placeholders `<...>`; drop sections for kinds that do not apply.180181````markdown182---183name: find-XXXX184description: |185 Final-guarantee fallback for the find-XXXX preprocessor. Recovers <one-line summary of the targets> in CS2186 <module binaries> by decompiling <PREDECESSOR> and following de-inlined callees when a target is no longer187 accessed directly. Use when the deterministic/LLM preprocessor (ida_preprocessor_scripts/find-XXXX.py) could188 not resolve every target because a symbol was inlined or de-inlined in a way the LLM_DECOMPILE references do189 not cover.190 Trigger: <every target symbol, comma-separated>191disable-model-invocation: true192---193194# Find XXXX (final-guarantee fallback)195196Recover every symbol the find-XXXX preprocessor produces, in CS2 `<binary.dll>` / `<libbinary.so>`, using IDA197Pro MCP tools. This is the Agent fallback: it runs only when the preprocessor returned failure — which almost198always means a target's access pattern **moved** across the inline/de-inline boundary.199200## Realworld Function References201202Read the platform-relevant real-world YAMLs before searching in IDA. Treat their addresses and offsets as203reference-build values only; verify every result against the current binary.204205- `ida_preprocessor_scripts/references/<module>/<predecessor>.windows.yaml`206- `ida_preprocessor_scripts/references/<module>/<predecessor>.linux.yaml`207- `ida_preprocessor_scripts/references/<module>/<relevant-helper-or-variant>.windows.yaml`208- `ida_preprocessor_scripts/references/<module>/<relevant-helper-or-variant>.linux.yaml`209210Spell out each existing path literally and drop non-applicable placeholders or platforms.211212## Background — <PREDECESSOR> and what it wires up213214<2–5 sentences: what the predecessor does and which targets it touches, in source terms. Note that all member215accesses are relative to `this` (arg1: rcx/rsi on Windows, rdi/rbx on Linux) — the key to robustness.>216217## Robustness principle — follow the de-inline boundary218219For every target: (1) look for its access pattern inside `<PREDECESSOR>`; (2) if absent, it was de-inlined —220enumerate the functions `<PREDECESSOR>` calls, decompile the plausible ones, and search there (the helper221receives `this` as its first argument, so the same `this + offset` reappears; recurse a level or two);222(3) conversely a target the reference expected in a separate function may have been inlined back into223`<PREDECESSOR>`. Anchor each target by its semantic fingerprint (string / constant / neighboring call), never by224a fixed address or containing function.225226## Output inventory227228`struct_name` is `<STRUCT>` where applicable. Offsets/indices are **reference values from build <gamever> —229verify against the binary, do not assume**.230231| # | Output symbol | Kind | Windows | Linux | Writer skill |232|---|---------------|------|---------|-------|--------------|233| 1 | `<symbol>` | <kind> | `<value or "inlined — skip">` | `<value or "inlined — skip">` | `/write-...-as-yaml` |234| … | | | | | |235236Platform gating: <list cross-platform vs windows-only / linux-only outputs>.237238## Step 0. Skip targets already produced239240For each output, if `<name>.<platform>.yaml` already exists in the active artifact module directory and parses to a241non-empty mapping, skip it — the preprocessor or an earlier fallback wrote it. Use the analyzer-reported artifact242module directory; never derive the artifact path from the binary directory.243244`/get-func-from-yaml` also reports existence for functions/vfuncs.245246## Step 1. Load and decompile the predecessor247248**ALWAYS** Use SKILL `/get-func-from-yaml` with `func_name=<PREDECESSOR>` to get its `func_va`. If it errors,249**STOP** and report to user. Then:250251```252mcp__ida-pro-mcp__decompile addr="<PREDECESSOR.func_va>"253```254255Note the `this` register and keep the list of called functions for the de-inline search.256257## Step 2…N. Resolve each target258259<One subsection per target (or per cluster sharing a call site). For each: the semantic anchor, the260this-relative offset / call displacement / vtable index, the reference value, and how to handle de-inline. Note261any decoys.>262263## Signatures and YAML output264265<Per kind, the sig-gen skill + writer skill + exact params — copy from the kind table. E.g. struct members →266/generate-signature-for-structoffset → /write-structoffset-as-yaml with struct_name/member_name/offset/size=None/267offset_sig/offset_sig_disp; indirect vcalls → /generate-signature-for-vfuncoffset → /write-vfunc-as-yaml with268func_addr=None, func_sig=None, vfunc_sig, vtable_name, vfunc_offset, vfunc_index=offset/8.>269270## Failure handling271272- Predecessor YAML missing → **STOP** and report.273- A required target unresolved even after following callees → resolve the rest, then **STOP** and report exactly274 which output(s) failed so the user can extend the references.275- Never emit a platform-gated symbol on the wrong platform.276277## Output YAML filenames278279Written under the active artifact module directory, one per symbol: `<symbol>.windows.yaml` / `<symbol>.linux.yaml`.280````281282---283284## Robustness principles (the heart of a good fallback)2852861. **Anchor semantically, not positionally.** A fixed address or "it's in function F" breaks on the next update.287 A string literal, a magic constant (e.g. an FNV seed `0x811C9DC5`), a named interface call, or a distinctive288 helper survives.2892. **`this + offset` is stable across the inline boundary.** Struct offsets are relative to the class pointer290 (arg1). Whether the access is inlined in the predecessor or de-inlined into a helper that receives `this`,291 the same `this + offset` appears — so members are recoverable either way.2923. **Follow the callee (the core move).** If a target isn't in the predecessor, enumerate the predecessor's293 calls, decompile them, and recurse. This is what makes the fallback a *guarantee* rather than a re-run of the294 fragile reference match. Cover the inline-back case too.2954. **Know the indirect-vcall shape.** For `call qword ptr [reg + disp]` on an interface pointer, the output is296 `vtable_name` + `vfunc_offset = disp` + `vfunc_index = disp/8` + a `vfunc_sig` pinning the call instruction;297 there is **no `func_va`**. Resolve the interface from the receiver global's type.2985. **Watch for decoys.** References sometimes annotate two nearby offsets with the same member name. Trust the299 source-owned `bin_artifacts/` YAML. (Real example: `CEntitySystem::m_eNetworkSerializationMode` is the DWORD at300 `0xBBC`, set from the mode param and re-read at the SetNetworkSerializationContextData call — **not** the byte301 flag at `0xBDA`, even though the reference labels both.)3026. **The offset is the must-have; the signature is best-effort.** For struct members, `offset` is the required303 output; `offset_sig` is for relocation and may legitimately be omitted (write offset only) when a unique304 signature can't be found — especially for members whose access spans a function boundary.305306## Checklist307308- [ ] Read the target preprocessor `.py`, its config entry, its reference YAMLs, and its `bin_artifacts/` source-owned309 output YAMLs.310- [ ] Output inventory lists **every** symbol with kind + platform + predecessor + reference value.311- [ ] Fallback SKILL.md filename equals the finder's skill name.312- [ ] `## Realworld Function References` lists exact, directly openable repo-relative YAML paths for each313 relevant platform and inline/de-inline helper or variant; it contains no `{platform}` shorthand.314- [ ] SKILL.md is self-contained: enumerates all outputs, gates by platform, has the Step-0 skip-existing step.315- [ ] Each target has a semantic anchor and the follow-the-callee instruction; decoys are called out.316- [ ] Each kind is mapped to the correct sig-gen + writer skill with correct params (indirect vcalls use317 `func_addr=None`/`func_sig=None` + `vfunc_sig`).318- [ ] Failure handling + output-filename sections present.319- [ ] `unittest discover -s tests -b` passes.320- [ ] Values cross-checked against `bin_artifacts/` Git truth.321- [ ] Real Agent-Skill-only test passed with `uv run ida_analyze_bin.py -gamever <gamever> -oldgamever none322 -modules=<module> -debug -skip_pp -skill=<skill_name>`; the log proves preprocessing was skipped, the323 Agent Skill actually started, all expected YAMLs were produced, and the summary reports `Failed: 0`.324- [ ] The current branch is `dev` (created from `main` when it did not already exist).325- [ ] New SKILL.md, computed `bin_artifacts` closure, and memory pointer are explicitly staged and committed.326- [ ] `/create-pr` was not called; no push or PR was performed without a separate user request.327328## Worked example — `find-CEntitySystem_Init-decompiles`329330The canonical output of this workflow lives at331`.claude/skills/find-CEntitySystem_Init-decompiles/SKILL.md` (commit `a31fdf4`). Study it as the reference.332333- **Fragile foundation:** `ida_preprocessor_scripts/find-CEntitySystem_Init-decompiles.py` mines 11 targets by334 LLM_DECOMPILE off one predecessor, `CEntitySystem_Init`335 (reference `references/server/CEntitySystem_Init.{platform}.yaml`).336- **What broke:** on Windows 14168, `CEntitySystem_InitEntityMaterialAttributes` was de-inlined out of337 `CEntitySystem_Init`, so the `m_EntityMaterialAttributes` access left the predecessor and the LLM match338 failed, aborting the module.339- **What the fallback covers:** all 11 targets — 7 cross-platform struct members, 2 indirect-vcall vtable340 offsets (`INetworkMessages_SetNetworkSerializationContextData` @0xA0/idx20,341 `IFlattenedSerializers_CreateFieldChangedEventQueue` @0x118/idx35), Linux-only342 `CEntitySystem_ProcessEntityRegistration`, and Windows-only `m_EntityMaterialAttributes` (@0x2070) — each343 anchored by a fingerprint (the `"string_t_table"` call, the `CUtlScratchMemoryPool::Init(_, 0x400, …)` call,344 the FNV material-hash loop, …) and recoverable whether inlined or de-inlined.345- It demonstrates every section of the template: directly openable real-world function references, the346 inventory table, Step-0 skip, follow-the-callee, the indirect-vcall shape, the `0xBBC`-vs-`0xBDA` decoy note,347 and the offset-is-must-have caveat for the field-change trio.
Run npx skillmds@latest add hlnd2t/create-agent-skill-fallback in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Create an Agent SKILL.md fallback for an existing find-XXXX finder that relies on a fragile discovery foundation — above all LLM_DECOMPILE (patterns C/D/E), which matches the decompiled shape of a predecessor function against a stored reference and breaks when a symbol is inlined or de-inlined in a way the reference does not cover. The generated fallback coexists with the preprocessor and runs only when it returns failure, recovering every target robustly by decompiling the predecessor and following the inline/de-inline boundary with semantic anchors. Use when a finder broke on a game update, or you want to durably backstop one before it does. The recipe generalizes to any finder foundation. Triggers: create agent skill fallback, add SKILL.md fallback, robust fallback for finder, backstop LLM_DECOMPILE finder, final guarantee skill It is listed under Product & Planning on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: docs only. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
HLND2T (@hlnd2t) published this skill. Their other Agent Skills are listed on their SkillMD profile.