Skip to content

Commit 4e6180a

Browse files
committed
docs: MaterialVariant и многофайловые скиллы во всей документации
Аудит после 7.10.0 и 7.11.0 нашёл девятнадцать мест, где документация врала или молчала. Разбор по существу. Самое важное — не человеческие доки, а текст, который читает МОДЕЛЬ. В агентском скилле Rbx API про MaterialVariant не было ни слова, а список создаваемых классов прямо утверждал, что любое другое имя даёт ошибку, — то есть модели сообщалось, что MaterialVariant создать нельзя. Фича делалась ровно для этой аудитории и оставалась ей невидима. Добавлены класс, сервис, свойство детали и рабочий пример; обе копии текста (Resources/AgentSkills/RbxApi и BuiltInRbxApiSkillText) правлены синхронно, побайтовое совпадение пинается тестом. Фраза про шесть упакованных CC0-наборов вместо тридцати шести протухла ещё в 7.9.0. В 7.10.0 я её правил и нашёл два файла из шести; остальные четыре — ROADMAP, DOCS_INDEX, Assets/CoreAI/Docs/README и агентский текст — нашлись только сейчас. Changelog Unity-пакета отстал на два релиза, хотя ведётся версия в версию с основным. Дописаны 7.10.0 и 7.11.0. MaterialVariant добавлен в списки создаваемых классов и свойств детали (RBX_API.md, RBX_API_SKILL.md, ROBLOX_API_ROADMAP.md), MaterialService — в перечисления сервисов в трёх файлах, durability вариантов — в WORLD_PACKAGE.md. По скиллам: FromFiles/FromTextParts/JoinInstructionParts в таблице API AGENT_BUILDER.md, поле additionalInstructionAssets в SCRIPTABLE_OBJECTS.md. Пункт бэклога про многофайловые скиллы переформулирован: сама многофайловость сделана, в остатке только структурное чтение по секциям — заголовок обещал работу, которой больше нет. Проверено отдельно и подтвердилось как чистое: нигде не написано, что материалы нельзя менять в рантайме, и нигде не утверждается, что скилл обязан быть прочитан до вызова его тулов. EditMode 3365 тестов, 3356 passed, 0 failed.
1 parent 91c2884 commit 4e6180a

15 files changed

Lines changed: 98 additions & 33 deletions

File tree

‎Assets/CoreAI/Docs/AGENT_BUILDER.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,10 @@ so the conversation stays clean. Returns `false` for a null store/skill or a bla
225225
| `ToolNames` | `string[]` of tool names |
226226
| `MergeToolNames(params SkillSet[])` | Merge names from multiple skills |
227227
| `FromFile(name, desc, path, tools)` | Create from file |
228+
| `FromFiles(name, desc, paths, tools)` | Create from several files, joined under `## name` headings |
228229
| `FromTextContent(name, desc, text, tools)` | Create from text |
230+
| `FromTextParts(name, desc, namedParts, tools)` | Create from several named parts, joined under `## name` headings |
231+
| `JoinInstructionParts(namedParts)` | Join named parts under `## name` headings |
229232
| `IJsonInvocableLlmTool` | Optional direct JSON invocation contract for skill proxy tools |
230233

231234
### Best practices — when to use skills vs direct tools

‎Assets/CoreAI/Docs/RBX_API.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ nil value".
3737

3838
## Classes
3939

40-
`Instance.new` accepts: **`Part`**, **`Folder`**, **`Model`**, **`ClickDetector`**,
40+
`Instance.new` accepts: **`Part`**, **`Folder`**, **`Model`**, **`ClickDetector`**, **`MaterialVariant`**,
4141
**`RemoteEvent`**, **`UnreliableRemoteEvent`**, and **`RemoteFunction`**. `Camera` is not
4242
creatable — the world's one camera is `workspace.CurrentCamera`.
4343
The class ancestry (`BasePart`, `PVInstance`, `WorldRoot`, …) is data-driven through `ClassCatalog`,
@@ -46,11 +46,11 @@ so `IsA` works the way it does in Roblox.
4646
Services reachable via `game:GetService`: `RunService`, `UserInputService`, `Players`,
4747
`CollectionService`, `TweenService`, `SoundService`, `Lighting`, `Debris`, `HttpService`,
4848
`ReplicatedStorage`, `ServerStorage`, `ServerScriptService`, `ContextActionService`,
49-
`PathfindingService`, `MarketplaceService`, `DataStoreService`, and CoreAI's own `AIService`.
49+
`PathfindingService`, `MarketplaceService`, `DataStoreService`, `MaterialService`, and CoreAI's own `AIService`.
5050

5151
### Part properties
5252

53-
`Name`, `Parent`, `Position`, `CFrame`, `Size`, `Color`, `Material`, `Orientation`, `Rotation`,
53+
`Name`, `Parent`, `Position`, `CFrame`, `Size`, `Color`, `Material`, `MaterialVariant` (string, `""` for none), `Orientation`, `Rotation`,
5454
`Anchored`, `Transparency`, `CanCollide`, and `Shape` (`Ball`, `Block`, `Cylinder`, `Wedge`,
5555
`CornerWedge` — each one materializes its own mesh). A `Part` materializes as a real GameObject as
5656
soon as its `Parent` is set into the world.

‎Assets/CoreAI/Docs/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Russian files are kept only when the filename is explicitly marked `_RU`.
3232
| [LESSON_ORCHESTRATION.md](LESSON_ORCHESTRATION.md) | Lesson/practice hooks: runtime context, tool policy, tests |
3333
| [LLM_ROUTING.md](LLM_ROUTING.md) | Execution modes, portable routing contracts, usage sinks, timeouts |
3434
| [LUA_SANDBOX_SECURITY.md](LUA_SANDBOX_SECURITY.md) | Lua sandbox boundary, removed APIs, execution limits, binding rules, and escape-test checklist |
35-
| [RBX_API.md](RBX_API.md) | Roblox-style API a mod builds with: `Instance.new`, datatypes, services, `BasePart.Material` / `Part.Color`, saving and loading a world, samples |
35+
| [RBX_API.md](RBX_API.md) | Roblox-style API a mod builds with: `Instance.new`, datatypes, services, `BasePart.Material` / `MaterialVariant` / `Part.Color`, saving and loading a world, samples |
3636
| [LUA_GAME_API.md](LUA_GAME_API.md) | Game Lua API reference: capabilities, mods, world, Full, LLM tools |
3737
| [LUA_BEST_PRACTICES.md](LUA_BEST_PRACTICES.md) | Best practices and anti-patterns for Lua in games |
3838
| [LUA_NATIVE_APIS.md](LUA_NATIVE_APIS.md) | Lua native APIs vs CoreAI wrappers |
@@ -48,7 +48,7 @@ Russian files are kept only when the filename is explicitly marked `_RU`.
4848
|------|--------|
4949
| [Docs/CoreAIMods/WORLD_PACKAGE.md](../../../Docs/CoreAIMods/WORLD_PACKAGE.md) | The `.world` package format, validation limits, manual slots vs. the autosave ring, the confirm/reject load flow, runtime session replacement |
5050
| [PROCEDURAL_MATERIALS.md](../../CoreAIMods/Runtime/RbxApi/Unity/PROCEDURAL_MATERIALS.md) | Procedural shader catalog behind `Enum.Material` (all 45 items + the magenta diagnostic fallback) |
51-
| [TEXTURE_MATERIALS.md](../../CoreAIMods/Runtime/RbxApi/Unity/TEXTURE_MATERIALS.md) | The six CC0 texture-backed materials and their projection/tint rules |
51+
| [TEXTURE_MATERIALS.md](../../CoreAIMods/Runtime/RbxApi/Unity/TEXTURE_MATERIALS.md) | The thirty-six CC0 texture-backed materials and their projection/tint rules |
5252

5353
## Maintenance Notes
5454

‎Assets/CoreAI/Runtime/Core/Features/AgentPrompts/BuiltInRbxApiSkillText.cs‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ by identity (`==`). Names AND numeric values are the real Roblox ones (e.g. KeyC
9494
## 4. Instances
9595
9696
- `Instance.new(""Class""[, parent])` — creatable classes are ""Part"", ""Folder"", ""Model"",
97-
""ClickDetector"", ""RemoteEvent"", ""UnreliableRemoteEvent"", ""RemoteFunction"". Any other name
97+
""ClickDetector"", ""RemoteEvent"", ""UnreliableRemoteEvent"", ""RemoteFunction"",
98+
""MaterialVariant"". Any other name
9899
errors (a Camera is not creatable — reach the world camera through `workspace.CurrentCamera`).
99100
The parent argument is deprecated (logs once); set `.Parent` after configuring instead.
100101
- `RemoteEvent`/`UnreliableRemoteEvent` fire one-way. `RemoteFunction:InvokeServer/InvokeClient`
@@ -107,7 +108,7 @@ MouseClick fires (no args) when the user clicks THAT part with the mouse and it
107108
`cd.MaxActivationDistance` studs (default 32). Only the part under the cursor fires.
108109
- `game:GetService(""Name"")` — the standard runtime has live tree-backed `Workspace`, `Lighting`,
109110
`ReplicatedStorage`, `ServerStorage`, `ServerScriptService`, `StarterPlayer`, `Players`,
110-
`HttpService`, `UserInputService`, and `RunService` services.
111+
`HttpService`, `UserInputService`, `RunService`, and `MaterialService` services.
111112
- The catalog's 13 registrations are: `HttpService` and `Players` (tree-backed), `RunService`
112113
(MVP2; fallback), `UserInputService` (MVP10; fallback), `TweenService` (MVP8),
113114
`CollectionService` (MVP8), `Debris` (MVP8), `DataStoreService` (MVP9),
@@ -145,14 +146,26 @@ needs WorldEdit.
145146
146147
Read+write (writes need WorldEdit): `Position`, `Size`, `CFrame` (Vector3/Vector3/CFrame),
147148
`Color` (Color3), `Transparency` (0..1), `Anchored` (bool), `CanCollide` (bool),
148-
`Shape` (Enum.PartType), `Material` (Enum.Material), `Orientation` and `Rotation` (Vector3 of
149+
`Shape` (Enum.PartType), `Material` (Enum.Material), `MaterialVariant` (string, """" for none),
150+
`Orientation` and `Rotation` (Vector3 of
149151
degrees; Orientation is YXZ, Rotation is XYZ).
150152
Setting `Position` keeps the part's rotation; setting `CFrame` sets position AND rotation;
151153
setting `Orientation` or `Rotation` sets the rotation and keeps the position.
152154
`Shape` accepts `Enum.PartType.Ball`, `.Block`, `.Cylinder`, `.Wedge`, `.CornerWedge` — every
153155
one of them materializes its real mesh.
154-
`Material` accepts any of the 45 `Enum.Material` items and all 45 render. Brick, Wood,
155-
WoodPlanks, Grass, Cobblestone and Metal use textured PBR surfaces; the rest are procedural.
156+
`Material` accepts any of the 45 `Enum.Material` items and all 45 render. Thirty-six of them
157+
use textured PBR surfaces shipped inside the package; the rest are procedural.
158+
`MaterialVariant` is how you use YOUR OWN textures instead of the built-in ones: create a
159+
`MaterialVariant` under `MaterialService`, point it at your texture paths, then name it on a part.
160+
local mv = Instance.new(""MaterialVariant"")
161+
mv.Name = ""MossyBrick""; mv.BaseMaterial = Enum.Material.Brick
162+
mv.ColorMap = ""MyGame/Textures/mossy_color"" -- a Resources path in your project
163+
mv.NormalMap = ""MyGame/Textures/mossy_normal""; mv.StudsPerTile = 8
164+
mv.Parent = game:GetService(""MaterialService"")
165+
wall.MaterialVariant = ""MossyBrick"" -- swap on; wall.MaterialVariant = """" swaps back
166+
A map you leave empty is inherited from `BaseMaterial`, so recolouring a surface is three lines.
167+
Editing a live variant repaints every part already wearing it. Naming a variant that does not
168+
exist renders the plain `Material` — that is not an error.
156169
A material id outside the enum resolves to a magenta diagnostic material instead of failing
157170
quietly, so a wrong value is visible at once.
158171
`Color` is an independent Roblox-style tint. A part whose `Color` was never assigned shows the

‎Assets/CoreAiUnity/CHANGELOG.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,39 @@ Unity host: **CoreAI.Source** build, EditMode / PlayMode tests, Editor menus, do
44

55
## [Unreleased]
66

7+
## [7.11.0] - 2026-09-04
8+
9+
### Fixed
10+
11+
- **Замена списка тулов роли молча отключала все её скиллы.** `SetToolsForRole` возвращал
12+
`read_skill` и `call_skill_tool` только из зарегистрированного живого каталога, а он появляется
13+
лишь при включённом авторстве скиллов — у агента, собранного обычным `WithSkill(...)`, оба прокси
14+
исчезали, и ни один тул скилла больше не вызывался. Ничего при этом не падало: пропавший тул
15+
возвращается обычным результатом, а не исключением, поэтому модель читала «not found», извинялась
16+
прозой и шла дальше.
17+
18+
### Added
19+
20+
- **`SkillSetAsset` принимает несколько файлов инструкций.** В инспекторе появился список
21+
дополнительных `TextAsset`: они идут за основным в порядке списка, каждый под заголовком
22+
`## имя-файла`. Скилл из одного файла сохраняет текст без изменений и заголовка не получает.
23+
- Тесты `SkillSetAssetInstructionsEditModeTests` и `SkillToolAvailabilityEditModeTests`: тул скилла
24+
вызывается без предварительного `read_skill`, а каталог в системном промпте не несёт тела
25+
инструкций.
26+
- **Агентский текст `Resources/AgentSkills/RbxApi` дополнен `MaterialVariant`** и приведён к правде
27+
по числу упакованных текстурных наборов — тридцать шесть, а не шесть.
28+
29+
## [7.10.0] - 2026-09-04
30+
31+
### Added
32+
33+
- **`MaterialVariant`: игра заводит свои материалы и меняет их на лету.** Экземпляр в
34+
`MaterialService` с `BaseMaterial`, четырьмя картами и `StudsPerTile`; деталь выбирает его строкой
35+
`BasePart.MaterialVariant`. Пути карт — обычные `Resources`-пути проекта. Варианты и ссылки на них
36+
переживают сохранение мира.
37+
- **Живой рендер-пруф в Play Mode.** `RbxMaterialVariantRenderPlayModeTests` фотографирует три слэба
38+
и требует, чтобы деталь с вариантом ушла от кирпича и встала рядом с травой.
39+
740
## [7.9.0] - 2026-09-04
841

942
### Added

‎Assets/CoreAiUnity/Docs/BACKLOG.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,16 @@ Items here are intentionally not active TODO checkboxes.
4646
4747
## Skill System (before MVP2)
4848

49-
- **Multi-file skills the model can browse structurally.** Today one `SkillSet` = one `Instructions`
50-
string, and `read_skill(name)` returns the whole blob. Add a third disclosure level like Claude Code
51-
(a main doc + `references/*` read lazily): a skill assembled from several files/sections, the model
52-
SEES the whole structure (index/table of contents) up front, and READS only the file/section it needs
53-
on demand (e.g. `read_skill_section(name, section)` or a sub-doc index). Lets big references (the Rbx
54-
API skill is already large) load in pieces instead of one dump.
49+
- **Structural browsing of a multi-file skill.** The assembly half shipped in 7.11.0: a `SkillSet` can
50+
take its `Instructions` from several sources via `FromFiles`/`FromTextParts`, joined under
51+
`## name` headings. What is still missing is the third disclosure level, as in Claude Code (a main
52+
doc plus `references/*` read lazily): the model SEES the structure — an index or table of contents —
53+
up front, and READS only the section it needs, e.g. `read_skill_section(name, section)`. Today
54+
`read_skill(name)` still returns the whole assembled document, so a large reference (the Rbx API
55+
skill is already large) arrives as one dump.
5556
- **Skill self-improvement.** A mechanism for a skill to update/augment its own instructions from
5657
experience (learned corrections, new patterns the agent discovered at runtime). Design after the
57-
multi-file work; both land before MVP2.
58+
structural-browsing work; both land before MVP2.
5859

5960
## UX / Mobile
6061

‎Assets/CoreAiUnity/Docs/DOCS_INDEX.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Package manifests:
7979
| [FIRST_MOD](../../CoreAI/Docs/FIRST_MOD.md) | Your first Lua mod in 5 minutes: load via agent/C#/TextAsset, persistence, sharing. |
8080
| [RBX_API](../../CoreAI/Docs/RBX_API.md) | The Roblox-style API a mod builds with: `Instance.new`, datatypes, services, `BasePart.Material` / `Part.Color`, saving and loading a world, bundled samples. |
8181
| [WORLD_PACKAGE](../../../Docs/CoreAIMods/WORLD_PACKAGE.md) | The `.world` package format, validation limits, manual slots vs. the autosave ring, the confirm/reject load flow, and runtime session replacement. |
82-
| [PROCEDURAL_MATERIALS](../../CoreAIMods/Runtime/RbxApi/Unity/PROCEDURAL_MATERIALS.md) · [TEXTURE_MATERIALS](../../CoreAIMods/Runtime/RbxApi/Unity/TEXTURE_MATERIALS.md) | The `Enum.Material` render catalogs: all 45 items render; a catalog-driven texture provider (six packaged CC0 sets, any item overridable from a project-local 2K–4K catalog via the Editor menus) plus procedural surfaces and the magenta diagnostic fallback. |
82+
| [PROCEDURAL_MATERIALS](../../CoreAIMods/Runtime/RbxApi/Unity/PROCEDURAL_MATERIALS.md) · [TEXTURE_MATERIALS](../../CoreAIMods/Runtime/RbxApi/Unity/TEXTURE_MATERIALS.md) | The `Enum.Material` render catalogs: all 45 items render; a catalog-driven texture provider (thirty-six packaged CC0 sets, any item overridable from a project-local 2K–4K catalog via the Editor menus) plus procedural surfaces and the magenta diagnostic fallback. |
8383
| [LUA_GAME_API](../../CoreAI/Docs/LUA_GAME_API.md) | Capabilities, mods, world API, Full tier, `execute_lua` / `manage_mods`, persistence & sharing. |
8484
| [LUA_BEST_PRACTICES](../../CoreAI/Docs/LUA_BEST_PRACTICES.md) | Do's and don'ts: slots, extensions, Lua-CSharp, LLM context. |
8585
| [LUA_NATIVE_APIS](../../CoreAI/Docs/LUA_NATIVE_APIS.md) | Native Lua-CSharp vs custom CoreAI code. |

‎Assets/CoreAiUnity/Docs/SCRIPTABLE_OBJECTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ CoreAI uses **Options + ScriptableObject wrapper** for Unity-authored settings.
2222
| `AiPermissionsAsset` | CoreAiUnity | `IAiPermissions`, `AiPermissionsOptions`, `ToOptions()`, `ApplyOptions(...)` | Dashboard/runtime code can use the interface/options. |
2323
| `LlmRoutingManifest` | CoreAiUnity | `LlmRouteTable`, `ToRouteTable()`, `ToOptions()` | Manifest stays Unity authoring storage. Runtime routing uses the portable route table. Backend construction may still use Unity profile entries in composition. |
2424
| `AgentPromptsManifest` | CoreAiUnity | `AgentPromptsDefinition`, `ToDefinition()` | Reads `TextAsset.text` into plain strings. Prompt providers consume the snapshot. |
25-
| `SkillSetAsset` | CoreAiUnity | `SkillSetDefinition`, `ToSkillDefinition()`, `ApplyDefinition(...)` | `TextAsset`/inline instructions become a portable skill definition; tools are supplied by code. `ApplyDefinition(...)` lets editor/bootstrap code create or update skill assets without private-field reflection. |
25+
| `SkillSetAsset` | CoreAiUnity | `SkillSetDefinition`, `ToSkillDefinition()`, `ApplyDefinition(...)` | `TextAsset`/inline instructions become a portable skill definition; tools are supplied by code. Extra instruction files in `additionalInstructionAssets` are joined in order under `## filename` headings. `ApplyDefinition(...)` lets editor/bootstrap code create or update skill assets without private-field reflection. |
2626
| `CoreAiPrefabRegistryAsset` | CoreAiUnity | `ICoreAiPrefabRegistry` | Not portable because it stores `GameObject` prefab references. Consumers depend on the Unity-side interface. |
2727

2828
## How to use from runtime code

‎Assets/CoreAiUnity/Resources/AgentSkills/RbxApi.txt‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ buttons live at 1000+).
7070
## 4. Instances
7171

7272
- `Instance.new("Class"[, parent])` — creatable classes are "Part", "Folder", "Model",
73-
"ClickDetector", "RemoteEvent", "UnreliableRemoteEvent", "RemoteFunction". Any other name
73+
"ClickDetector", "RemoteEvent", "UnreliableRemoteEvent", "RemoteFunction",
74+
"MaterialVariant". Any other name
7475
errors (a Camera is not creatable — reach the world camera through `workspace.CurrentCamera`).
7576
The parent argument is deprecated (logs once); set `.Parent` after configuring instead.
7677
- `RemoteEvent`/`UnreliableRemoteEvent` fire one-way. `RemoteFunction:InvokeServer/InvokeClient`
@@ -83,7 +84,7 @@ buttons live at 1000+).
8384
`cd.MaxActivationDistance` studs (default 32). Only the part under the cursor fires.
8485
- `game:GetService("Name")` — the standard runtime has live tree-backed `Workspace`, `Lighting`,
8586
`ReplicatedStorage`, `ServerStorage`, `ServerScriptService`, `StarterPlayer`, `Players`,
86-
`HttpService`, `UserInputService`, and `RunService` services.
87+
`HttpService`, `UserInputService`, `RunService`, and `MaterialService` services.
8788
- The catalog's 13 registrations are: `HttpService` and `Players` (tree-backed), `RunService`
8889
(MVP2; fallback), `UserInputService` (MVP10; fallback), `TweenService` (MVP8),
8990
`CollectionService` (MVP8), `Debris` (MVP8), `DataStoreService` (MVP9),
@@ -121,14 +122,26 @@ Pivot/property writes need WorldEdit. BaseParts also support `GetPivot()` and `P
121122

122123
Read+write (writes need WorldEdit): `Position`, `Size`, `CFrame` (Vector3/Vector3/CFrame),
123124
`Color` (Color3), `Transparency` (0..1), `Anchored` (bool), `CanCollide` (bool),
124-
`Shape` (Enum.PartType), `Material` (Enum.Material), `Orientation` and `Rotation` (Vector3 of
125+
`Shape` (Enum.PartType), `Material` (Enum.Material), `MaterialVariant` (string, "" for none),
126+
`Orientation` and `Rotation` (Vector3 of
125127
degrees; Orientation is YXZ, Rotation is XYZ).
126128
Setting `Position` keeps the part's rotation; setting `CFrame` sets position AND rotation;
127129
setting `Orientation` or `Rotation` sets the rotation and keeps the position.
128130
`Shape` accepts `Enum.PartType.Ball`, `.Block`, `.Cylinder`, `.Wedge`, `.CornerWedge` — every
129131
one of them materializes its real mesh.
130-
`Material` accepts any of the 45 `Enum.Material` items and all 45 render. Brick, Wood,
131-
WoodPlanks, Grass, Cobblestone and Metal use textured PBR surfaces; the rest are procedural.
132+
`Material` accepts any of the 45 `Enum.Material` items and all 45 render. Thirty-six of them
133+
use textured PBR surfaces shipped inside the package; the rest are procedural.
134+
`MaterialVariant` is how you use YOUR OWN textures instead of the built-in ones: create a
135+
`MaterialVariant` under `MaterialService`, point it at your texture paths, then name it on a part.
136+
local mv = Instance.new("MaterialVariant")
137+
mv.Name = "MossyBrick"; mv.BaseMaterial = Enum.Material.Brick
138+
mv.ColorMap = "MyGame/Textures/mossy_color" -- a Resources path in your project
139+
mv.NormalMap = "MyGame/Textures/mossy_normal"; mv.StudsPerTile = 8
140+
mv.Parent = game:GetService("MaterialService")
141+
wall.MaterialVariant = "MossyBrick" -- swap on; wall.MaterialVariant = "" swaps back
142+
A map you leave empty is inherited from `BaseMaterial`, so recolouring a surface is three lines.
143+
Editing a live variant repaints every part already wearing it. Naming a variant that does not
144+
exist renders the plain `Material` — that is not an error.
132145
A material id outside the enum resolves to a magenta diagnostic material instead of failing
133146
quietly, so a wrong value is visible at once.
134147
`Color` is an independent Roblox-style tint. A part whose `Color` was never assigned shows the

0 commit comments

Comments
 (0)