/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>
- <EXTREMELY-IMPORTANT> **GetChild name contract** — every `GetChild("X")` must match a REGISTERED name: a `"name" : "X"` in the target uiscript's `children` tree, or an explicit `InsertChild("X", widget)` on the code path. The root window dict's own `name` is NOT regi