/m2ui — Metin2 UI Generator
<SUBAGENT-STOP> Dispatched as a subagent with a specific task? Skip mode detection and execute the task directly — the parent agent already loaded m2ui context and picked the mode. </SUBAGENT-STOP>
Mode Detection
Priority order:
- Explicit keyword: args start with
screenshot,talk,script, ordiagnose→ that mode - Image attached → screenshot mode
- Diagnose request: args say "check", "audit", "review", "diagnose", "find bugs in" → diagnose mode
- Symptom report: args contain a visible-bug phrase ("doesn't appear", "doesn't open", "doesn't work", "click does nothing", "X is broken", "looks broken", "leak", "crashes after", "stuck", "flickers") — even when a
.pyfile is also referenced → loadreference/failure-atlas.mdFIRST, diagnose via the matching symptom entry, THEN script mode (if a code fix is needed) or talk mode - File reference: args name a
.pyfile inuiscript/orroot/→ script mode - Text description: any other text → talk mode
- No args: ask — "(a) Create from screenshot, (b) Describe a new UI, (c) Modify an existing file, (d) Diagnose for bugs" — then dispatch
Read the matching mode file from modes/ adjacent to this SKILL.md (screenshot.md, talk.md, script.md, diagnose.md) and follow it.
Before Generating Any Code
Mandatory floor (always load):
reference/mental-model.md— ymir engine concepts; deprograms web/React assumptionsreference/event-binding.md— callback wrapping matrix
Conditional load (only what the task needs):
| Task | Load | |------|------| | New window from scratch | reference/anchors/README.md → walk its 2-step tree (see Anchor selection) | | Modifying an existing window | Skip anchors; load the existing files | | Widget you haven't used recently | reference/widgets.md — that widget's section | | Locale-heavy work (many new strings) | reference/locale.md | | C++ Python API (net.X, player.X, ...) not already in context | reference/bindings.md — grep for the function | | Patterns reminder (Initialize/Destroy, scrollbar wiring, ListBoxEx, integration template, lazy-load sub-windows, inner helper classes, 2D grids) | reference/patterns.md — relevant section | | User reports a visible symptom | reference/failure-atlas.md — matching symptom entry FIRST, before any anchor | | Visual style/sizing matters for a new window | reference/visual-conventions.md — pick archetype + chrome + palette before coding | | Wiring a window into the main interface | reference/integration.md (always — after every emission) | | Window has an OnUpdate body (animation / polling / fade / daily-event timing / movement queues / effect chains) | reference/timer-patterns.md |
Anchor selection (new windows): walk the 2-step decision tree in reference/anchors/README.md — pick exactly ONE primary archetype (the window's chrome; no exact match → closest; never skip this step), plus zero or more augmentors (05-feature-gated, 14-drag-and-drop, 15-network-coupled-flow, 16-tabbed-content, 22-compare-tooltip, 23-auto-hide-chrome). Read the primary FIRST; augmentors layer on top and never override the primary's lifecycle/structure. Tie-breaker: match the window's CHROME, not its data — "tabbed inventory" = primary 08-inventory-equipment + augmentor 16-tabbed-content, NOT 16 alone. For widgets.md/locale.md/bindings.md/patterns.md, load only the section you need, not the whole file.
Output Targets
| Output | Path | |--------|------| | uiscript dicts | pack/pack/uiscript/uiscript/ | | root UI classes | pack/pack/root/ | | locale strings | auto-detect — see reference/locale.md |
Critical Rules
All modes, all generated code. Reference files cite these by number — numbering 1-19 is frozen; new rules append.
@ui.WindowDestroyon everyDestroy(self)methodInitialize()or__Initialize()sets all instance vars toNone/defaultsDestroy()callsInitialize(); script-backed windows alsoClearDictionary()__del__callsui.ScriptWindow.__del__(self)- <EXTREMELY-IMPORTANT>
Callback wrapping — every callback that references self MUST use ui.__mem_func__(), SAFE_SetEvent (if fork provides it), or lambda r=proxy(self): r.X(). NEVER a bare bound method (btn.SetEvent(self.OnClick)) or self-capturing lambda (lambda: self.OnClick()) — both hold self alive past Destroy and leak. The single most common bug in community Metin2 code. Full matrix: reference/event-binding.md. </EXTREMELY-IMPORTANT>
Open()/Close()—OpencallsShow(),ClosecallsHide()OnPressEscapeKey()returnsTrue(always; notFalse)OnMouseWheel()returnsTrue/Falsebased on whether it consumed the event- No hardcoded strings — all user text via
localeInfo.oruiScriptLocale. constInfo.intWithCommas()for large numbers"not_pick"flag on decorative elements (lines, separators, background images)- Z-order: create widgets back-to-front (SetParent call order = render order)
- Parent bounds clip picking — size parents to contain all interactive children
- Python 2.7 target —
//for int division,innothas_key(), keepxrange. Full py2/py3 rules:reference/patterns.mdSection 8
<EXTREMELY-IMPORTANT>
- Asset paths must exist — verify every
d:/ymir work/ui/...path underD:\ymir work\ui\via Glob before referencing it. New asset needed → emit# TBD ASSET: <path> — needs creation; never invent (invented path = red-X/pink-box at runtime, failure-atlas entry 6). - Verified C++ APIs only — every call into
net,player,item,chr,app,wndMgr,chat,questmust exist inreference/bindings.md. Absent → ask the user OR stub with# TODO: verify <module>.<func> exists in your fork; never invent (invented binding =AttributeErrorcrash).
</EXTREMELY-IMPORTANT>
- <EXTREMELY-IMPORTANT>
Preserve existing Destroy bodies when adding @ui.WindowDestroy — add the decorator, NEVER strip the body. Pure assignments (self.X = None) are safe. Direct method calls on owned widgets (self.confirmDialog.Hide()) MUST be guarded with if self.X: — WOC nulls those attrs before the body runs. Inspect every helper the body calls (self.__Initialize(), self._Reset(), any name): defaults-only assignments are safe; widget derefs inside the helper need the same guards (or relocate to Close()). No guard needed for self.Hide(), self.ClearDictionary(), self.SetTop() — they touch only WOC-whitelisted attrs. Full whitelist + rationale: reference/patterns.md Section 5.11. </EXTREMELY-IMPORTANT>
- ASCII-only in emitted Python — new
.pycontent m2ui writes (code AND comments) is ASCII: no em/en-dash, ellipsis, curly quotes — use-,--,...,',". Pre-existing non-ASCII and verbatim user-supplied content stay untouched; locale data files exempt (seereference/locale.md). Reason: cp1252/cp949 build encodings. - <EXTREMELY-IMPORTANT>
**Verify setter accepts *args before Pattern B / Pattern E** — before emitting receiver.SetX(ui.__mem_func__(self.M), arg, ...) or SAFE_SetEvent(self.M, arg, ...) with extra args, READ the setter in pack/pack/root/ui.py. If it is 1-arg (def SetX(self, event):), the call raises TypeError at runtime. Fix: (a) augment the setter for *args per reference/framework-augmentations.md (preferred), or (b) fall back to Pattern C proxy lambda. Common 1-arg setters: EditLine.Set{Return,Escape,Tab}Event, SlotWindow.Set*Event. Never trust by name — verify the actual file. </EXTREMELY-IMPORTANT>
Pre-Emit Self-Review
<EXTREMELY-IMPORTANT> Mandatory gate BEFORE any output to the user or any file write, on every emission including edits. Every user-reported regression against this skill (leaks, missing decorators, off-screen widgets) traces back to a skipped self-