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
설치
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
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
disable-model-invocation
true
Create an Agent SKILL.md Fallback for a Finder
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.
Ground-truth output YAMLsbin/<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. <gamever> comes from .env (CS2VIBE_GAMEVER); bin/ is gitignored, so
read whatever versions exist. Document these as reference values that change per update and per platform —
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 ground-truth bin/ 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. Ensure at
least one expected output for each tested platform is absent; if all outputs already exist, use a disposable
game-version copy or temporarily back up the target outputs outside the binary directory and restore them
afterward. 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 — Run post-change gates
After the end-to-end fallback test has produced and verified its YAMLs, run these skills in order:
ALWAYS Use SKILL /post-change-update with phase=before-validation and the tested gamever.
ALWAYS Use SKILL /post-change-validation with the same gamever.
Only after validation succeeds, ALWAYS Use SKILL /post-change-update with
phase=after-validation and the same gamever.
These calls replace direct formatting, gamedata update, C++ validation, and snapshot-pack commands. If any gate
fails, stop the entire task and do not commit.
Step 7 — Commit
If on main, branch to dev first. Review git status --short, then stage the new SKILL.md, changed tracked
gamedata files, and gamesymbols/<gamever>.yamlexplicitly; never use git add -A. Commit with:
Then record a one-line memory pointer per the repo workflow.
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-linesummaryofthetargets> in CS2
<modulebinaries> 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: <everytargetsymbol, 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: <listcross-platformvswindows-only / linux-onlyoutputs>.
## Step 0. Skip targets already produced
For each output, if `<name>.<platform>.yaml` already exists beside the binary and parses to a non-empty mapping,
skip it — the preprocessor or an earlier fallback wrote it. List the directory with:
```
mcp__ida-pro-mcp__py_eval code="import idaapi, os; d=os.path.dirname(idaapi.get_input_file_path()); print('\n'.join(sorted(f for f in os.listdir(d) if f.endswith('.yaml'))))"
````/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<Onesubsectionpertarget (orperclustersharingacallsite).Foreach:thesemanticanchor, thethis-relativeoffset / calldisplacement / vtableindex, thereferencevalue, andhowtohandlede-inline.Noteanydecoys.>## Signatures and YAML output<Perkind, thesig-genskill + writerskill + exactparams — copyfromthekindtable.E.g.structmembers →
/generate-signature-for-structoffset → /write-structoffset-as-yamlwithstruct_name/member_name/offset/size=None/offset_sig/offset_sig_disp; indirectvcalls → /generate-signature-for-vfuncoffset → /write-vfunc-as-yamlwithfunc_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 beside the binary, 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
ground-truth bin/ 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/ ground-truth
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).
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.
/post-change-update phase=before-validation succeeds for the tested game version.
/post-change-validation succeeds for the same game version.
New SKILL.md, gamedata, and snapshot staged explicitly and committed on dev; memory pointer recorded.
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.