---
id: gh-msw-search
name: "msw-search"
url: https://skills.yangsir.net/skill/gh-msw-search
author: msw-git
domain: ai-llm-engineering
tags: ["information-retrieval", "api-documentation", "vector-search", "game-assets"]
install_count: 3200
rating: 4.40 (120 reviews)
github: https://github.com/msw-git/msw-ai-coding-plugins-official/tree/main/plugins/msw-maker-base-skill/skills/msw-search
---

# msw-search

> 此技能提供 MSW 平台的双重搜索能力。它通过向量搜索为开发者检索 API 文档、代码示例和实现指南，解决 `.d.mlua` 信息不足的问题。同时，该 Skill 也支持 REST API 搜索游戏资源，如精灵、动画、音效和头像，帮助用户快速获取 RUIDs 及相关资产。

**Stats**: 3,200 installs · 4.4/5 (120 reviews)

## Before / After 对比

### 开发者信息检索效率

**Before**:

开发者在查找 API 详细说明、代码示例或游戏资源 RUID 时，需要手动翻阅文档、论坛或资产库，耗费大量时间且效率低下，常因信息不全或不准确而导致开发受阻。

**After**:

此技能通过智能搜索，能迅速提供精确的 API 文档、代码示例和游戏资源 RUID，大幅减少信息检索时间，提升开发效率和准确性。

| Metric | Before | After | Change |
|---|---|---|---|
| 信息检索时长 | 60分钟 | 5分钟 | -91% |

## Readme

# MSW Search

MSW has **two distinct search targets**:

1. **API docs & implementation guides** — Vector search for descriptions, code examples, and related APIs missing from `.d.mlua`.
2. **Resources** — REST API for sprites, animations, sounds, resource packs, and avatars. The only path for obtaining RUIDs.

---

## Routing Table

| Request type | Go to section |
|--------------|---------------|
| "How do I implement this?", "Show me an example", "What related APIs exist?" | **Document search** |
| ".d.mlua only has the signature; the description is insufficient" | **Document search** |
| "I don't know the API name (semantic search)" | **Document search** |
| "Implementation guide / best practice / pattern" | **Document search** |
| "I need a SpriteRUID", "Find a sprite for monster / NPC / background" | **Resource search** → **start with `resource_pack`** |
| "Find an animation / sound / resource pack" | **Resource search** → **start with `resource_pack`** |
| "Details for this RUID", "Similar resources" | **Resource search** |
| "Avatar item / default avatar lookup" | **Resource search** |
| "Upload / list / update / delete my own assets" | Call `msw-mcp` `asset_*` tools directly (`account_get_my_user_id` first for `ownerId`) |

> **★ Resource search default — always `resource_pack` first**
>
> Unless the user **explicitly** asks for an individual sprite / animationclip / sound / avatar item (or names a non-pack RUID directly), pass `resourceTypeFilter: ["resource_pack"]` to `searchResources`. A pack bundles every sprite + animation + sound for one asset, so picking a stray `sprite` or `animationclip` first usually leaves the entity with a single frame, no animation set, or the wrong asset family.
>
> Search the pack → drill into `payload.elements` → assign individual RUIDs.
> Switch types only on explicit intent: "BGM file", "individual sprite only", "avatar item", "animationclip similar to this RUID", etc.

---

# Section 1 — Document Search (APIs & Guides)

Vector search via the **`msw-mcp`** MCP server. Supplies the **detailed descriptions, code examples, related APIs, and implementation guides** missing from `.d.mlua`.

## Decision Flow

```
Need API-related information
│
├─ Checking signature / type / property / enum
│   → Read .d.mlua first (highest priority)
│   → If .d.mlua is insufficient, call msw-mcp
│     (code examples, parameter details, related APIs, etc.)
│
├─ Implementation guide / pattern / best practice
│   → mlua_document_retriever
│
└─ Don't know the API name (semantic search)
    → mlua_api_retriever (and/or mlua_document_retriever for broader scope)
```

---

## API Research Order

### Priority 1 — .d.mlua (always first)

If you know the API name, **always read `.d.mlua` first.** Signatures, types, properties, event parameters, and enum values can be confirmed here accurately.

**Path**: `Environment/NativeScripts/{Component,Service,Event,Enum,Logic,Misc}/Name.d.mlua`

| Situation | Example |
|-----------|---------|
| Confirm method signature | "Does TransformComponent have SetPosition?" |
| Property type / existence | "What is the type of SpriteRendererComponent.RUID?" |
| Event parameter structure | "What are the AttackEvent constructor parameters?" |
| List of enum values | "What are the BodyMoveType values?" |
| Method existence | "What methods does SpawnService have?" |

### Priority 2 — Vector search (when .d.mlua is not enough)

`.d.mlua` contains only signatures and **lacks detailed descriptions and examples.** Use vector search when you need any of the following.

| Situation | MCP tool | Example query |
|-----------|----------|---------------|
| Need a **code example** | `mlua_api_retriever` | `AIComponent example`, `BehaviorTree usage` |
| **Parameter details** | `mlua_api_retriever` | `BadgeService GetBadgeInfosAndWait parameters` |
| **Related API** cross-references | `mlua_api_retriever` | `AttackComponent related`, `HitComponent` |
| **ScriptOverridable** check | `mlua_api_retriever` | `AttackComponent CalcCritical override` |
| **Don't know** the API name | both retrievers | `damage calculation`, `inventory save` |
| **"How do I …?"** implementation guide | `mlua_document_retriever` | `how to make inventory system` |
| **Pattern / best practice** | `mlua_document_retriever` | `collision detection best practice` |

---

## MCP Tools (`msw-mcp`)

| Tool | Description |
|------|-------------|
| **`mlua_api_retriever`** | API details for Service / Component / Misc etc. (signatures, parameters, examples). Pass an API/class/function/component name. |
| **`mlua_document_retriever`** | Authoring manuals, guidelines, MLua usage, and other document-style material. Pass a natural-language sentence describing what to implement. |

**On failure**: If a `msw-mcp` tool call errors out, surface the failure to the user and fall back to `.d.mlua`. Do not guess — state what you couldn't verify.

**Default result count**: request `3` results unless wider exploration is explicitly required.

---

## .d.mlua vs Search — Information Comparison

`.d.mlua` is a type stub (~29 lines); Search returns the full document (254+ lines).

| Information | .d.mlua | Search |
|-------------|:-------:|:------:|
| Method signature / types | **O** | O |
| Property declarations | **O** | O |
| Detailed method description (DetailDesc) | X | **O** |
| Code examples (AdditionalPageContent) | X | **O** |
| Per-parameter descriptions | X | **O** |
| Related APIs (SeeAlsoAPIs) | X | **O** |
| Related guides (SeeAlsoGuides) | X | **O** |
| ScriptOverridable flag | X | **O** |
| SyncDirection | Partial | **O** |
| Localized descriptions (Ko/Ja/Es/Zh) | X | **O** |

---

## Maker Editor Syntax → .mlua Conversion Rules

Code examples in search results use **Maker Editor syntax**. They must be converted before being used in a local `.mlua` file.

| Item | Maker Editor | .mlua file | Note |
|------|--------------|------------|------|
| Override declaration | `override integer CalcDamage(...)` | `method integer CalcDamage(...)` | `override` → `method` |
| Block | `{ ... }` | `... end` | Braces → `end` |
| Exec space (own method) | `[server only]` | `@ExecSpace("ServerOnly")` | Self-defined methods: annotate explicitly |
| Exec space (override) | `[server only]` shown / omitted in editor | **Match the parent's `@ExecSpace` exactly** — see warning below | LEA-3014 if mismatched |
| Property | `Property: int32 Score = 0` | `@Sync property int32 Score = 0` | Add `@Sync` if synced |
| Type `int` | `int` | `integer` | C# int → mlua integer |
| Type `number` | `number` | `number` | Same (double) |
| Type `float` | `float` | `float` | Same (single) |

> `number` (64-bit double) and `float` (32-bit single) are assignable to each other but remain distinct types. Follow the `.d.mlua` declaration.

> ⚠ **Override ExecSpace caveat — LEA-3014 `SignatureMismatch`**
>
> The Maker Editor often **hides** the parent's exec space and lets you toggle `[server only]` freely on an `override` block. In `.mlua`, however, the override's `@ExecSpace` must be **byte-identical** to the parent declared in `.d.mlua`. If the parent has no `@ExecSpace` (engine default = `ExecSpace=All`), the override must also **omit** `@ExecSpace` entirely.
>
> Concretely, the AttackComponent / HitComponent damage hooks (`CalcDamage`, `CalcCritical`, `GetCriticalDamageRate`, `GetDisplayHitCount`, `IsAttackTarget`, `IsHitTarget`, `OnAttack`) are all `ExecSpace=All` upstream. Adding `@ExecSpace("ServerOnly")` produces:
>
> ```
> [LEA-3014] SignatureMismatch : The signature of <Child>.CalcDamage[... (ExecSpace=ServerOnly)]
>   must match the overridden <Parent>.CalcDamage.[... (ExecSpace=All)].
> ```
>
> Always look up the parent in `.d.mlua` first and copy its annotation block verbatim. Detail: [`msw-scripting/SKILL.md` §9 "Method override → LEA-3014"](../msw-scripting/SKILL.md).

**Conversion example** — AttackComponent from search results:

```
-- Maker Editor syntax (search result)
override int CalcDamage(Entity attacker, Entity defender, string attackInfo) {
    return 50
}
override boolean CalcCritical(Entity attacker, Entity defender, string attackInfo) {
    return _UtilLogic:RandomDouble() < 0.3
}
```

```lua
-- Converted to .mlua
-- ⚠ Parent AttackComponent.CalcDamage / CalcCritical declare no @ExecSpace
--   (ExecSpace=All). Adding @ExecSpace here triggers LEA-3014 SignatureMismatch.
method integer CalcDamage(Entity attacker, Entity defender, string attackInfo)
    return 50
end

method boolean CalcCritical(Entity attacker, Entity defender, string attackInfo)
    return _UtilLogic:RandomDouble() < 0.3
end
```

---

# Section 2 — Resource Search (Sprite / Animation / Sound / Resource Pack / Avatar)

REST API for searching and browsing MSW resources.
Never guess or fabricate a RUID — **always obtain one through this API**.

> **Default search type = `resource_pack`** — see the pack-first rule under the Routing Table above.

## Access — always go through `msw_resource_api.cjs`

All resource-API calls in this skill are made through the Node.js wrapper

```
scripts/msw_resource_api.cjs
```

**Do not assemble curl commands by hand.** The wrapper:

- Sends UTF-8 JSON bodies directly, so non-ASCII queries (Korean / Japanese / Chinese / emoji) avoid the `{"detail":"There was an error parsing the body"}` failure mode that hits inline `curl -d '...'`.
- URL-encodes slash-containing path parameters (e.g. pack IDs like `npc/1013617.img`).
- Zero dependencies (Node 18+ built-in `fetch` / `AbortController`).
- Uses the **exact OpenAPI field names** (`topK`, `resourceTypeFilter`, `categoryFilter`, `count`, …). Legacy names like `limit` / `types` / `categories` are silently ignored by the server.

Two ways to use it:

```bash
# 1) CLI — fire one call from a shell. Output is pretty-printed JSON.
node scripts/msw_resource_api.cjs \
    search "orange mushroom" --resource-type resource_pack --category npc --topK 3

# Discover available subcommands:
node scripts/msw_resource_api.cjs --help
```

```js
// 2) require — preferred when already in a Node.js context.
const {
  searchResources, searchAvatarItems, findSimilarResources,
  getResource, getResourcesBatch, getResourceTags,
  listResources, randomResources, findPacksContaining,
  listAvatars, getAvatarDefaults,
} = require('./scripts/msw_resource_api.cjs');

const result = await searchResources("orange mushroom", {
  resourceTypeFilter: ["resource_pack"],
  categoryFilter: ["npc"],
  topK: 3,
});
```

## Wrapper function ↔ endpoint map

| Wrapper function | CLI subcommand | Endpoint |
|------------------|----------------|----------|
| `searchResources` | `search` | `POST /v3/search/resources` |
| `searchAvatarItems` | `search-avatar` | `POST /v3/search/resources` (avatar mode) |
| `findSimilarResources` | `similar` | `GET /v3/search/resources/similar/{ruid}` |
| `getResource` | `get` | `GET /v3/resources/{ruid}` (works for sprite / animationclip / resource_pack / avataritem) |
| `getResourcesBatch` | `batch` | `POST /v3/resources/batch` |
| `getResourceTags` | `tags` | `GET /v3/resources/tags/{ruid}` |
| `listResources` | `list` | `GET /v3/resources` (Qdrant Scroll, opaque-string `offset` cursor) |
| `randomResources` | `random` | `GET /v3/resources/random` |
| `findPacksContaining` | `packs` | `GET /v3/resources/packs/{ruid}` (lists packs **containing** a RUID — pack id is NOT accepted here) |
| `listAvatars` | `avatars` | `GET /v3/avatars` |
| `getAvatarDefaults` | `avatar-defaults` | `GET /v3/avatars/defaults` |

> **No `/v3/avatars/{ruid}` endpoint exists.** To inspect an avataritem
> (color_hex, group members, …), call `getResource(ruid)` — the
> `/v3/resources/{ruid}` endpoint returns avataritem detail just like
> any other resource.

## Base URL & transport (informational)

The wrapper handles all of this — you do not need to set it manually.

- Base URL: `https://maplestoryworlds-resourcesearch-new.nexon.com/api`
- No auth (public), `/v3/` prefix, POST bodies are `application/json; charset=utf-8`
- Default timeout: 15s (override via the wrapper's `_request(method, path, { timeout })`)

### Result count — this skill's default is **3**

Unless explicitly told otherwise, **always send `3`** for the result-count parameter
on every search call. The wrapper defaults to 3 as well, and parameter names follow
the OpenAPI spec exactly — note that `limit` / `count` / `topK` differ per endpoint.

| Endpoint | Server parameter | Wrapper default |
|----------|------------------|:---------------:|
| `POST /v3/search/resources` (resources + avatar) | `topK` | **3** |
| `GET /v3/search/resources/similar/{ruid}` | `topK` | **3** |
| `GET /v3/resources` (browsing) | `limit` | **3** |
| `GET /v3/resources/random` | `count` | **3** |
| `GET /v3/resources/packs/{ruid}` (packs containing a RUID) | `limit` | **3** |

> The server-side default is 20 or 50, so **always pass these parameters explicitly**.
> Increase to 10+ (or 50–100 for avatar broad-browse) only when wider exploration is explicitly required.

> **`offset` parameter caveat** — for `GET /v3/resources` and `GET /v3/resources/packs/{ruid}`,
> `offset` is **not an integer** but the **opaque string cursor `nextOffset` returned by the previous response**.
> Do not send it on the first page (sending integer `0` is interpreted as a cursor and returns empty results).

### POST body rule — let `msw_resource_api.cjs` handle it

If you must POST without the wrapper (no HTTP client in your language), reproduce its behaviour:

1. Serialize the body as **UTF-8 JSON bytes** (not a re-encoded shell string).
2. Send `Content-Type: application/json; charset=utf-8`.
3. POST raw bytes (e.g. curl's `--data-binary "@file"` reading a UTF-8 temp file).

Otherwise, just call the wrapper.

## Resource Types

`type` values (the `type` field on server responses, and the values you put
into the `resourceTypeFilter` array when searching):

| type | Description |
|------|-------------|
| `sprite` | Static image (PNG) |
| `animationclip` | Frame-based animation |
| `resource_pack` | Finished asset bundling sprites + animations + sounds |
| `bgm` | Background music (audio) |
| `voice` | Voice clip — NPC dialogue, etc. (audio) |
| `effect` | **Sound effect (audio).** Not a visual effect. For visual particles / hit / skill FX, search `sprite` or `animationclip` (categories `skill` / `mob` / `etc`). |
| `avataritem` | Avatar costume item (cap, coat, pants, shoes, weapon, …) — same `POST /v3/search/resources` endpoint with `resourceTypeFilter: ["avataritem"]`. See [`references/resource/search.md`](references/resource/search.md) ("Avatar Item Search") and [`references/resource/avatar.md`](references/resource/avatar.md). |

> All search and listing endpoints use the same type-filter field name: **`resourceTypeFilter`**
> (an array). Other names like `types` are silently ignored by the server.
> The wrapper's `resource_type_filter` argument (or CLI `--resource-type`) maps to this field.

> ⚠ **`SpriteRendererComponent.SpriteRUID` accepts both `sprite` and `animationclip`, but renders them differently:**
> - `animationclip` → all frame layers play (shadow + body + foreground)
> - `sprite` → that single Sprite renders only
>
> Symptom of mistake: feeding an `animationclip` RUID where you intended a `sprite` (or vice-versa) leaves only the shadow layer visible — the body silently vanishes. Always check `payload.type` of the response before assigning to `SpriteRUID`. Use `sprite` for the static idle/default frame; use `animationclip` only for fields like `StateAnimationComponent.ActionSheet` values.
>
> **`skeleton` and `avataritem` RUIDs fail silently (no error, nothing renders) when assigned to `SpriteRUID` / `ImageRUID` without the `thumbnail://` prefix.** Conversely, `CostumeManagerComponent.Custom*Equip` / `SkeletonRendererComponent.SkeletonRUID` / `StateAnimationComponent.ActionSheet` do **not** accept the `thumbnail://` prefix — pass a plain RUID there. If the search query targeted an icon / thumbnail image and returned a `sprite` RUID, that RUID is already renderable directly — adding `thumbnail://` is redundant. Full assignment rules — accepted types, slot-by-slot prefix matrix, RUID-vs-prefix usage — live in [`msw-sprite-ruid/SKILL.md`](../msw-sprite-ruid/SKILL.md).

## Categories

`category` values that actually appear on responses. Use these with `categoryFilter`.

### General resources (`sprite` / `animationclip` / `resource_pack` / `bgm` / `voice` / `effect`)

| category | Description |
|----------|-------------|
| `mob` | Monster |
| `npc` | NPC |
| `item` | Item |
| `skill` | Skill effect / skill resources |
| `object` | Map object (tree, rock, decoration) |
| `background` | Background / map tile / BGM |
| `foothold` | Walkable platform |
| `rope` | Rope |
| `ladder` | Ladder |
| `etc` | Uncategorized |

### Avatar (`avataritem` only)

| category | Slot |
|----------|------|
| `cap`, `hair`, `face`, `faceaccessory`, `eyeaccessory`, `earaccessory` | Head / face |
| `coat`, `longcoat`, `pants`, `shoes`, `glove`, `cape` | Body |
| `weapon`, `twohandweapon`, `subweapon`, `shield` | Weapon |

> `map`, `effect`, `ui` are **not** valid category values — they return zero results.
> - Looking for maps / backgrounds → `category: "background"` or `"object"`.
> - Looking for **visual effects** → search `sprite` / `animationclip` with `category: "skill"` (or `mob`/`etc`); `effect` is the **audio** resource_type, not a category.
> - There is no `ui` resource family in this index — UI sprites usually live as `sprite` + `category: "etc"`.

## RUID

A 32-character hex string that uniquely identifies every resource. Example: `"0017da7385e04bc4b2ddbe5949b4b462"`

- The `id` field in search results is the RUID
- `assetGuid` is a separate Unity asset GUID (used in `spawn_preset`)
- Never guess or fabricate a RUID — always obtain it from an API response

## Common Response Fields

```json
{
  "id": "32-char hex RUID",
  "type": "sprite|animationclip|resource_pack|bgm|voice|effect|avataritem",
  "category": "mob|npc|item|skill|object|background|foothold|rope|ladder|etc | <avatar slot>",
  "names": {
    "ko": ["Korean name"],
    "en": ["English name"]
  },
  "assetGuid": "Unity asset GUID (may or may not exist)",
  "payload": {
    "width": 64,
    "height": 64,
    "thumbnail": "https://...",
    "pivot": {"x": 32, "y": 32},
    "frames": [],
    "elements": []
  }
}
```

## Pagination — same name, two flavors

`nextOffset` appears in every list-style response but means **different things** depending on the endpoint. Round-tripping a value into the wrong endpoint silently misbehaves.

| Endpoint | `nextOffset` type | Meaning | How to paginate |
|---|---|---|---|
| `POST /v3/search/resources` (search) | **integer** | Item offset (0-based) | Pass it back as `offset` (number) |
| `GET /v3/search/resources/similar/{id}` (similar) | **integer** | Item offset | Same |
| `GET /v3/resources` (list) | **opaque UUID string** | Qdrant Scroll cursor | Pass the string back as `offset`. **End-of-stream = `null`** |
| `GET /v3/resources/packs/{ruid}` (packs) | **opaque UUID string** | Same cursor | Same |
| `GET /v3/resources/random` | n/a | No pagination | — |

**Rules:**

1. Never feed a `list` cursor into a `search` call (or vice versa) — the server ignores the wrong-shape value and returns the first page.
2. On the **first page**, omit `offset` entirely. Sending integer `0` to `list` / `packs` is interpreted as a cursor and yields **zero items** (silent failure).
3. Stop paginating when the response returns `nextOffset: null` (list / packs) or returns fewer items than `topK` (search / similar).

## Endpoint Summary

| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/v3/search/resources` | Natural-language semantic search (incl. avatar items via `resourceTypeFilter: ["avataritem"]`) |
| GET | `/v3/search/resources/similar/{ruid}` | Find similar resources |
| GET | `/v3/resources/{ruid}` | Single resource details (sprite / animationclip / **resource_pack with populated elements** / **avataritem**) |
| POST | `/v3/resources/batch` | Batch fetch multiple resources |
| GET | `/v3/resources/tags/{ruid}` | AI-generated multilingual tags |
| GET | `/v3/resources` | List resources (Qdrant Scroll, opaque-string `offset` cursor) |
| GET | `/v3/resources/random` | Random resource recommendation |
| GET | `/v3/resources/packs/{ruid}` | List resource packs **containing** the given RUID — the path parameter is a 32-char-hex RUID, not a pack id |
| GET | `/v3/avatars` | List all avatar items (cached) |
| GET | `/v3/avatars/defaults` | Default avatar body / head RUIDs |

> Single avataritem detail uses `/v3/resources/{ruid}` (no
> `/v3/avatars/{ruid}` endpoint exists).

---

## Resource Routing Guide

> **★ When in doubt, search `resource_pack` first.** Only the rows marked with an
> explicit non-pack intent below should bypass the pack-first default.

| Situation | Wrapper call (CLI subcommand) | Reference file |
|-----------|-------------------------------|----------------|
| "Find a slime / orange mushroom / monster / NPC / item / background / map asset" (default — no type specified) | `searchResources(query, { resourceTypeFilter: ["resource_pack"], ... })` (`search ... --resource-type resource_pack`) | [`references/resource/search.md`](references/resource/search.md) |
| "Find an **individual sprite** / single image" (user explicitly asked for a sprite) | `searchResources(query, { resourceTypeFilter: ["sprite"], ... })` | [`references/resource/search.md`](references/resource/search.md) |
| "Find an **individual animationclip**" (user explicitly asked for an animation) | `searchResources(query, { resourceTypeFilter: ["animationclip"], ... })` | [`references/resource/search.md`](references/resource/search.md) |
| "Find a **visual effect / particle / hit FX**" | `searchResources(query, { resourceTypeFilter: ["animationclip","sprite"], categoryFilter: ["skill","mob","etc"] })` — note: `effect` here would mean **audio**, not visual | [`references/resource/search.md`](references/resource/search.md) |
| "Find a **sound / BGM / voice / sound-effect**" (audio) | `searchResources(query, { resourceTypeFilter: ["bgm"\|"voice"\|"effect"], ... })` — `effect` resource_type = sound-effect (audio) | [`references/resource/search.md`](references/resource/search.md) |
| "Find a **background / map tile / scenery**" | `searchResources(query, { resourceTypeFilter: ["sprite","animationclip"], categoryFilter: ["background","object"] })` — there is no `map` category in the index | [`references/resource/search.md`](references/resource/search.md) |
| "Find a costume / hat / shoes / weapon (avatar item)" | `searchAvatarItems(...)` (`search-avatar`) | [`references/resource/search.md`](references/resource/search.md) (Avatar Item Search section) + [`references/resource/avatar.md`](references/resource/avatar.md) |
| "Any more monsters like this one?" | `findSimilarResources(ruid, ...)` (`similar`) | [`references/resource/search.md`](references/resource/search.md) |
| "Details for RUID abc123" (any type incl. avataritem and resource_pack) | `getResource(ruid)` (`get`) | [`references/resource/detail.md`](references/resource/detail.md) |
| "Show me a list of monster sprites" | `listResources(...)` (`list`) | [`references/resource/browse.md`](references/resource/browse.md) |
| "Which resource packs include this RUID?" | `findPacksContaining(ruid, ...)` (`packs`) | [`references/resource/browse.md`](references/resource/browse.md) |
| "Browse all avatar items" | `listAvatars(...)` (`avatars`) | [`references/resource/avatar.md`](references/resource/avatar.md) |

### Typical Workflow (pack-first)

```
1. searchResources(query, { resourceTypeFilter: ["resource_pack"], topK: 3 })
   → obtain a resource_pack RUID (or pack id like "npc/9072309.img")
   → switch types only on explicit user intent, or fall back when 0 packs match
2. getResource(id)
   → resource_pack: payload.elements is pre-populated with element payloads
                    (sprite / animationclip / sound RUIDs live here)
   → avataritem:    payload has color_hex / group meta
3. Pick the element from payload.elements and assign its RUID to
   SpriteRendererComponent.SpriteRUID / StateAnimationComponent.ActionSheet
   (or assign avataritem RUIDs through the slot mapping in `msw-avatar`)
```

> **Don't** call `findPacksContaining(packId)` to "open" a pack — that endpoint takes a 32-hex RUID and returns the **packs that include that RUID**, not the contents of a pack. Use `getResource(packId)` for pack contents.

> **For detailed Request/Response of each endpoint, refer to the files under `references/resource/`.**

---

## Sprite Orientation — Most Resources Face Left

Most MSW sprite / animationclip / resource_pack assets — especially `mob`, `npc`, and player-character — are authored **facing left**, so a freshly spawned `SpriteRendererComponent` renders left unless you flip it.

| Situation | What to do |
|-----------|-----------|
| Spawn an entity that should face **right** | Set `FlipX = true` on `SpriteRendererComponent` (default is `false` = left-facing as authored) |
| Custom AI / chase using `MovementComponent:MoveToDirection` | Update `FlipX` on direction change: `sprite.FlipX = velocity.x > 0` (right ⇒ flip) |
| Monster model / monster collider alignment | Invert `TransformComponent.Scale.x` instead of `FlipX` so the sprite and collider stay aligned; see [`msw-general/references/monster.md`](../msw-general/references/monster.md) |
| Native `AIChaseComponent` / `AIWanderComponent` | Engine flips automatically based on movement — do nothing |
| Top-down (`RectTile`) movement | Decide per-axis: usually flip when `dx > 0`; sprites with up/down frames need the StateAnimationComponent action set instead |
| `_EffectService:PlayEffect(...)` should face right | Pass `FlipX = true` in the `options` table |
| Player-attached effect must follow the player's facing | Use `SyncFlip = true` in `PlayEffect` options, or read `PlayerControllerComponent.LookDirectionX` |
| Resource is authored facing right (rare) | Inspect `payload.thumbnail` via `GET /v3/resources/{ruid}` and invert the rule for that asset |

```lua
-- Custom side-view chase: flip sprite to match movement direction
local sprite = self.Entity.SpriteRendererComponent
local selfX = self.Entity.TransformComponent.WorldPosition.x
local dx    = targetPos.x - selfX
if dx ~= 0 then
    sprite.FlipX = dx > 0   -- target on the right → flip
end
```

> **Sanity check** — the left-facing convention is not contractual. Open `payload.thumbnail` from `GET /v3/resources/{ruid}` to confirm.
>
> **Do not use `TransformComponent.Scale.x` as a general renderer flip** — for players / effects / non-monster renderers, use `SpriteRendererComponent.FlipX`. **Monster exception**: monster models should invert `TransformComponent.Scale.x` so the sprite and collider stay aligned. Related: [`msw-combat-system/SKILL.md` "Direction check ★"](../msw-combat-system/SKILL.md), [`msw-general/references/monster.md`](../msw-general/references/monster.md).

---

# Shared Tips

1. **Keyword choice** — Use the exact name if you know it; natural-language Korean/English also works.
2. **Adjust the page-size parameter** — the name differs per endpoint (`topK` for search/similar, `limit` for list/packs, `count` for random). **This skill's default is 3** (see the "Result count" table above). Keep it at 3 for precise lookups; increase to 10+ (or 50–100 for avatar broad-browse) only when wider exploration is required.
3. **When search fails**:
   - Document search fails → read `.d.mlua` directly.
   - Resource search fails → retry with synonyms or a different category; browse with `listResources(...)` (CLI: `list`) by type/category.
   - `POST` returns `{"detail":"There was an error parsing the body"}` → you bypassed
     the wrapper and sent JSON inline via `curl -d '{...}'`. Switch to
     `msw_resource_api.cjs` (or replicate its UTF-8 raw-body POST pattern) as
     described in Section 2.
4. **Composite queries are allowed** — e.g. `AttackComponent CalcCritical` for docs, `red slime jump` for resources.
5. **No guessing** — Never guess API names, RUIDs, or enum values; always confirm via search or references.


---
*Source: https://skills.yangsir.net/skill/gh-msw-search*
*Markdown mirror: https://skills.yangsir.net/api/skill/gh-msw-search/markdown*