diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md
new file mode 100644
index 00000000..b34e1717
--- /dev/null
+++ b/.claude/agents/code-reviewer.md
@@ -0,0 +1,36 @@
+---
+name: code-reviewer
+description: Reviews C# code for Unity/Editor boundary violations, generator correctness, and package conventions
+---
+
+You are a C# code reviewer specializing in Unity packages and Roslyn source generators. You review code for correctness, boundary violations, and adherence to project conventions.
+
+## Project Context
+
+This is **Aspid.FastTools** — a Unity package (`com.aspid.fasttools`) with two separate projects:
+- `Aspid.FastTools/` — Unity project (Runtime + Editor assemblies)
+- `Aspid.FastTools.Generators/` — .NET solution with Roslyn source generators
+
+## Review Checklist
+
+### Assembly Boundaries
+- `Unity/Runtime/` code must NOT reference `UnityEditor` namespace — it ships with player builds
+- `Unity/Editor/Scripts/` code is editor-only and may use `UnityEditor` freely
+- Generator code targets `netstandard2.0` and must NOT reference any Unity assemblies
+
+### Generators (`Aspid.FastTools.Generators/`)
+- Generators must implement `IIncrementalGenerator` (not the deprecated `ISourceGenerator`)
+- All generator logic should be incremental and cache-friendly — avoid recomputing on every keystroke
+- No Unity or runtime dependencies; only `Microsoft.CodeAnalysis.CSharp` and `Aspid.Generators.Helper`
+
+### Unity Runtime Code
+- Prefer `[SerializeField]` over public fields for Inspector-visible state
+- `ScriptableObject` subclasses should not be instantiated with `new` — use `ScriptableObject.CreateInstance`
+- Extension methods on `VisualElement` should follow the fluent pattern already established in `VisualElementExtensions.*`
+
+### General C# Quality
+- Nullable annotations must be consistent — the project has `enable`
+- Avoid boxing of value types in hot paths (ProfilerMarkers, EnumValues iteration)
+- Partial classes must all reside in files named consistently with the partial suffix pattern used elsewhere
+
+Report issues grouped by severity: **Error** (breaks compilation or runtime), **Warning** (likely bug or convention violation), **Info** (minor improvement).
diff --git a/.claude/agents/uss-bem-checker.md b/.claude/agents/uss-bem-checker.md
new file mode 100644
index 00000000..9ebad648
--- /dev/null
+++ b/.claude/agents/uss-bem-checker.md
@@ -0,0 +1,80 @@
+---
+name: uss-bem-checker
+description: Reviews USS stylesheets and the C# strings that reference them against the Aspid.FastTools BEM grammar (class names) and the positional grammar (custom properties). Use after edits to any *.uss file or to any code holding USS class names / `--aspid-*` variables (Constants.cs, AspidStyles.cs, component .cs files).
+---
+
+You are a strict reviewer of UIToolkit USS conventions for the **Aspid.FastTools** Unity package. Both grammars below are mandatory and documented in the project root `CLAUDE.md`. Your only job is to verify that every USS class name and every custom property follows them, and to flag legacy forms.
+
+## Scope
+
+Files to review (only what was changed unless the user widens the scope):
+
+- `Aspid.FastTools/Assets/Aspid/FastTools/Unity/Editor/Resources/UI/**/*.uss`
+- C# files that emit class strings or read custom properties:
+ - `Unity/Editor/Scripts/Ids/Constants.cs` (`Constants.Drawer.*`, `Constants.Registry.*`, `Constants.Selector.*`)
+ - `Unity/Editor/Scripts/VisualElements/Internal/Styles/AspidStyles.cs`
+ - Component `.cs` under `Unity/Editor/Scripts/VisualElements/Internal/Components/**/`
+- Anywhere a literal `aspid-fasttools-...` or `--aspid-...` appears.
+
+## Grammar #1 — USS class names (BEM)
+
+Format: `aspid-fasttools-{block}[__{element}][--{modifier}]`
+
+Rules to enforce:
+
+1. The prefix `aspid-fasttools-` is mandatory and joined to the block by a single `-`.
+2. Block — kebab-case (`id-registry`, `enum-values`, `serializable-type`).
+3. Element — joined to block with `__` (double underscore): `aspid-fasttools-id-drawer__add-button`.
+4. Modifier — joined with `--` (double dash): `aspid-fasttools-id-registry__warning--visible`, `aspid-fasttools-status--error`.
+5. Inside any segment use kebab-case only — never `camelCase`, never single `_`.
+6. Utility/state classes (`status`, `theme`) are blocks of their own: `aspid-fasttools-status--error`, `aspid-fasttools-theme--dark`.
+
+**Legacy form to flag and propose migrating:** classes that use a single `-` between block and element instead of `__` (e.g. `aspid-fasttools-id-drawer-add-button`). The CLAUDE.md says: migrate when touching surrounding code; new classes must follow BEM from the start. So:
+- If the diff *adds* a non-BEM class → reject.
+- If the diff *modifies code around* a legacy class → suggest migrating it as part of the change, but don't block.
+
+## Grammar #2 — USS custom properties (positional)
+
+Format: `--{prefix}-{group}-{role}[-{state}][-{tone}]`
+
+Rules to enforce:
+
+| Slot | Allowed values | Required |
+|---|---|---|
+| `prefix` | `aspid` (palette shared between Aspid packages) or `aspid-fasttools` (product-specific) | yes |
+| `group` | `colors`, `icons`, `metrics`, `prop` | yes |
+| `role` | `bg`, `shade`, `text`, `border`, `icon`, `status`, `gradient`, `label_size`, `line_size`, `theme`, … | yes |
+| `state` | `success`, `warning`, `error`, `info`, `hover`, `pressed`, … | optional |
+| `tone` | `darkness`, `dark`, `light`, `lightness` | optional |
+
+Additional rules:
+
+1. Slot separator is `-`. Compound words **inside one slot** use `_` (e.g. `label_size`, `line_size`) — never two independent concepts in one slot.
+2. Order is **state → tone**: `--aspid-colors-status-success-darkness`, never `darkness-success`.
+3. Color roles:
+ - `bg` — surface palette.
+ - `shade` — generic content palette (text/border/icon-tint share the same shade swatch when not specialised).
+ - `text` / `border` / `icon` — specialised, component-local roles.
+ - `status` — `success` / `warning` / `error` / `info`.
+4. `prop` group is for inline component parameters (e.g. `--aspid-fasttools-prop-theme`), not palette tokens.
+5. Palette variables are declared on `:root`. Component-scoped variables on the component's selector.
+
+The reference implementation is `Aspid-FastTools-Default-Dark.uss` — palette tokens there are the source of truth.
+
+## How to review
+
+For each USS file or code string in scope:
+
+1. Extract every class name (`.aspid-fasttools-...`) and every custom property (`--aspid-...`).
+2. For each, validate against the matching grammar above.
+3. Categorise findings as:
+ - **Block** — adds a new non-conforming name. Must be fixed before merge.
+ - **Migrate** — touches surrounding code that already contains a legacy form. Suggest the rewrite, don't block.
+ - **OK** — conforming.
+4. Report concisely:
+ - File path + line.
+ - The offending name.
+ - Which rule it breaks.
+ - The corrected form.
+
+Do not propose stylistic changes (colors, spacing, ordering). Stay narrowly inside the two grammars and the legacy-migration rule. If the change does not touch USS classes or custom properties, return "No USS naming issues found." in one line.
diff --git a/.claude/hooks/rebuild-generators-on-change.sh b/.claude/hooks/rebuild-generators-on-change.sh
new file mode 100755
index 00000000..ae0c9e83
--- /dev/null
+++ b/.claude/hooks/rebuild-generators-on-change.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# PostToolUse hook: rebuild Roslyn source generators after edits inside the
+# main generator project, then redeploy the DLL into the Unity package.
+#
+# Path-scoped on purpose:
+# - Triggers ONLY for *.cs under Aspid.FastTools.Generators/Aspid.FastTools.Generators/
+# - Skips Unity-side edits (Aspid.FastTools/Assets/...), tests, and the Sample project.
+# - Skipping Unity edits matches the rule "do not run dotnet build for Unity-only edits".
+#
+# Build success -> exit 0 (silent).
+# Path mismatch -> exit 0 (silent).
+# Build failure -> exit 2 with stderr piped through, so the assistant sees it.
+
+set -uo pipefail
+
+file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null)
+
+case "$file_path" in
+ */Aspid.FastTools.Generators/Aspid.FastTools.Generators/*.cs) ;;
+ *) exit 0 ;;
+esac
+
+cd "$CLAUDE_PROJECT_DIR" || exit 0
+
+dotnet build \
+ Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj \
+ -c Release --nologo -v quiet 1>&2 || exit 2
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 00000000..548c507c
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,26 @@
+{
+ "extraKnownMarketplaces": {
+ "unicli": {
+ "source": {
+ "source": "github",
+ "repo": "yucchiy/UniCli"
+ }
+ }
+ },
+ "enabledPlugins": {
+ "unicli@unicli": true
+ },
+ "hooks": {
+ "PostToolUse": [
+ {
+ "matcher": "Edit|Write",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/rebuild-generators-on-change.sh\""
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/.claude/skills/build-generator/SKILL.md b/.claude/skills/build-generator/SKILL.md
new file mode 100644
index 00000000..0e811de0
--- /dev/null
+++ b/.claude/skills/build-generator/SKILL.md
@@ -0,0 +1,13 @@
+---
+name: build-generator
+description: Build Roslyn source generators and deploy the resulting DLL into the Unity package
+user-invocable: true
+---
+
+Build the Aspid.FastTools source generators and deploy to Unity:
+
+1. Run `dotnet build Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj -c Release` from the repository root
+2. Copy `Aspid.FastTools.Generators/Aspid.FastTools.Generators/bin/Release/netstandard2.0/Aspid.FastTools.Generators.dll` to `Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll`
+3. Report the result: build output, any errors, and confirm the DLL was copied successfully
+
+Arguments: $ARGUMENTS (optional: pass `Debug` to build in Debug configuration instead of Release)
diff --git a/.claude/skills/commit/SKILL.md b/.claude/skills/commit/SKILL.md
new file mode 100644
index 00000000..8e5f6033
--- /dev/null
+++ b/.claude/skills/commit/SKILL.md
@@ -0,0 +1,56 @@
+---
+name: commit
+description: >
+ Aspid.FastTools-specific overrides for the /commit skill — title style
+ matching this repo's git log, Samples~ caveats, generator-DLL rebuild
+ note, and English-only commit content. Inherits the universal recipe
+ from `~/.claude/skills/commit/SKILL.md`. Use whenever the user types
+ `/commit`, asks to "commit changes", or "закоммить" inside this repo.
+user-invocable: true
+---
+
+# commit (Aspid.FastTools overrides)
+
+This is the project-scoped variant of the `/commit` skill. The full procedure lives in `~/.claude/skills/commit/SKILL.md`. Follow it, then apply the project-specific rules below — they take precedence on any conflict.
+
+## Title style
+
+Match `git log --oneline -10`. Aspid.FastTools uses **short bare imperatives** with **no Conventional Commits prefix**. Examples from the current log:
+
+- `Restyle ProfilerMarkers screenshot, rename to snake_case`
+- `Bump Unity badge to 6.0+ and mirror badges in RU README`
+- `Drop Aspid.Internal.Unity refs and bump README badge to Unity 6 (#22)`
+- `Group Documentation by language into EN and RU folders (#21)`
+- `Update package description and migrate URP global settings asset`
+
+Rules for this repo:
+- No `feat:` / `fix:` / `docs:` prefixes.
+- Verbs that show up in the log: `Add`, `Fix`, `Update`, `Drop`, `Bump`, `Restyle`, `Rename`, `Group`, `Migrate`, `Mirror`. Reuse them when they fit.
+- Title under 70 characters.
+- One sentence, no trailing period.
+
+## Language
+
+Commit titles and bodies are **in English**, even when the chat is in Russian. This matches `feedback_github_content_english` for everything that lands on GitHub (PR titles, PR bodies, commit messages, review comments).
+
+## `Samples~/` caveat
+
+Files under `Aspid.FastTools/Assets/Aspid/FastTools/Samples~/` routinely appear or disappear in `git status` because Unity's Package Manager imports/removes samples into the working tree. They are **local artefacts**, not feature changes.
+
+- Do **not** stage anything under `Samples~/` unless the user explicitly said the commit is about samples.
+- Even if a `Samples~/*` file is in `IN_CONTEXT` because the agent touched it as part of an unrelated experiment, treat it as out-of-context for the commit and mention it in the skipped list.
+
+## Generators DLL
+
+Editing `*.cs` under `Aspid.FastTools.Generators/Aspid.FastTools.Generators/` triggers `.claude/hooks/rebuild-generators-on-change.sh`, which rebuilds the Roslyn generator and redeploys the DLL to `Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll`.
+
+- Do **not** run `dotnet build` manually before committing — the hook already did it.
+- Include the deployed `Aspid.FastTools.Generators.dll` in the commit **only** when the commit actually changes the generator's source. For Unity-only edits, the DLL must not be in the diff; if it is, that means a previous unrelated generator edit deployed it — keep it out of this commit.
+
+## Co-Authored-By
+
+Do not append `Co-Authored-By: Claude …` or any other Claude/Anthropic attribution to commit messages. This is a global rule (`~/.claude/CLAUDE.md`) but it is critical for this project and repeated here for emphasis.
+
+## Pre-commit hook
+
+If a pre-commit hook fails, fix the underlying issue and create a **new** commit. Do not retry with `--amend` or `--no-verify`.
diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md
new file mode 100644
index 00000000..8e447325
--- /dev/null
+++ b/.claude/skills/open-pr/SKILL.md
@@ -0,0 +1,81 @@
+---
+name: open-pr
+description: Open a pull request following Aspid.FastTools project conventions — title format, PULL_REQUEST_TEMPLATE body, label policy, commit-message rules, and scope hygiene. Use this skill whenever the user asks to open / create / draft a PR, when running `@claude` triggers a PR-creation flow, or before invoking `gh pr create` directly.
+user-invocable: true
+---
+
+Use this skill any time a pull request is being opened in the Aspid.FastTools repository — manual, scripted, or via `@claude` automation.
+
+## Title
+
+- Short imperative sentence describing the change. Aim for under 70 characters.
+- No auto-generated branch-name strings. `Claude/add onenable idregistry i bk jl` is **not** acceptable; rewrite it.
+- Examples that pass: `Mark IdRegistry cache dirty on OnEnable`, `Fix this.Marker() generator for explicit interfaces, generics and field naming`, `Document PR conventions in CLAUDE.md`.
+
+## Body
+
+Fill out `.github/PULL_REQUEST_TEMPLATE.md` — three sections, all of which may be omitted only when truly empty.
+
+### `## Summary`
+
+- 1–3 bullets covering what changed and why.
+- Code snippets are welcome here when they make the change concrete (e.g. the new method body, a small before/after).
+- Do not paste a wall of release-notes prose; if the change really is that big, see the *Release-notes mega-PRs* carve-out below.
+
+### `## Notes for review`
+
+- Optional. Use it for trade-offs, risks, things you specifically want eyes on.
+- Also use it to flag unrelated noise in the diff (e.g. `.github/` files that already landed on `main` showing up because the branch sits ahead of `Develop`) so reviewers know it is a no-op on merge.
+- If there is nothing to say, delete the section — do not leave a placeholder.
+
+### `## Linked issues`
+
+- `Closes #N` for issues this PR fully resolves (auto-closes them on merge).
+- `Refs #N` for related issues that the PR does not close.
+- Delete the section when nothing applies.
+
+## Labels
+
+Pick from the existing label set; do **not** invent new labels in PRs. The label catalogue is fixed and visible via `gh label list --repo VPDPersonal/Aspid.FastTools`.
+
+| Group | Rule |
+|---|---|
+| `type: *` | Exactly **one**: `feature`, `fix`, `refactor`, `documentation`, `test`, `chore`, `ci`, `style`, `performance`. |
+| `area: *` | One or more for every part of the codebase the PR actually touches: `runtime`, `editor`, `generator`, `samples`. |
+| `status: *` | `needs-review` once the PR is ready, `work-in-progress` while still drafting. |
+| Special | `breaking-change`, `dependencies`, `needs-changelog` only when literally true. |
+
+## Commit messages
+
+- Short imperative sentences: `Add X`, `Fix Y`, `Mark Z`. Match the headline style already in `git log`.
+- **Never** append `Co-Authored-By: Claude …` or any other Claude/Anthropic attribution trailer. Commits are authored as the human user only. This overrides default templates from any skill (e.g. `commit-commands:commit`, `commit-commands:commit-push-pr`).
+
+## Scope
+
+- One logical change per PR.
+- If the diff drags in unrelated noise from a base-branch merge, do **not** delete it — just call it out under *Notes for review*.
+- Templates (`bug_report.yml`, `feature_request.yml`, `config.yml`, `PULL_REQUEST_TEMPLATE.md`) and CI workflows (`.github/workflows/*.yml`) live on `main`; targeting them at `Develop` directly is wrong unless the change is meant to ride a release merge.
+
+## Release-notes mega-PRs (carve-out)
+
+`Develop` → `main` release cuts (e.g. #8) are **exempt** from the three-section template. Use feature-scoped `###` subsections instead — one per major area (e.g. `### Source generators`, `### ID System`, `### UIToolkit`, `### Layout & rename`, `### Docs & tooling`). Mirror the structure of the most recent release PR.
+
+## Recipe
+
+When invoked, walk through this checklist before reporting "PR opened":
+
+1. **Branch** — confirm the head branch is named per existing patterns (`Feature/`, `chore/`, `claude/`). If the auto-name is ugly (e.g. `claude/add-onenable-idregistry-IBkJl`), live with it but compensate with a clean PR title.
+2. **Diff** — run `git diff ...HEAD --stat` and skim what's actually changing. Identify the *one* logical change vs. accidental noise.
+3. **Title** — draft per the rules above.
+4. **Body** — fill the three template sections. Use `gh pr create --body "$(cat <<'EOF' ... EOF)"` with a HEREDOC; never inline backticks through zsh, they get eaten as command substitution and silently drop content (this happened on issue-comment 4415890039 — see the `gh api -X PATCH` recovery).
+5. **Labels** — apply via `--add-label "type: X,area: Y,…"` either at create time or right after.
+6. **Closes / Refs** — if any issues are involved, write them in *Linked issues*. Verify with `gh issue list --state open` what is actually linkable.
+7. **Verify** — `gh pr view --json title,labels,body` to confirm everything took.
+
+## Common failure modes to avoid
+
+- Empty body. Fix immediately if you see `body=""` on a PR you opened.
+- Auto-generated title from branch name (`Claude/add-…`).
+- Two `type: *` labels at once (pick one).
+- Pasting unredacted Claude-attribution trailers in commit messages.
+- Backtick-inside-`gh-pr-comment` shell injection (always heredoc).
diff --git a/.claude/skills/sync-readmes/SKILL.md b/.claude/skills/sync-readmes/SKILL.md
new file mode 100644
index 00000000..b9d09c81
--- /dev/null
+++ b/.claude/skills/sync-readmes/SKILL.md
@@ -0,0 +1,81 @@
+---
+name: sync-readmes
+description: Verify and update Aspid.FastTools README files against the actual codebase — namespaces, public API, CreateAssetMenu paths — keeping EN/RU and root/Documentation copies in sync
+user-invocable: true
+---
+
+The package ships **eight** README files that drift from the code easily. Use this skill whenever the user asks to "check / update / sync READMEs", or after any change that touches: namespaces of public types, public API surface, `[CreateAssetMenu]` paths, source generator output, or sample structure.
+
+## Files in scope
+
+**Main READMEs (mirror each other 1:1 except for image paths and one heading):**
+
+| Path | Locale | Image base path |
+|---|---|---|
+| `README.md` | EN | `Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/` |
+| `README_RU.md` | RU | `Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/` |
+| `Aspid.FastTools/Assets/Aspid/FastTools/Documentation/README.md` | EN | `Images/` |
+| `Aspid.FastTools/Assets/Aspid/FastTools/Documentation/README_RU.md` | RU | `Images/` |
+
+The Documentation copies have an extra `## Source Code` / `## Исходный код` block linking to the GitHub repo — the root copies don't. Otherwise the body is identical character-for-character.
+
+**Sample READMEs (one EN + one RU per sample):**
+
+- `Aspid.FastTools/Assets/Aspid/FastTools/Samples/Types/`
+- `Aspid.FastTools/Assets/Aspid/FastTools/Samples/Ids/`
+- `Aspid.FastTools/Assets/Aspid/FastTools/Samples/EnumValues/`
+- `Aspid.FastTools/Assets/Aspid/FastTools/Samples/ProfilerMarkers/`
+- `Aspid.FastTools/Assets/Aspid/FastTools/Samples/VisualElements/`
+
+## Workflow
+
+### 1. Verify against source before editing
+
+For every fact the README states, prove it from the code:
+
+| Claim | Verify with |
+|---|---|
+| Namespace of a public type | `grep -rn "namespace " --include="*.cs"` then locate the file declaring the type |
+| `[CreateAssetMenu]` menu path | `grep -rn "CreateAssetMenu\|menuName" --include="*.cs"` |
+| Public method signature / return value | Read the source file directly; do not infer from name |
+| Generated code shape (`IdStructGenerator`, `ProfilerMarkersGenerator`) | Read `Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/.../*Body.cs` |
+| Class actually exists | `find … -name ".cs"` — not all helper classes documented in old READMEs still exist (e.g. there is **no** `AspidEditorGUILayout`) |
+
+Common drift points discovered historically:
+
+- **Namespaces split per feature.** Public types live in `Aspid.FastTools` (root: `IId`, `UniqueIdAttribute`, `StringIdRegistry`), `Aspid.FastTools.Types`, `Aspid.FastTools.Enums`, `Aspid.FastTools.Ids`, `Aspid.FastTools.UIElements`. Editor helpers split similarly: `Aspid.FastTools.Editors` for `SerializedProperty` extensions / IMGUI scopes / `GetScriptName`, but per-feature editor code lives in `Aspid.FastTools.{Feature}.Editors`. A `using Aspid.FastTools;` line in a `SerializableType` example is wrong — it must be `using Aspid.FastTools.Types;`.
+- **Two ID registries.** `StringIdRegistry` (in `Aspid.FastTools`) keeps int↔string at runtime; `IdRegistry` (in `Aspid.FastTools.Ids`) is int-only at runtime with names stripped from player builds. Don't conflate them. Their menu paths differ: `Aspid/FastTools/String Id Registry` vs `Aspid/FastTools/Id Registry`. `StringIdRegistry.GetId` returns `-1` (not `0`) when not found; the lookup-by-id method is `GetNameId(int)`, not `GetName(int)`. Neither registry exposes public `Add`, `Remove`, or `Rename` — those live behind the registry inspector / `RegistryEditorCore`.
+- **Sample asset menu order.** Samples use `Aspid/Samples/FastTools/` (Samples first), not `Aspid/FastTools/Samples/`. Always re-grep `[CreateAssetMenu]` instead of trusting the existing README.
+
+### 2. Apply edits to all matching files
+
+Most edits hit all four main READMEs (EN root, EN Documentation, RU root, RU Documentation). Apply the same change to each — they must stay textually identical inside their respective body except for the image paths and the `## Source Code` heading.
+
+For RU edits, follow the existing RU translation conventions in the file: `Namespace` → `Пространство имён`, `Description` → `Описание`, code identifiers and English technical terms like `runtime`, `partial struct`, `Inspector` stay in English.
+
+When updating sample READMEs, edit both the EN and RU copy in the same sample folder.
+
+### 3. Sanity-check after editing
+
+Run these commands from the repo root and skim the output:
+
+```bash
+# All using statements in samples — these are ground truth for namespaces
+grep -rn "^using Aspid" Aspid.FastTools/Assets/Aspid/FastTools/Samples --include="*.cs" | sort -u
+
+# All CreateAssetMenu paths in the package
+grep -rn "menuName" Aspid.FastTools/Assets/Aspid/FastTools --include="*.cs"
+
+# Confirm both READMEs of a pair stay aligned (count sections)
+grep -c "^## " README.md Aspid.FastTools/Assets/Aspid/FastTools/Documentation/README.md
+grep -c "^## " README_RU.md Aspid.FastTools/Assets/Aspid/FastTools/Documentation/README_RU.md
+```
+
+Then visually diff each pair of matched files (EN root vs EN Documentation; RU root vs RU Documentation) — only image paths and the `## Source Code` block should differ.
+
+## Arguments
+
+`$ARGUMENTS` (optional):
+- empty — full audit and update of all eight READMEs;
+- `--check` — audit only, report findings without editing;
+- a feature name (`ids`, `types`, `enums`, `visualelements`, `profilermarkers`, `imgui`, `serializedproperty`) — narrow the audit/update to that section.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..63e8cd43
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,41 @@
+name: Bug report
+description: Report a bug or unexpected behavior in Aspid.FastTools.
+labels: ["type: fix"]
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: What went wrong? What did you expect instead?
+ validations:
+ required: true
+ - type: textarea
+ id: repro
+ attributes:
+ label: Steps to reproduce
+ description: Minimal steps so the bug can be reproduced locally.
+ placeholder: |
+ 1. ...
+ 2. ...
+ 3. ...
+ validations:
+ required: true
+ - type: input
+ id: unity-version
+ attributes:
+ label: Unity version
+ placeholder: "2022.3.x"
+ validations:
+ required: true
+ - type: input
+ id: package-version
+ attributes:
+ label: Aspid.FastTools version
+ placeholder: "1.0.0"
+ validations:
+ required: true
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional context
+ description: Logs, screenshots, related issues — anything that helps.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..f0c07480
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Questions and ideas
+ url: https://github.com/VPDPersonal/Aspid.FastTools/discussions
+ about: Open-ended questions, design ideas and Q&A go in Discussions, not Issues.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 00000000..451683f5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,27 @@
+name: Feature request
+description: Suggest a new capability or improvement for Aspid.FastTools.
+labels: ["type: feature"]
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem
+ description: What problem does this solve? Who is it for?
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposal
+ description: Sketch of the API or behavior you'd like — code examples are welcome.
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives
+ description: Other approaches you considered, and why this one is preferred.
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional context
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..456d16e4
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+## Summary
+
+
+
+## Notes for review
+
+
+
+## Linked issues
+
+
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
new file mode 100644
index 00000000..b5e8cfd4
--- /dev/null
+++ b/.github/workflows/claude-code-review.yml
@@ -0,0 +1,44 @@
+name: Claude Code Review
+
+on:
+ pull_request:
+ types: [opened, synchronize, ready_for_review, reopened]
+ # Optional: Only run on specific file changes
+ # paths:
+ # - "src/**/*.ts"
+ # - "src/**/*.tsx"
+ # - "src/**/*.js"
+ # - "src/**/*.jsx"
+
+jobs:
+ claude-review:
+ # Optional: Filter by PR author
+ # if: |
+ # github.event.pull_request.user.login == 'external-contributor' ||
+ # github.event.pull_request.user.login == 'new-developer' ||
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
+
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code Review
+ id: claude-review
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
+ plugins: 'code-review@claude-code-plugins'
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 00000000..6b15fac7
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,50 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+ actions: read # Required for Claude to read CI results on PRs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+
+ # This is an optional setting that allows Claude to read CI results on PRs
+ additional_permissions: |
+ actions: read
+
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
+ # prompt: 'Update the pull request description to include a summary of changes.'
+
+ # Optional: Add claude_args to customize behavior and configuration
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ # claude_args: '--allowed-tools Bash(gh pr *)'
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..ca6f4bb0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.claude/settings.local.json
+.worktrees/
+
+# UPM convention: `Samples~` is hidden from Unity importer but must be tracked.
+# Override global `*~` ignore.
+!Samples~/
+!Samples~/**
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29b..00000000
diff --git a/Aspid.UnityFastTools.Generators/.gitignore b/Aspid.FastTools.Generators/.gitignore
similarity index 100%
rename from Aspid.UnityFastTools.Generators/.gitignore
rename to Aspid.FastTools.Generators/.gitignore
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
similarity index 61%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
index c7d1c6e2..c971c512 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
@@ -3,13 +3,13 @@
net6.0
enable
- UnityFastToolsGenerators.Sample
+ Aspid.FastTools.Sample
6000.2.7f2
9
-
+
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
similarity index 79%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
index 963f0781..a09224de 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
@@ -3,10 +3,11 @@
net6.0
enable
+ latest
false
- UnityFastToolsGenerators.Tests
+ Aspid.FastTools.Tests
@@ -20,7 +21,7 @@
-
+
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Helpers/GeneratorTestHost.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Helpers/GeneratorTestHost.cs
new file mode 100644
index 00000000..d75a5060
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Helpers/GeneratorTestHost.cs
@@ -0,0 +1,113 @@
+using Xunit;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.CSharp;
+using Aspid.FastTools.Generators.IdStruct;
+using Aspid.FastTools.Generators.ProfilerMarkers;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Generators.Tests.Helpers;
+
+internal static class GeneratorTestHost
+{
+ public const string IIdDefinition = """
+ namespace Aspid.FastTools.Ids
+ {
+ public interface IId { int Id { get; } }
+ }
+ """;
+
+ public const string UnityEngineStubs = """
+ namespace UnityEngine
+ {
+ [System.AttributeUsage(System.AttributeTargets.Field)]
+ public sealed class SerializeField : System.Attribute { }
+ }
+ """;
+
+ public const string ProfilerMarkerStubs = """
+ namespace Unity.Profiling
+ {
+ public struct ProfilerMarker
+ {
+ public ProfilerMarker(string _) { }
+ public AutoScope Auto() => default;
+ public struct AutoScope : System.IDisposable { public void Dispose() { } }
+ }
+ }
+
+ public static class ProfilerMarkerExtensionsForGenerator
+ {
+ public static Unity.Profiling.ProfilerMarker.AutoScope Marker(this object _) => default;
+ public static Unity.Profiling.ProfilerMarker.AutoScope WithName(this in Unity.Profiling.ProfilerMarker.AutoScope marker, string _) => marker;
+ }
+ """;
+
+ public static GeneratorRun RunIdStruct(string userSource)
+ {
+ var compilation = BuildCompilation(new[] { userSource, IIdDefinition, UnityEngineStubs });
+ return Run(compilation, new IdStructGenerator());
+ }
+
+ public static GeneratorRun RunProfilerMarkers(string userSource)
+ {
+ var compilation = BuildCompilation(new[] { userSource, ProfilerMarkerStubs });
+ return Run(compilation, new ProfilerMarkersGenerator());
+ }
+
+ public static void AssertNoErrors(GeneratorRun run)
+ {
+ var generatorErrors = run.RunResult.Diagnostics
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ Assert.True(
+ generatorErrors.Length == 0,
+ "Generator emitted errors: " + string.Join("; ", generatorErrors.Select(d => d.ToString())));
+
+ var compileErrors = run.OutputCompilation.GetDiagnostics()
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ Assert.True(
+ compileErrors.Length == 0,
+ "Generated source has compile errors: " + string.Join("; ", compileErrors.Select(d => d.ToString())));
+ }
+
+ private static GeneratorRun Run(CSharpCompilation compilation, IIncrementalGenerator generator)
+ {
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
+ driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var output, out _);
+ return new GeneratorRun(driver, driver.GetRunResult(), output);
+ }
+
+ private static CSharpCompilation BuildCompilation(IEnumerable sources)
+ {
+ var trees = sources.Select(s => CSharpSyntaxTree.ParseText(s));
+ var references = new[]
+ {
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.CallerLineNumberAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.ComponentModel.EditorBrowsableAttribute).Assembly.Location),
+ };
+
+ return CSharpCompilation.Create(
+ assemblyName: "TestCompilation",
+ syntaxTrees: trees,
+ references: references,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+}
+
+internal readonly struct GeneratorRun
+{
+ public readonly GeneratorDriver Driver;
+ public readonly GeneratorDriverRunResult RunResult;
+ public readonly Compilation OutputCompilation;
+
+ public GeneratorRun(GeneratorDriver driver, GeneratorDriverRunResult runResult, Compilation outputCompilation)
+ {
+ Driver = driver;
+ RunResult = runResult;
+ OutputCompilation = outputCompilation;
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs
new file mode 100644
index 00000000..3fc44618
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs
@@ -0,0 +1,350 @@
+using Xunit;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Aspid.FastTools.Generators.Tests.Helpers;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Generators.Tests;
+
+public class IdStructGeneratorTests
+{
+ [Fact]
+ public void Generator_DoesNotCrash_OnEmptySource()
+ {
+ var run = GeneratorTestHost.RunIdStruct("namespace Test { }");
+
+ Assert.Empty(run.RunResult.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+ }
+
+ [Fact]
+ public void Struct_WithIId_InNamespace_GeneratesIdFieldAndProperty()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ Assert.EndsWith(".IId.g.cs", generated[0].HintName);
+
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial struct Foo", text);
+ Assert.Contains("private int _id;", text);
+ Assert.Contains("public int Id =>", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_WithoutPartial_EmitsAFID001AndDoesNotGenerate()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public struct Foo : global::Aspid.FastTools.Ids.IId
+ {
+ public int Id => 0;
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+
+ var diags = run.RunResult.Diagnostics;
+ Assert.Contains(diags, d => d.Id == "AFID001" && d.Severity == DiagnosticSeverity.Error);
+ }
+
+ [Fact]
+ public void Struct_WithExistingId_EmitsAFID002AndDoesNotGenerate()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId
+ {
+ private int _id;
+ public int Id => _id;
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+
+ var afid002 = run.RunResult.Diagnostics.FirstOrDefault(d => d.Id == "AFID002");
+ Assert.NotNull(afid002);
+ Assert.Equal(DiagnosticSeverity.Error, afid002!.Severity);
+ var msg = afid002.GetMessage();
+ Assert.Contains("_id", msg);
+ Assert.Contains("Id", msg);
+ }
+
+ [Fact]
+ public void Struct_WithoutIId_NotGenerated()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public interface IMarker { }
+ public partial struct Foo : IMarker { }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+ }
+
+ [Fact]
+ public void TwoStructsSameName_DifferentNamespaces_NoHintNameCollision()
+ {
+ const string source = """
+ namespace SampleA
+ {
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+ }
+
+ namespace SampleB
+ {
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Equal(2, generated.Length);
+
+ var hintNames = generated.Select(s => s.HintName).ToArray();
+ Assert.NotEqual(hintNames[0], hintNames[1]);
+ Assert.Contains(hintNames, h => h.Contains("SampleA"));
+ Assert.Contains(hintNames, h => h.Contains("SampleB"));
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void NestedStruct_WrappedInPartialOuterClass()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial class Outer
+ {
+ public partial struct Inner : global::Aspid.FastTools.Ids.IId { }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial class Outer", text);
+ Assert.Contains("partial struct Inner", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void NestedStruct_InGenericOuter_EmitsTypeParameters()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial class Outer
+ {
+ public partial struct Inner : global::Aspid.FastTools.Ids.IId { }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial class Outer", text);
+ Assert.Contains("partial struct Inner", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_InGlobalNamespace_GeneratesWithoutNamespaceBlock()
+ {
+ const string source = "public partial struct GlobalFoo : global::Aspid.FastTools.Ids.IId { }";
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial struct GlobalFoo", text);
+ Assert.DoesNotContain("namespace ", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_FileScopedNamespace_Generates()
+ {
+ const string source = """
+ namespace Sample;
+
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("namespace Sample", text);
+ Assert.Contains("partial struct Foo", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_ThreeLevelNesting_WrappedInCorrectOrder()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial class Outer
+ {
+ public partial class Middle
+ {
+ public partial struct Inner : global::Aspid.FastTools.Ids.IId { }
+ }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial class Outer", text);
+ Assert.Contains("partial class Middle", text);
+ Assert.Contains("partial struct Inner", text);
+
+ // Verify Outer wraps Middle wraps Inner.
+ var outerIdx = text.IndexOf("partial class Outer", System.StringComparison.Ordinal);
+ var middleIdx = text.IndexOf("partial class Middle", System.StringComparison.Ordinal);
+ var innerIdx = text.IndexOf("partial struct Inner", System.StringComparison.Ordinal);
+ Assert.True(outerIdx < middleIdx && middleIdx < innerIdx, "Wrappers must be ordered Outer → Middle → Inner");
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_TransitiveIId_Generates()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public interface IMyId : global::Aspid.FastTools.Ids.IId { }
+ public partial struct Foo : IMyId { }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial struct Foo", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_GenericTarget_EmitsTypeParameters()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial struct MyId : global::Aspid.FastTools.Ids.IId { }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial struct MyId", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Struct_RecordStructContaining_GeneratesRecordWrapper()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial record struct Outer
+ {
+ public partial struct Inner : global::Aspid.FastTools.Ids.IId { }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial record struct Outer", text);
+ Assert.Contains("partial struct Inner", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void TwoStructs_InGenericOuters_DifferentArity_NoHintCollision()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public partial class Outer
+ {
+ public partial struct MyId : global::Aspid.FastTools.Ids.IId { }
+ }
+
+ public partial class Outer
+ {
+ public partial struct MyId : global::Aspid.FastTools.Ids.IId { }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunIdStruct(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Equal(2, generated.Length);
+
+ var hintNames = generated.Select(s => s.HintName).ToArray();
+ Assert.NotEqual(hintNames[0], hintNames[1]);
+
+ var combined = string.Concat(generated.Select(s => s.SourceText.ToString()));
+ Assert.Contains("partial class Outer", combined);
+ Assert.Contains("partial class Outer", combined);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IncrementalCacheTests.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IncrementalCacheTests.cs
new file mode 100644
index 00000000..65d81242
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IncrementalCacheTests.cs
@@ -0,0 +1,116 @@
+using Xunit;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Aspid.FastTools.Generators.IdStruct;
+using Aspid.FastTools.Generators.ProfilerMarkers;
+using Aspid.FastTools.Generators.Tests.Helpers;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Generators.Tests;
+
+// Verifies that running the same generator twice over compilations that differ only
+// in an *unrelated* file produces cached pipeline steps. Regression guard for the
+// "ISymbol stored in data structures" anti-pattern that defeats the cache.
+public class IncrementalCacheTests
+{
+ [Fact]
+ public void IdStruct_UnrelatedSourceChange_PreservesCache()
+ {
+ const string targetSource = """
+ namespace Sample
+ {
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+ }
+ """;
+
+ AssertCachedAfterUnrelatedEdit(
+ targetSource,
+ new IdStructGenerator(),
+ useUnityStubs: true);
+ }
+
+ [Fact]
+ public void ProfilerMarkers_UnrelatedSourceChange_PreservesCache()
+ {
+ const string targetSource = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker(); }
+ }
+ }
+ """;
+
+ AssertCachedAfterUnrelatedEdit(
+ targetSource,
+ new ProfilerMarkersGenerator(),
+ useUnityStubs: false);
+ }
+
+ private static void AssertCachedAfterUnrelatedEdit(string targetSource, IIncrementalGenerator generator, bool useUnityStubs)
+ {
+ var stubs = useUnityStubs
+ ? new[] { GeneratorTestHost.IIdDefinition, GeneratorTestHost.UnityEngineStubs }
+ : new[] { GeneratorTestHost.ProfilerMarkerStubs };
+
+ var driverOptions = new GeneratorDriverOptions(
+ disabledOutputs: IncrementalGeneratorOutputKind.None,
+ trackIncrementalGeneratorSteps: true);
+
+ var compilation1 = MakeCompilation(targetSource, "// version 1", stubs);
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(
+ generators: new[] { generator.AsSourceGenerator() },
+ additionalTexts: null,
+ parseOptions: null,
+ optionsProvider: null,
+ driverOptions: driverOptions);
+
+ driver = driver.RunGenerators(compilation1);
+
+ // Second run with the unrelated tree edited; the target tree is unchanged.
+ var compilation2 = MakeCompilation(targetSource, "// version 2", stubs);
+ driver = driver.RunGenerators(compilation2);
+
+ var result = driver.GetRunResult().Results.Single();
+
+ // Inspect the steps from the *second* run. Steps tied to the target source
+ // must show all-Cached / Unchanged outputs (the user code didn't change).
+ var trackedSteps = result.TrackedOutputSteps.SelectMany(kvp => kvp.Value).ToArray();
+ Assert.NotEmpty(trackedSteps);
+
+ foreach (var step in trackedSteps)
+ {
+ foreach (var (_, reason) in step.Outputs)
+ {
+ Assert.True(
+ reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged,
+ $"Output step '{step.Name}' was '{reason}', expected Cached/Unchanged. " +
+ "This indicates a non-equatable value somewhere in the pipeline.");
+ }
+ }
+ }
+
+ private static CSharpCompilation MakeCompilation(string targetSource, string unrelatedSource, string[] stubs)
+ {
+ var trees = new[]
+ {
+ CSharpSyntaxTree.ParseText(targetSource, path: "Target.cs"),
+ CSharpSyntaxTree.ParseText(unrelatedSource, path: "Unrelated.cs"),
+ }.Concat(stubs.Select(s => CSharpSyntaxTree.ParseText(s)));
+
+ var references = new[]
+ {
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.CallerLineNumberAttribute).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(System.ComponentModel.EditorBrowsableAttribute).Assembly.Location),
+ };
+
+ return CSharpCompilation.Create(
+ assemblyName: "TestCompilation",
+ syntaxTrees: trees,
+ references: references,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/ProfilerMarkersGeneratorTests.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/ProfilerMarkersGeneratorTests.cs
new file mode 100644
index 00000000..2d535d0c
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/ProfilerMarkersGeneratorTests.cs
@@ -0,0 +1,445 @@
+using Xunit;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Aspid.FastTools.Generators.Tests.Helpers;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Generators.Tests;
+
+public class ProfilerMarkersGeneratorTests
+{
+ [Fact]
+ public void Generator_DoesNotCrash_OnEmptySource()
+ {
+ var run = GeneratorTestHost.RunProfilerMarkers("namespace Test { }");
+
+ Assert.Empty(run.RunResult.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+ }
+
+ [Fact]
+ public void SingleMarkerCall_GeneratesExtensionClass()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("internal static class __FooProfilerMarkerExtensions", text);
+ Assert.Contains("Run_Marker_Line_", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void TwoCallsOnDifferentLines_GenerateTwoFields()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run()
+ {
+ this.Marker();
+ this.Marker();
+ }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ // Two distinct fields, named after method + their line numbers.
+ var fieldDeclarations = System.Text.RegularExpressions.Regex.Matches(
+ text, @"static\s+readonly\s+global::Unity\.Profiling\.ProfilerMarker\s+(\w+)\s*=");
+ Assert.Equal(2, fieldDeclarations.Count);
+
+ var name1 = fieldDeclarations[0].Groups[1].Value;
+ var name2 = fieldDeclarations[1].Groups[1].Value;
+ Assert.NotEqual(name1, name2);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void TwoCallsOnSameLine_DedupesFieldNames()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker(); this.Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ // Two distinct fields even though they're on the same source line.
+ var fieldDeclarations = System.Text.RegularExpressions.Regex.Matches(
+ text, @"static\s+readonly\s+global::Unity\.Profiling\.ProfilerMarker\s+(\w+)\s*=");
+ Assert.Equal(2, fieldDeclarations.Count);
+
+ var name1 = fieldDeclarations[0].Groups[1].Value;
+ var name2 = fieldDeclarations[1].Groups[1].Value;
+ Assert.NotEqual(name1, name2);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void WithNameLiteral_OverridesMarkerLabel()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker().WithName("CustomLabel"); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("CustomLabel", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void WithNameInterpolated_PlainText_OverridesMarkerLabel()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker().WithName($"MyMarker"); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("MyMarker", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void WithNameAfterParenthesis_OverridesMarkerLabel()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { (this.Marker()).WithName("ParenLabel"); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("ParenLabel", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void WithNameInterpolatedWithVariable_FallsBackToMethodName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { var x = 42; this.Marker().WithName($"X{x}"); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ // Variable interpolation can't be evaluated at compile time → label is the method name.
+ Assert.Contains("Run_Marker_Line_", text);
+ Assert.DoesNotContain("\"X{x}\"", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Constructor_UsesCtorAsMarkerName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public Foo() { this.Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("Ctor_Marker_Line_", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void PropertyGetter_UsesPropertyNameAsMarkerName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public int Value
+ {
+ get { this.Marker(); return 0; }
+ }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("Value_Marker_Line_", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void GenericClass_EmitsTypeParameters()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("__Foo_1ProfilerMarkerExtensions", text);
+ Assert.Contains("Markers", text);
+ Assert.Contains("Marker(this global::Sample.Foo", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void GlobalNamespace_GeneratesWithoutNamespaceBlock()
+ {
+ const string source = """
+ public class Foo
+ {
+ public void Run() { this.Marker(); }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.DoesNotContain("namespace ", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void TwoTypesSameName_DifferentNamespaces_NoHintCollision()
+ {
+ const string source = """
+ namespace SampleA
+ {
+ public class Foo { public void Run() { this.Marker(); } }
+ }
+ namespace SampleB
+ {
+ public class Foo { public void Run() { this.Marker(); } }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Equal(2, generated.Length);
+ var hintNames = generated.Select(s => s.HintName).ToArray();
+ Assert.NotEqual(hintNames[0], hintNames[1]);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void NestedTypesSameName_DifferentOuters_NoHintCollision()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class OuterA { public class Inner { public void Run() { this.Marker(); } } }
+ public class OuterB { public class Inner { public void Run() { this.Marker(); } } }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Equal(2, generated.Length);
+ var hintNames = generated.Select(s => s.HintName).ToArray();
+ Assert.NotEqual(hintNames[0], hintNames[1]);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Lambda_UsesContainingMethodAsMarkerName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run()
+ {
+ System.Action a = () => this.Marker();
+ a();
+ }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ // Marker name must be the enclosing real method, not the synthesized lambda symbol.
+ Assert.Contains("Run_Marker_Line_", text);
+ Assert.DoesNotContain("<>", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void LocalFunction_UsesContainingMethodAsMarkerName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run()
+ {
+ Local();
+ void Local() { this.Marker(); }
+ }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("Run_Marker_Line_", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void FieldInitializer_UsesFieldNameAsMarkerName()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ private static readonly int _count = ((object)null).Marker() is var _ ? 1 : 0;
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var generated = run.RunResult.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("_count_Marker_Line_", text);
+ }
+
+ [Fact]
+ public void UnrelatedMarkerExtension_IsNotProcessed()
+ {
+ // User defines their own Marker() extension on a custom type — the generator
+ // must NOT mistake it for ProfilerMarkerExtensionsForGenerator.Marker.
+ const string source = """
+ namespace Sample
+ {
+ public class Foo { }
+ public static class FooExtensions
+ {
+ public static int Marker(this Foo _) => 0;
+ }
+ public class Caller
+ {
+ public void Run() { var x = new Foo().Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+
+ Assert.Empty(run.RunResult.Results[0].GeneratedSources);
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+
+ [Fact]
+ public void Dispatcher_IsGatedByEnableProfiler_AndFallsBackToDefault()
+ {
+ const string source = """
+ namespace Sample
+ {
+ public class Foo
+ {
+ public void Run() { this.Marker(); }
+ }
+ }
+ """;
+
+ var run = GeneratorTestHost.RunProfilerMarkers(source);
+ var text = run.RunResult.Results[0].GeneratedSources[0].SourceText.ToString();
+
+ Assert.Contains("#if ENABLE_PROFILER", text);
+ Assert.Contains("#endif", text);
+ Assert.Contains("return default;", text);
+ Assert.DoesNotContain("throw new", text);
+
+ GeneratorTestHost.AssertNoErrors(run);
+ }
+}
diff --git a/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
similarity index 64%
rename from Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
index aa25b9e4..29d5927d 100644
--- a/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
@@ -1,10 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators\Aspid.UnityFastTools.Generators.csproj", "{CB9D8D51-7D86-4B84-A0DB-73E418962DA7}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators", "Aspid.FastTools.Generators\Aspid.FastTools.Generators.csproj", "{CB9D8D51-7D86-4B84-A0DB-73E418962DA7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators.Sample", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators.Sample\Aspid.UnityFastTools.Generators.Sample.csproj", "{2835DD81-D105-4C2E-AE03-BC7D064C29D1}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators.Sample", "Aspid.FastTools.Generators.Sample\Aspid.FastTools.Generators.Sample.csproj", "{2835DD81-D105-4C2E-AE03-BC7D064C29D1}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators.Tests", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators.Tests\Aspid.UnityFastTools.Generators.Tests.csproj", "{F4953608-2F14-4B2E-B91C-B3FDFC81B180}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators.Tests", "Aspid.FastTools.Generators.Tests\Aspid.FastTools.Generators.Tests.csproj", "{F4953608-2F14-4B2E-B91C-B3FDFC81B180}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
similarity index 73%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
index 74888a77..b3d7d6c4 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
@@ -3,11 +3,17 @@
netstandard2.0
enable
- 13
+ latest
true
- UnityFastToolsGenerators
+ Aspid.FastTools
+
+ $(NoWarn);RS2008
+
+
+
+
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs
new file mode 100644
index 00000000..51a88c84
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs
@@ -0,0 +1,9 @@
+using Aspid.Generators.Helper;
+
+namespace Aspid.FastTools.Descriptions;
+
+internal static class General
+{
+ public static readonly string ProfilerMarkerGeneratedCode = $"""{Classes.GeneratedCodeAttribute}("Aspid.FastTools.Generators.ProfilerMarkersGenerator", "1.0.0")""";
+ public static readonly string IdStructGeneratedCode = $"""{Classes.GeneratedCodeAttribute}("Aspid.FastTools.Generators.IdStructGenerator", "1.0.0")""";
+}
\ No newline at end of file
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs
new file mode 100644
index 00000000..d1d16d04
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs
@@ -0,0 +1,58 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Aspid.Generators.Helper;
+using Aspid.FastTools.Generators.IdStruct.Data;
+using static Aspid.FastTools.Descriptions.General;
+
+namespace Aspid.FastTools.Generators.IdStruct.Bodies;
+
+internal static class IdStructBody
+{
+ public static void GenerateCode(in SourceProductionContext context, in IdStructData data)
+ {
+ var hasNamespace = data.Namespace != null;
+ var nestedDepth = data.ContainingTypes.Length;
+
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ .AppendLine()
+ .AppendLineIf(hasNamespace, $"namespace {data.Namespace}")
+ .BeginBlockIf(hasNamespace);
+
+ var nestedNames = new StringBuilder();
+ foreach (var containing in data.ContainingTypes)
+ {
+ code.AppendLine($"partial {containing.Keyword} {containing.Name}{containing.TypeParameters}")
+ .BeginBlock();
+
+ nestedNames.Append(containing.Name);
+ if (containing.Arity > 0) nestedNames.Append('_').Append(containing.Arity);
+ nestedNames.Append('.');
+ }
+
+ code.AppendLine($"partial struct {data.StructName}{data.TypeParameters}")
+ .BeginBlock()
+ .AppendLine("#if UNITY_EDITOR")
+ .AppendLine($"[{IdStructGeneratedCode}]")
+ .AppendLine("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]")
+ .AppendLine("[global::UnityEngine.SerializeField] private string __stringId;")
+ .AppendLine("#endif")
+ .AppendLine($"[{IdStructGeneratedCode}]")
+ .AppendLine("[global::UnityEngine.SerializeField] private int _id;")
+ .AppendLine($"[{IdStructGeneratedCode}]")
+ .AppendLine("public int Id => _id;")
+ .EndBlock();
+
+ for (var i = 0; i < nestedDepth; i++)
+ code.EndBlock();
+
+ code.EndBlockIf(hasNamespace);
+
+ var aritySuffix = data.Arity > 0 ? "_" + data.Arity : string.Empty;
+ var hintName = hasNamespace
+ ? $"{data.Namespace}.{nestedNames}{data.StructName}{aritySuffix}.IId.g.cs"
+ : $"{nestedNames}{data.StructName}{aritySuffix}.IId.g.cs";
+
+ context.AddSource(hintName, code.GetSourceText());
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs
new file mode 100644
index 00000000..a6735661
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+internal readonly struct ContainingTypeInfo : IEquatable
+{
+ public readonly string Name;
+ public readonly string Keyword;
+ public readonly string TypeParameters;
+ public readonly int Arity;
+
+ public ContainingTypeInfo(string name, string keyword, string typeParameters, int arity)
+ {
+ Name = name;
+ Keyword = keyword;
+ TypeParameters = typeParameters;
+ Arity = arity;
+ }
+
+ public bool Equals(ContainingTypeInfo other) =>
+ Name == other.Name
+ && Keyword == other.Keyword
+ && TypeParameters == other.TypeParameters
+ && Arity == other.Arity;
+
+ public override bool Equals(object? obj) =>
+ obj is ContainingTypeInfo other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = Name.GetHashCode();
+ hash = (hash * 397) ^ Keyword.GetHashCode();
+ hash = (hash * 397) ^ TypeParameters.GetHashCode();
+ hash = (hash * 397) ^ Arity;
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/DiagnosticInfo.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/DiagnosticInfo.cs
new file mode 100644
index 00000000..3d985cca
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/DiagnosticInfo.cs
@@ -0,0 +1,73 @@
+using System;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+internal readonly struct DiagnosticInfo : IEquatable
+{
+ public readonly string DescriptorId;
+ public readonly string MessageArg0;
+ public readonly string? MessageArg1;
+ public readonly string? FilePath;
+ public readonly TextSpan TextSpan;
+ public readonly LinePositionSpan LineSpan;
+
+ public DiagnosticInfo(DiagnosticDescriptor descriptor, Location? location, string messageArg0, string? messageArg1 = null)
+ {
+ DescriptorId = descriptor.Id;
+ MessageArg0 = messageArg0;
+ MessageArg1 = messageArg1;
+
+ if (location is { SourceTree: not null })
+ {
+ FilePath = location.SourceTree.FilePath;
+ TextSpan = location.SourceSpan;
+ LineSpan = location.GetLineSpan().Span;
+ }
+ else
+ {
+ FilePath = null;
+ TextSpan = default;
+ LineSpan = default;
+ }
+ }
+
+ public Diagnostic ToDiagnostic()
+ {
+ var descriptor = IdStructDiagnostics.GetDescriptor(DescriptorId);
+ if (descriptor is null) return Diagnostic.Create("UNKNOWN", "Generator", "Unknown descriptor", DiagnosticSeverity.Hidden, DiagnosticSeverity.Hidden, false, 4);
+
+ var location = FilePath is null
+ ? Location.None
+ : Location.Create(FilePath, TextSpan, LineSpan);
+
+ return MessageArg1 is null
+ ? Diagnostic.Create(descriptor, location, MessageArg0)
+ : Diagnostic.Create(descriptor, location, MessageArg0, MessageArg1);
+ }
+
+ public bool Equals(DiagnosticInfo other) =>
+ DescriptorId == other.DescriptorId
+ && MessageArg0 == other.MessageArg0
+ && MessageArg1 == other.MessageArg1
+ && FilePath == other.FilePath
+ && TextSpan.Equals(other.TextSpan)
+ && LineSpan.Equals(other.LineSpan);
+
+ public override bool Equals(object? obj) => obj is DiagnosticInfo other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = DescriptorId.GetHashCode();
+ hash = (hash * 397) ^ MessageArg0.GetHashCode();
+ hash = (hash * 397) ^ (MessageArg1?.GetHashCode() ?? 0);
+ hash = (hash * 397) ^ (FilePath?.GetHashCode() ?? 0);
+ hash = (hash * 397) ^ TextSpan.GetHashCode();
+ hash = (hash * 397) ^ LineSpan.GetHashCode();
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs
new file mode 100644
index 00000000..74c4c0ff
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+internal readonly struct IdStructData : IEquatable
+{
+ public readonly string StructName;
+ public readonly string TypeParameters;
+ public readonly int Arity;
+ public readonly string? Namespace;
+ public readonly ImmutableArray ContainingTypes;
+
+ public IdStructData(INamedTypeSymbol symbol)
+ {
+ StructName = symbol.Name;
+
+ var typeParams = symbol.TypeParameters;
+ Arity = typeParams.Length;
+ TypeParameters = Arity > 0
+ ? "<" + string.Join(", ", typeParams.Select(p => p.Name)) + ">"
+ : string.Empty;
+
+ Namespace = symbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : symbol.ContainingNamespace.ToDisplayString();
+
+ if (symbol.ContainingType is null)
+ {
+ ContainingTypes = ImmutableArray.Empty;
+ return;
+ }
+
+ var builder = ImmutableArray.CreateBuilder();
+ var current = symbol.ContainingType;
+ while (current is not null)
+ {
+ builder.Add(MakeContainingInfo(current));
+ current = current.ContainingType;
+ }
+
+ builder.Reverse();
+ ContainingTypes = builder.ToImmutable();
+ }
+
+ private static ContainingTypeInfo MakeContainingInfo(INamedTypeSymbol symbol)
+ {
+ var typeParams = symbol.TypeParameters;
+ var typeParamList = typeParams.Length > 0
+ ? "<" + string.Join(", ", typeParams.Select(p => p.Name)) + ">"
+ : string.Empty;
+ return new ContainingTypeInfo(symbol.Name, GetKeyword(symbol), typeParamList, typeParams.Length);
+ }
+
+ private static string GetKeyword(INamedTypeSymbol symbol)
+ {
+ if (symbol.IsRecord)
+ return symbol.IsValueType ? "record struct" : "record";
+
+ return symbol.TypeKind switch
+ {
+ TypeKind.Class => "class",
+ TypeKind.Struct => "struct",
+ TypeKind.Interface => "interface",
+ _ => "class",
+ };
+ }
+
+ public bool Equals(IdStructData other)
+ {
+ if (StructName != other.StructName) return false;
+ if (TypeParameters != other.TypeParameters) return false;
+ if (Arity != other.Arity) return false;
+ if (Namespace != other.Namespace) return false;
+ if (ContainingTypes.Length != other.ContainingTypes.Length) return false;
+
+ for (var i = 0; i < ContainingTypes.Length; i++)
+ {
+ if (!ContainingTypes[i].Equals(other.ContainingTypes[i])) return false;
+ }
+
+ return true;
+ }
+
+ public override bool Equals(object? obj) =>
+ obj is IdStructData other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = StructName.GetHashCode();
+ hash = (hash * 397) ^ TypeParameters.GetHashCode();
+ hash = (hash * 397) ^ Arity;
+ hash = (hash * 397) ^ (Namespace?.GetHashCode() ?? 0);
+
+ foreach (var ct in ContainingTypes)
+ hash = (hash * 397) ^ ct.GetHashCode();
+
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructDiagnostics.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructDiagnostics.cs
new file mode 100644
index 00000000..2859a587
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructDiagnostics.cs
@@ -0,0 +1,31 @@
+using Microsoft.CodeAnalysis;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+internal static class IdStructDiagnostics
+{
+ private const string Category = "Aspid.FastTools.IdStruct";
+
+ public static readonly DiagnosticDescriptor NotPartial = new(
+ id: "AFID001",
+ title: "IId struct must be partial",
+ messageFormat: "Struct '{0}' implements 'Aspid.FastTools.Ids.IId' but is not declared partial; the generator cannot emit the required '_id' field and 'Id' property",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor MemberConflict = new(
+ id: "AFID002",
+ title: "Generated IId members already declared",
+ messageFormat: "Struct '{0}' already declares member(s) that the IId generator would emit: {1}",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static DiagnosticDescriptor? GetDescriptor(string id) => id switch
+ {
+ "AFID001" => NotPartial,
+ "AFID002" => MemberConflict,
+ _ => null,
+ };
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructResult.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructResult.cs
new file mode 100644
index 00000000..5f860954
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructResult.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+internal readonly struct IdStructResult : IEquatable
+{
+ public readonly IdStructData? Data;
+ public readonly DiagnosticInfo? Diagnostic;
+
+ public IdStructResult(IdStructData? data, DiagnosticInfo? diagnostic)
+ {
+ Data = data;
+ Diagnostic = diagnostic;
+ }
+
+ public bool IsEmpty => Data is null && Diagnostic is null;
+
+ public bool Equals(IdStructResult other) =>
+ Nullable.Equals(Data, other.Data) && Nullable.Equals(Diagnostic, other.Diagnostic);
+
+ public override bool Equals(object? obj) => obj is IdStructResult other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = Data?.GetHashCode() ?? 0;
+ hash = (hash * 397) ^ (Diagnostic?.GetHashCode() ?? 0);
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs
new file mode 100644
index 00000000..318a0571
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Aspid.FastTools.Generators.IdStruct.Data;
+using Aspid.FastTools.Generators.IdStruct.Bodies;
+
+namespace Aspid.FastTools.Generators.IdStruct;
+
+[Generator(LanguageNames.CSharp)]
+internal sealed class IdStructGenerator : IIncrementalGenerator
+{
+ private const string IIdFullName = "Aspid.FastTools.Ids.IId";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var provider = context.SyntaxProvider
+ .CreateSyntaxProvider(Predicate, Transform)
+ .Where(static r => !r.IsEmpty);
+
+ context.RegisterSourceOutput(provider, static (ctx, result) => Emit(ctx, result));
+ }
+
+ private static bool Predicate(SyntaxNode node, CancellationToken _)
+ {
+ if (node is not StructDeclarationSyntax structDecl) return false;
+ return structDecl.BaseList is { Types.Count: > 0 };
+ }
+
+ private static IdStructResult Transform(GeneratorSyntaxContext context, CancellationToken ct)
+ {
+ var structDecl = (StructDeclarationSyntax)context.Node;
+
+ if (context.SemanticModel.GetDeclaredSymbol(structDecl, ct) is not INamedTypeSymbol symbol)
+ return default;
+
+ var iidInterface = context.SemanticModel.Compilation.GetTypeByMetadataName(IIdFullName);
+ if (iidInterface is null) return default;
+
+ var implementsIId = false;
+ foreach (var iface in symbol.AllInterfaces)
+ {
+ if (!SymbolEqualityComparer.Default.Equals(iface, iidInterface)) continue;
+ implementsIId = true;
+ break;
+ }
+
+ if (!implementsIId) return default;
+
+ if (!structDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
+ {
+ return new IdStructResult(
+ data: null,
+ diagnostic: new DiagnosticInfo(
+ IdStructDiagnostics.NotPartial,
+ structDecl.Identifier.GetLocation(),
+ symbol.Name));
+ }
+
+ var conflicts = CollectMemberConflicts(symbol);
+ if (conflicts.Count > 0)
+ {
+ return new IdStructResult(
+ data: null,
+ diagnostic: new DiagnosticInfo(
+ IdStructDiagnostics.MemberConflict,
+ structDecl.Identifier.GetLocation(),
+ symbol.Name,
+ string.Join(", ", conflicts)));
+ }
+
+ return new IdStructResult(new IdStructData(symbol), diagnostic: null);
+ }
+
+ private static List CollectMemberConflicts(INamedTypeSymbol symbol)
+ {
+ var result = new List();
+ AddIfDeclared(symbol, "_id", result);
+ AddIfDeclared(symbol, "Id", result);
+ AddIfDeclared(symbol, "__stringId", result);
+ return result;
+ }
+
+ private static void AddIfDeclared(INamedTypeSymbol symbol, string memberName, List output)
+ {
+ foreach (var member in symbol.GetMembers(memberName))
+ {
+ if (member.Kind is not (SymbolKind.Field or SymbolKind.Property)) continue;
+ output.Add(memberName);
+ return;
+ }
+ }
+
+ private static void Emit(SourceProductionContext context, IdStructResult result)
+ {
+ if (result.Diagnostic is { } diag)
+ context.ReportDiagnostic(diag.ToDiagnostic());
+
+ if (result.Data is { } data)
+ IdStructBody.GenerateCode(context, data);
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
new file mode 100644
index 00000000..71818031
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
@@ -0,0 +1,169 @@
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Aspid.Generators.Helper;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Aspid.FastTools.Generators.ProfilerMarkers.Data;
+using static Aspid.Generators.Helper.Classes;
+using static Aspid.FastTools.Descriptions.General;
+using static Aspid.Generators.Helper.Unity.UnityClasses;
+
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Bodies;
+
+internal static class ExtensionClassBody
+{
+ public static void GenerateCode(in SourceProductionContext context, in ImmutableArray markerCalls)
+ {
+ var typeGroups = markerCalls.GroupBy(call => call.Type.TypeKey);
+
+ foreach (var typeGroup in typeGroups)
+ GenerateForType(context, typeGroup);
+ }
+
+ private static void GenerateForType(SourceProductionContext context, IGrouping typeGroup)
+ {
+ var calls = typeGroup.ToImmutableArray();
+ var type = calls[0].Type;
+
+ var memberGroups = calls
+ .GroupBy(call => call.MethodKey)
+ .Select(g => g.OrderBy(c => c.Line).ToImmutableArray())
+ .ToImmutableArray();
+
+ var renamedByMember = AssignUniqueFieldNames(memberGroups);
+
+ var hasNamespace = !string.IsNullOrEmpty(type.Namespace);
+ var aritySuffix = type.Arity > 0 ? $"_{type.Arity}" : string.Empty;
+ var containingPart = type.ContainingTypeChain.Replace('.', '_');
+ var className = $"__{containingPart}{type.TypeName}{aritySuffix}ProfilerMarkerExtensions";
+ var isGeneric = type.TypeParamList.Length > 0;
+
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ .AppendLine()
+ .AppendLineIf(hasNamespace, $"namespace {type.Namespace}")
+ .BeginBlockIf(hasNamespace)
+ .AppendLine($"[{ProfilerMarkerGeneratedCode}]")
+ .AppendLine($"internal static class {className}")
+ .BeginBlock()
+ .AppendProfilerMarkers(type, renamedByMember, isGeneric)
+ .AppendWithoutMessage(type, renamedByMember, isGeneric)
+ .EndBlock()
+ .EndBlockIf(hasNamespace);
+
+ var hintNamespacePart = hasNamespace ? type.Namespace + "." : string.Empty;
+ var hintName = $"{hintNamespacePart}{type.ContainingTypeChain}{type.TypeName}{aritySuffix}ProfilerMarkerExtensions.g.cs";
+ context.AddSource(hintName, code.GetSourceText());
+ }
+
+ private static List> AssignUniqueFieldNames(ImmutableArray> memberGroups)
+ {
+ var seen = new Dictionary();
+ var result = new List>(memberGroups.Length);
+
+ foreach (var member in memberGroups)
+ {
+ var renamed = ImmutableArray.CreateBuilder(member.Length);
+ foreach (var call in member)
+ {
+ seen.TryGetValue(call.MarkerName, out var count);
+ seen[call.MarkerName] = count + 1;
+ var fieldName = count == 0 ? call.MarkerName : $"{call.MarkerName}_{count + 1}";
+ renamed.Add(new RenamedCall(call, fieldName));
+ }
+ result.Add(renamed.MoveToImmutable());
+ }
+
+ return result;
+ }
+
+ private static CodeWriter AppendProfilerMarkers(
+ this CodeWriter code,
+ TypeData type,
+ List> members,
+ bool isGeneric)
+ {
+ if (isGeneric)
+ {
+ code.AppendLine($"private static class Markers{type.TypeParamList}{type.ConstraintsClause}")
+ .BeginBlock();
+ }
+
+ var fieldVisibility = isGeneric ? "public" : "private";
+
+ foreach (var member in members)
+ {
+ foreach (var renamed in member)
+ {
+ var markerValueExpression = BuildMarkerValueExpression(type, renamed.Call);
+ code.AppendMultiline(
+ $"""
+ [{ProfilerMarkerGeneratedCode}]
+ {fieldVisibility} static readonly {ProfilerMarker} {renamed.FieldName} = new({markerValueExpression});
+
+ """);
+ }
+ }
+
+ if (isGeneric)
+ code.EndBlock().AppendLine();
+
+ return code;
+ }
+
+ private static CodeWriter AppendWithoutMessage(
+ this CodeWriter code,
+ TypeData type,
+ List> members,
+ bool isGeneric)
+ {
+ code.AppendLine($"[{ProfilerMarkerGeneratedCode}]")
+ .AppendLine($"public static {ProfilerMarker}.AutoScope Marker{type.TypeParamList}(this {type.FullyQualifiedDisplay} _, [{CallerLineNumberAttribute}] int line = -1){type.ConstraintsClause}")
+ .BeginBlock()
+ .AppendLine("#if ENABLE_PROFILER");
+
+ var prefix = isGeneric ? $"Markers{type.TypeParamList}." : string.Empty;
+ foreach (var member in members)
+ {
+ foreach (var renamed in member)
+ code.AppendLine($"if (line is {renamed.Call.Line}) return {prefix}{renamed.FieldName}.Auto();");
+ }
+
+ code.AppendLine("#endif")
+ .AppendLine("return default;")
+ .EndBlock();
+
+ return code;
+ }
+
+ private static string BuildMarkerValueExpression(TypeData type, MarkerCall call)
+ {
+ if (type.Arity == 0)
+ return $"\"{type.TypeName}.{call.Label} ({call.Line})\"";
+
+ var typeArgs = ExtractTypeParamNames(type.TypeParamList);
+ var interpolations = string.Join(", ", typeArgs.Select(p => $"{{typeof({p}).Name}}"));
+ return $"$\"{type.TypeName}<{interpolations}>.{call.Label} ({call.Line})\"";
+ }
+
+ private static IEnumerable ExtractTypeParamNames(string typeParamList)
+ {
+ if (string.IsNullOrEmpty(typeParamList)) yield break;
+
+ var inner = typeParamList.Substring(1, typeParamList.Length - 2);
+ foreach (var part in inner.Split(','))
+ yield return part.Trim();
+ }
+
+ private readonly struct RenamedCall
+ {
+ public readonly MarkerCall Call;
+ public readonly string FieldName;
+
+ public RenamedCall(MarkerCall call, string fieldName)
+ {
+ Call = call;
+ FieldName = fieldName;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
new file mode 100644
index 00000000..b645efcc
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Data;
+
+internal readonly struct MarkerCall : IEquatable
+{
+ public readonly TypeData Type;
+ public readonly string MethodKey;
+ public readonly int Line;
+ public readonly string MarkerName;
+ public readonly string Label;
+
+ public MarkerCall(
+ TypeData type,
+ string methodKey,
+ int line,
+ string markerName,
+ string markerValue)
+ {
+ Type = type;
+ MethodKey = methodKey;
+ Line = line;
+ MarkerName = markerName + "_Marker_Line_" + line;
+ Label = markerValue;
+ }
+
+ public bool Equals(MarkerCall other) =>
+ Type.Equals(other.Type)
+ && MethodKey == other.MethodKey
+ && Line == other.Line
+ && MarkerName == other.MarkerName
+ && Label == other.Label;
+
+ public override bool Equals(object? obj) => obj is MarkerCall other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = Type.GetHashCode();
+ hash = (hash * 397) ^ MethodKey.GetHashCode();
+ hash = (hash * 397) ^ Line;
+ hash = (hash * 397) ^ MarkerName.GetHashCode();
+ hash = (hash * 397) ^ Label.GetHashCode();
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/TypeData.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/TypeData.cs
new file mode 100644
index 00000000..669478a3
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/TypeData.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Data;
+
+internal readonly struct TypeData : IEquatable
+{
+ public readonly string TypeKey;
+ public readonly string TypeName;
+ public readonly string? Namespace;
+ public readonly string ContainingTypeChain;
+ public readonly string FullyQualifiedDisplay;
+ public readonly string TypeParamList;
+ public readonly string ConstraintsClause;
+ public readonly int Arity;
+
+ public TypeData(
+ string typeKey,
+ string typeName,
+ string? @namespace,
+ string containingTypeChain,
+ string fullyQualifiedDisplay,
+ string typeParamList,
+ string constraintsClause,
+ int arity)
+ {
+ TypeKey = typeKey;
+ TypeName = typeName;
+ Namespace = @namespace;
+ ContainingTypeChain = containingTypeChain;
+ FullyQualifiedDisplay = fullyQualifiedDisplay;
+ TypeParamList = typeParamList;
+ ConstraintsClause = constraintsClause;
+ Arity = arity;
+ }
+
+ public bool Equals(TypeData other) =>
+ TypeKey == other.TypeKey
+ && TypeName == other.TypeName
+ && Namespace == other.Namespace
+ && ContainingTypeChain == other.ContainingTypeChain
+ && FullyQualifiedDisplay == other.FullyQualifiedDisplay
+ && TypeParamList == other.TypeParamList
+ && ConstraintsClause == other.ConstraintsClause
+ && Arity == other.Arity;
+
+ public override bool Equals(object? obj) => obj is TypeData other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = TypeKey.GetHashCode();
+ hash = (hash * 397) ^ TypeName.GetHashCode();
+ hash = (hash * 397) ^ (Namespace?.GetHashCode() ?? 0);
+ hash = (hash * 397) ^ ContainingTypeChain.GetHashCode();
+ hash = (hash * 397) ^ FullyQualifiedDisplay.GetHashCode();
+ hash = (hash * 397) ^ TypeParamList.GetHashCode();
+ hash = (hash * 397) ^ ConstraintsClause.GetHashCode();
+ hash = (hash * 397) ^ Arity;
+ return hash;
+ }
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
new file mode 100644
index 00000000..4e445eae
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
@@ -0,0 +1,233 @@
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Aspid.FastTools.Generators.ProfilerMarkers.Data;
+using Aspid.FastTools.Generators.ProfilerMarkers.Bodies;
+
+namespace Aspid.FastTools.Generators.ProfilerMarkers;
+
+[Generator(LanguageNames.CSharp)]
+internal sealed class ProfilerMarkersGenerator : IIncrementalGenerator
+{
+ private const string TargetClassName = "ProfilerMarkerExtensionsForGenerator";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var callsProvider = context.SyntaxProvider
+ .CreateSyntaxProvider(Predicate, Transform)
+ .Where(static markerCall => markerCall.HasValue)
+ .Select(static (markerCall, _) => markerCall!.Value);
+
+ var collected = callsProvider.Collect();
+ context.RegisterSourceOutput(collected, GenerateCode);
+ }
+
+ private static bool Predicate(SyntaxNode node, CancellationToken _)
+ {
+ if (node is not InvocationExpressionSyntax invocation) return false;
+ if (invocation.Expression is not MemberAccessExpressionSyntax memberAccessExpression) return false;
+
+ return memberAccessExpression.Name is IdentifierNameSyntax
+ {
+ Identifier.ValueText: "Marker"
+ };
+ }
+
+ private static MarkerCall? Transform(GeneratorSyntaxContext context, CancellationToken ct)
+ {
+ var node = context.Node;
+ if (node is not InvocationExpressionSyntax invocation) return null;
+ if (invocation.Expression is not MemberAccessExpressionSyntax memberAccessExpression) return null;
+ if (memberAccessExpression.Name is not IdentifierNameSyntax idName || idName.Identifier.ValueText is not "Marker") return null;
+
+ // Semantic gate: only match Marker() declared on the global-namespace ProfilerMarkerExtensionsForGenerator class.
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(invocation, ct);
+ if (symbolInfo.Symbol is not IMethodSymbol invokedMethod) return null;
+ var owningType = invokedMethod.ContainingType;
+ if (owningType is null) return null;
+ if (owningType.Name != TargetClassName) return null;
+ if (!owningType.ContainingNamespace.IsGlobalNamespace) return null;
+
+ var initialEnclosing = context.SemanticModel.GetEnclosingSymbol(invocation.SpanStart, ct);
+ if (ResolveEnclosingMember(initialEnclosing) is not { } enclosingInfo) return null;
+ var (namedTypeSymbol, markerName, methodKey) = enclosingInfo;
+
+ var markerValue = markerName;
+
+ // Walk past any parentheses so `(this.Marker()).WithName("x")` is still recognised.
+ SyntaxNode outer = invocation;
+ while (outer.Parent is ParenthesizedExpressionSyntax paren)
+ outer = paren;
+
+ if (outer.Parent is MemberAccessExpressionSyntax memberAccessExpressionWithName
+ && memberAccessExpressionWithName.Name is IdentifierNameSyntax { Identifier.ValueText: "WithName" }
+ && memberAccessExpressionWithName.Parent is InvocationExpressionSyntax invocationExpressionWithName
+ && invocationExpressionWithName.ArgumentList.Arguments.FirstOrDefault()?.Expression is { } argExpr
+ && TryExtractStringLiteral(argExpr) is { } extracted)
+ {
+ markerValue = extracted;
+ }
+
+ var lineSpan = invocation.GetLocation().GetLineSpan();
+ var lineNumber = lineSpan.StartLinePosition.Line + 1;
+
+ var typeData = BuildTypeData(namedTypeSymbol);
+
+ return new MarkerCall(typeData, methodKey, lineNumber, markerName, markerValue);
+ }
+
+ private static (INamedTypeSymbol Type, string MarkerName, string MethodKey)? ResolveEnclosingMember(ISymbol? enclosing)
+ {
+ // Walk past synthesized symbols (lambdas, local functions, anonymous methods)
+ // until we find a real declared member that owns the call site.
+ while (enclosing is not null)
+ {
+ switch (enclosing)
+ {
+ case IMethodSymbol method:
+ if (method.MethodKind is MethodKind.LambdaMethod
+ or MethodKind.AnonymousFunction
+ or MethodKind.LocalFunction)
+ {
+ enclosing = method.ContainingSymbol;
+ continue;
+ }
+ if (method.ContainingType is null) return null;
+ return (method.ContainingType, ResolveMarkerName(method), method.ToDisplayString());
+
+ case IFieldSymbol field:
+ if (field.ContainingType is null) return null;
+ return (field.ContainingType, field.Name, field.ToDisplayString());
+
+ case IPropertySymbol property:
+ if (property.ContainingType is null) return null;
+ return (property.ContainingType, property.Name, property.ToDisplayString());
+
+ default:
+ enclosing = enclosing.ContainingSymbol;
+ continue;
+ }
+ }
+
+ return null;
+ }
+
+ private static TypeData BuildTypeData(INamedTypeSymbol symbol)
+ {
+ var typeName = symbol.Name;
+ var ns = symbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : symbol.ContainingNamespace.ToDisplayString();
+
+ var containingChain = string.Empty;
+ if (symbol.ContainingType is not null)
+ {
+ var stack = new Stack();
+ for (var t = symbol.ContainingType; t is not null; t = t.ContainingType)
+ stack.Push(t.Name);
+
+ var sb = new System.Text.StringBuilder();
+ foreach (var name in stack)
+ sb.Append(name).Append('.');
+ containingChain = sb.ToString();
+ }
+
+ var typeKey = (ns is null ? string.Empty : ns + ".") + containingChain + typeName;
+
+ var typeParameters = symbol.TypeParameters;
+ var isGeneric = typeParameters.Length > 0;
+ var typeParamList = isGeneric
+ ? "<" + string.Join(", ", typeParameters.Select(p => p.Name)) + ">"
+ : string.Empty;
+ var constraintsClause = BuildConstraintsClause(typeParameters);
+
+ var fullyQualifiedDisplay = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ return new TypeData(
+ typeKey: typeKey,
+ typeName: typeName,
+ @namespace: ns,
+ containingTypeChain: containingChain,
+ fullyQualifiedDisplay: fullyQualifiedDisplay,
+ typeParamList: typeParamList,
+ constraintsClause: constraintsClause,
+ arity: symbol.Arity);
+ }
+
+ private static string? TryExtractStringLiteral(ExpressionSyntax expr)
+ {
+ switch (expr)
+ {
+ case LiteralExpressionSyntax lit when lit.Token.IsKind(SyntaxKind.StringLiteralToken):
+ return lit.Token.ValueText;
+
+ case InterpolatedStringExpressionSyntax interp:
+ {
+ var sb = new StringBuilder();
+ foreach (var content in interp.Contents)
+ {
+ if (content is not InterpolatedStringTextSyntax text) return null;
+ sb.Append(text.TextToken.ValueText);
+ }
+ return sb.ToString();
+ }
+ }
+
+ return null;
+ }
+
+ private static string ResolveMarkerName(IMethodSymbol enclosing)
+ {
+ if (enclosing.AssociatedSymbol is IPropertySymbol property)
+ {
+ return property.ExplicitInterfaceImplementations.Length > 0
+ ? property.ExplicitInterfaceImplementations[0].Name
+ : property.Name;
+ }
+
+ if (enclosing.MethodKind is MethodKind.Constructor)
+ return "Ctor";
+
+ return enclosing.ExplicitInterfaceImplementations.Length > 0
+ ? enclosing.ExplicitInterfaceImplementations[0].Name
+ : enclosing.Name;
+ }
+
+ private static string BuildConstraintsClause(ImmutableArray typeParameters)
+ {
+ if (typeParameters.Length is 0) return string.Empty;
+
+ var clauses = new List();
+ foreach (var tp in typeParameters)
+ {
+ var constraints = new List();
+
+ if (tp.HasReferenceTypeConstraint) constraints.Add("class");
+ else if (tp.HasUnmanagedTypeConstraint) constraints.Add("unmanaged");
+ else if (tp.HasValueTypeConstraint) constraints.Add("struct");
+
+ if (tp.HasNotNullConstraint) constraints.Add("notnull");
+
+ foreach (var ct in tp.ConstraintTypes)
+ constraints.Add(ct.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+
+ if (tp.HasConstructorConstraint) constraints.Add("new()");
+
+ if (constraints.Count > 0)
+ clauses.Add($"where {tp.Name} : {string.Join(", ", constraints)}");
+ }
+
+ return clauses.Count is 0 ? string.Empty : " " + string.Join(" ", clauses);
+ }
+
+ private static void GenerateCode(SourceProductionContext context, ImmutableArray markerCalls)
+ {
+ if (markerCalls.Length is 0) return;
+ ExtensionClassBody.GenerateCode(context, markerCalls);
+ }
+}
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Properties/launchSettings.json b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Properties/launchSettings.json
similarity index 100%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Properties/launchSettings.json
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Properties/launchSettings.json
diff --git a/Aspid.FastTools.Generators/CLAUDE.md b/Aspid.FastTools.Generators/CLAUDE.md
new file mode 100644
index 00000000..cb793f8a
--- /dev/null
+++ b/Aspid.FastTools.Generators/CLAUDE.md
@@ -0,0 +1,219 @@
+# Aspid.FastTools.Generators
+
+Standalone .NET solution containing Roslyn source generators for the `com.aspid.fasttools` Unity package.
+
+## Commands
+
+```bash
+# Build and auto-deploy DLL into Unity package
+dotnet build -c Release
+
+# Run generator unit tests
+dotnet test
+```
+
+`Directory.Build.targets` copies the compiled DLL to `../Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll` on build.
+
+A repo-level PostToolUse hook (`.claude/hooks/rebuild-generators-on-change.sh`) also runs `dotnet build` automatically after any `Edit`/`Write` to `*.cs` under `Aspid.FastTools.Generators/Aspid.FastTools.Generators/`. The hook intentionally **does not** trigger for tests, the Sample project, or Unity-side edits — keep that scope if you modify it.
+
+## Solution Structure
+
+```
+Aspid.FastTools.Generators/ ← generator implementation
+Aspid.FastTools.Generators.Tests/ ← unit tests + GeneratorTestHost helper
+Aspid.FastTools.Generators.Sample/ ← manual smoke-test project
+Aspid.FastTools.Generators.sln
+Directory.Build.targets
+```
+
+## Target Framework
+
+`netstandard2.0` — required by Roslyn. No Unity assemblies, no runtime packages.
+
+## Dependencies
+
+| Package | Role |
+|---|---|
+| `Microsoft.CodeAnalysis.CSharp` 4.3.0 | Roslyn semantic model and syntax |
+| `Aspid.Generators.Helper` | `CodeWriter` utility for emitting source |
+| `Aspid.Generators.Helper.Unity` | Unity-specific analysis helpers |
+| `SourceGenerator.Foundations` 2.0.13 | Incremental generator infrastructure |
+
+## Generator Implementation Pattern
+
+All generators implement `IIncrementalGenerator` (never the deprecated `ISourceGenerator`).
+
+### Three-Stage Pipeline
+
+```
+Predicate (SyntaxNode) → Transform (SemanticModel) → GenerateCode (SourceProductionContext)
+```
+
+**1. Predicate** — cheap syntax-only filter, no semantic model:
+```csharp
+private static bool Predicate(SyntaxNode node, CancellationToken _)
+{
+ if (node is not StructDeclarationSyntax s) return false;
+ return s.BaseList is { Types.Count: > 0 };
+}
+```
+
+Keep predicates broad enough that diagnostics for malformed declarations (e.g. `IId` struct without `partial`) are still reachable in Transform — they need the semantic model to decide whether to emit a diagnostic vs. silently skip.
+
+**2. Transform** — semantic extraction. Returns a value-equatable `readonly struct` (data and/or diagnostic) or `default` to skip:
+```csharp
+private static IdStructResult Transform(GeneratorSyntaxContext ctx, CancellationToken ct)
+{
+ var symbol = ctx.SemanticModel.GetDeclaredSymbol(structDecl, ct) as INamedTypeSymbol;
+ // resolve IId by metadata name, check AllInterfaces
+ // emit diagnostic if precondition fails, otherwise wrap data
+ return new IdStructResult(new IdStructData(symbol), diagnostic: null);
+}
+```
+
+Always pass `CancellationToken` through to semantic-model APIs that accept it (`GetDeclaredSymbol`, `GetSymbolInfo`, `GetEnclosingSymbol`).
+
+**3. GenerateCode** — emit source using `CodeWriter`, report diagnostics:
+```csharp
+private static void Emit(SourceProductionContext context, IdStructResult result)
+{
+ if (result.Diagnostic is { } d) context.ReportDiagnostic(d.ToDiagnostic());
+ if (result.Data is { } data) IdStructBody.GenerateCode(context, data);
+}
+```
+
+### Data Structures — value-equality is mandatory
+
+Pipeline data passed between stages **must be value-equatable**. Roslyn caches results by comparing them with `Equals`; if equality is reference-based (the default for `readonly struct` containing reference fields), the cache never hits and the generator re-runs on every keystroke. Worse, holding `ISymbol` keeps the source `Compilation` alive.
+
+**Forbidden in data structs:** any `ISymbol`/`INamedTypeSymbol`/`IMethodSymbol`/`SyntaxNode` field. Extract everything you need at Transform time as primitives/strings.
+
+**Required for every data struct:**
+- `readonly struct` with explicit `IEquatable` implementation
+- `Equals` and `GetHashCode` over every field
+- For nested arrays use `ImmutableArray` with element-wise comparison (or `EquatableArray` if introduced)
+
+Reference shapes:
+
+```csharp
+internal readonly struct TypeData : IEquatable
+{
+ public readonly string TypeKey; // "Foo.Outer.Inner"
+ public readonly string TypeName;
+ public readonly string? Namespace;
+ public readonly string ContainingTypeChain; // "Outer.Middle." or ""
+ public readonly string FullyQualifiedDisplay;
+ public readonly string TypeParamList; // "" or ""
+ public readonly string ConstraintsClause;
+ public readonly int Arity;
+ // ... ctor + IEquatable Equals/GetHashCode over all fields
+}
+
+internal readonly struct IdStructData : IEquatable
+{
+ public readonly string StructName;
+ public readonly string TypeParameters; // "" or ""
+ public readonly int Arity;
+ public readonly string? Namespace;
+ public readonly ImmutableArray ContainingTypes;
+ // ... IEquatable walks ContainingTypes element-wise
+}
+```
+
+Cache stability is regression-tested in `IncrementalCacheTests` — that test runs each generator twice over compilations differing only in an unrelated source file, and asserts every output step is `Cached`/`Unchanged`. Adding a non-equatable field to any pipeline struct will fail it.
+
+### Diagnostic Delivery
+
+Generators that need to report diagnostics use a value-equatable wrapper that survives the pipeline:
+
+```csharp
+internal readonly struct DiagnosticInfo : IEquatable
+{
+ public readonly string DescriptorId; // "AFID001"
+ public readonly string MessageArg0;
+ public readonly string? MessageArg1;
+ public readonly string? FilePath; // location is rebuilt from path + spans
+ public readonly TextSpan TextSpan;
+ public readonly LinePositionSpan LineSpan;
+
+ public Diagnostic ToDiagnostic() { ... } // looks up the DiagnosticDescriptor by id
+}
+
+internal readonly struct IdStructResult : IEquatable
+{
+ public readonly IdStructData? Data;
+ public readonly DiagnosticInfo? Diagnostic;
+ public bool IsEmpty => Data is null && Diagnostic is null;
+}
+```
+
+Transform returns the wrapper; the `RegisterSourceOutput` callback emits the diagnostic and/or generates the source. Storing `Location` directly in the wrapper would defeat caching (it holds a `SyntaxTree` reference); rebuild it from `FilePath` + spans inside `ToDiagnostic()`.
+
+Descriptors live in `Generators/{Feature}/Data/{Feature}Diagnostics.cs` with IDs prefixed `AFID` (Aspid FastTools, IdStruct domain). The csproj suppresses `RS2008` (analyzer release tracking) — this is a private in-tree generator, not a published analyzer package.
+
+### Generated File Naming
+
+Every hint name must include enough qualifiers to be unique across types with the same short name in different namespaces or containing types. Prefer `{ns}.{containing-chain}{Name}{aritySuffix}.{tag}.g.cs`.
+
+| Generator | Pattern |
+|---|---|
+| `IdStructGenerator` | `{Namespace}.{Containing.Chain.}{StructName}{_Arity?}.IId.g.cs` |
+| `ProfilerMarkersGenerator` | `{Namespace}.{Containing.Chain.}{TypeName}{_Arity?}ProfilerMarkerExtensions.g.cs` |
+
+`{_Arity?}` is `_2`, `_3`, … for generic types (omitted when arity is 0). The containing chain is dot-joined with a trailing dot, or empty for top-level types.
+
+The emitted `partial`/extension class names follow the same uniqueness rule; for `ProfilerMarkersGenerator` the extension class is `__{Containing_}{TypeName}{_Arity?}ProfilerMarkerExtensions` (containing chain joined with `_`).
+
+All generated files begin with `// `.
+
+## Existing Generators
+
+### ProfilerMarkersGenerator
+
+Finds every `.Marker()` call site, semantically verifies it resolves to `ProfilerMarkerExtensionsForGenerator.Marker` (global-namespace class on the Unity side — calls to user-defined extensions named `Marker` are ignored), and generates `private static readonly ProfilerMarker` fields unique to each call site.
+
+- **Field name:** `"{markerName}_Marker_Line_{line}"`. If multiple `.Marker()` calls share the same `(markerName, line)` (e.g. two calls on one source line), subsequent fields receive a `_2`/`_3` suffix and the dispatcher is updated in lockstep.
+- **Marker display value:** `"{TypeName}.{member} ({line})"`. For generic enclosing types the marker value is interpolated with `typeof(T).Name` so each closed instantiation gets its own runtime label.
+- **`.WithName(string)` override:** accepts string literals, plain interpolated strings without holes (`$"X"`), and survives a `(this.Marker()).WithName(...)` parenthesised receiver. Interpolated strings with substitutions are silently ignored — the generator falls back to the method name.
+- **Enclosing resolution:** walks past lambdas, anonymous functions, and local functions to the nearest declared `IMethodSymbol`/`IFieldSymbol`/`IPropertySymbol`. Field initializers use the field name as the marker.
+- **Release-build gating:** the dispatcher body (the `if (line is N) return …` chain) is wrapped in `#if ENABLE_PROFILER`. When the symbol is undefined (non-development player builds) the method falls through to `return default;`, so player builds pay no per-call lookup cost. The static `ProfilerMarker` field declarations are emitted unconditionally — their `Begin`/`End` calls already strip via Unity's `[Conditional("ENABLE_PROFILER")]`.
+
+### IdStructGenerator
+
+Finds `partial struct` types implementing `Aspid.FastTools.Ids.IId` (transitive interfaces are resolved through `INamedTypeSymbol.AllInterfaces`) and generates boilerplate: serialized `_id` field, `Id` property, editor-only `__stringId` field. All generated members carry `[GeneratedCode("Aspid.FastTools.Generators.IdStructGenerator", "1.0.0")]`.
+
+**Supported shapes:**
+- Nested types — generated code is wrapped in matching `partial class`/`partial struct`/`partial record`/`partial record struct` containing-type declarations, with full type-parameter lists (`partial class Outer`).
+- Generic target structs — `partial struct MyId : IId` is supported; the wrapper is emitted with `` and the hint name encodes the arity.
+- Global namespace, file-scoped namespace, multi-level nesting, transitive `IId` implementations.
+
+**Diagnostics:**
+
+| Id | Title | Trigger |
+|---|---|---|
+| `AFID001` | IId struct must be partial | Struct implements `IId` but the declaration lacks `partial` |
+| `AFID002` | Generated IId members already declared | User declares any of `_id`/`Id`/`__stringId` themselves |
+
+When a diagnostic fires, the body is **not** emitted — the user gets a generator-level error pointing at the struct identifier instead of a CS compile error from inside the generated source.
+
+## Conventions
+
+- `[Generator(LanguageNames.CSharp)]` on every generator class
+- Generators and their `Bodies/Data/` types are `internal sealed` / `internal`. The Tests project gets access via `` in the csproj
+- Generated members use the attribute string from `Descriptions/General.cs` (`ProfilerMarkerGeneratedCode` for ProfilerMarkers, `IdStructGeneratedCode` for IdStruct)
+- Always check `IsGlobalNamespace` before emitting a `namespace` block
+- Avoid LINQ in hot Predicate/Transform paths — allocations defeat incremental caching
+- Diagnostic descriptor IDs use the `AFID` prefix (Aspid FastTools IdStruct); add new descriptor groups under their feature's `Data/` folder
+
+## Test Infrastructure
+
+`Aspid.FastTools.Generators.Tests/Helpers/GeneratorTestHost.cs` provides:
+
+- `RunIdStruct(userSource)` / `RunProfilerMarkers(userSource)` — drive the generator over a synthesised compilation that includes the necessary stubs (`IIdDefinition`, `UnityEngineStubs`, `ProfilerMarkerStubs`)
+- `AssertNoErrors(GeneratorRun)` — fails the test if either the generator emitted error-severity diagnostics **or** the resulting `Compilation.GetDiagnostics()` reports any compile error in the generated source
+
+Use `AssertNoErrors` on every test that exercises a happy-path emission — it catches malformed C# (missing using directives, bad escapes, unbalanced blocks) for free.
+
+`IncrementalCacheTests` verifies cache stability — when extending pipeline data, run it to confirm equality is intact.
+
+The `Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit` package is referenced but not currently used; the harness uses `CSharpGeneratorDriver` directly because the test surface is small.
diff --git a/Aspid.FastTools.Generators/Directory.Build.targets b/Aspid.FastTools.Generators/Directory.Build.targets
new file mode 100644
index 00000000..9c685132
--- /dev/null
+++ b/Aspid.FastTools.Generators/Directory.Build.targets
@@ -0,0 +1,11 @@
+
+
+
+
+ <_UnityDestination>$(MSBuildThisFileDirectory)../Aspid.FastTools/Assets/Aspid/FastTools/
+
+
+
+
+
+
diff --git a/Aspid.UnityFastTools/.gitignore b/Aspid.FastTools/.gitignore
similarity index 100%
rename from Aspid.UnityFastTools/.gitignore
rename to Aspid.FastTools/.gitignore
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta b/Aspid.FastTools/Assets/Aspid.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
rename to Aspid.FastTools/Assets/Aspid.meta
index a72d9f7d..2d31186d 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
+++ b/Aspid.FastTools/Assets/Aspid.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c6490710e847c4157a38f045d5205648
+guid: def7f8b5a72624babb42cd71818bf201
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools.meta b/Aspid.FastTools/Assets/Aspid/FastTools.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll b/Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll
similarity index 99%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll
rename to Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll
index 95a75d75..54cbb84b 100644
Binary files a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll and b/Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Aspid.FastTools.Generators.dll.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md b/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md
new file mode 100644
index 00000000..76f4a284
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md
@@ -0,0 +1,86 @@
+# Changelog
+
+All notable changes to **Aspid.FastTools** will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.0.0-rc.1] — 2026-05-18
+
+First release candidate for `1.0.0`. Marketed as a preview while the **ID System** is finalised — its public API, generated boilerplate and editor workflow may still change before the final `1.0.0` release.
+
+### Added
+
+#### ProfilerMarkers
+- `this.Marker()` extension method that resolves to a `ProfilerMarker` unique to the call-site (enclosing type + method/field/property + line number).
+- `ProfilerMarkersGenerator` (Roslyn incremental source generator) that emits one `ProfilerMarker` field per call-site and a per-type dispatcher. Walks through lambdas and local functions; supports `.WithName(literal)` and plain `$"..."` interpolated names; deduplicates fields when several call-sites share a line.
+- Semantic gating: only `ProfilerMarkerExtensionsForGenerator.Marker` is rewritten, user-defined `Marker()` extensions are left untouched.
+- The generated dispatcher is wrapped in `#if ENABLE_PROFILER` and falls back to `return default;`, so non-development builds carry no per-call cost.
+
+#### Serializable Type System
+- `SerializableType` — `[Serializable]` wrapper around `System.Type` that stores the assembly-qualified name and resolves the type lazily on first access; implicit conversion to `Type`.
+- `SerializableType` — generic variant with a base-type constraint enforced both at compile time and in the editor picker.
+- `TypeSelectorAttribute` — `PropertyAttribute` (editor-only via `[Conditional("UNITY_EDITOR")]`) that drives the type picker on `string` fields and lets you constrain the picker to one or more base types.
+- `TypeAllow` — `[Flags]` enum that opts the picker into abstract classes (`Abstract`), interfaces (`Interface`) or both (`All`); defaults to concrete classes only.
+- `ComponentTypeSelector` — `[Serializable]` helper that surfaces a `Component`-typed sibling on the same `GameObject` through the inspector.
+- `TypeSelectorWindow` — `EditorWindow`-based hierarchical type picker with namespace tree, fuzzy search, keyboard navigation and a public `Show(...)` API for invoking it from custom editors.
+- Property drawers for `SerializableType`, `SerializableType`, `ComponentTypeSelector` and `[TypeSelector]` strings (IMGUI + UI Toolkit). UI Toolkit drawer renders a reusable `TypeField` / `InspectorTypeField` element.
+
+#### Enum System
+- `EnumValues` — `[Serializable]` enum-keyed dictionary that survives Unity serialization and handles `[Flags]` enums.
+- `EnumValue` — single-entry building block used by the dictionary and exposed for standalone use.
+- Custom property drawers for both types with inline editing in the inspector.
+
+#### ID System (Beta)
+- `IId` marker interface and `[UniqueId]` attribute for ID-struct types (one struct ↔ one `IdRegistry`).
+- `IdRegistry` (`ScriptableObject`) holding the canonical `int ↔ string` map; runtime lookups via `TryGetId`, `TryGetName`, `Contains(int)`, `Contains(string)`.
+- `IdRegistryResolver` — lazily builds a `Type AQN → registry` index on first lookup and keeps it incrementally up to date through an `AssetPostprocessor`; `IdRegistry.OnEnable` marks the cache dirty so re-imports are picked up.
+- `UniqueIdIndex` — sibling index used by the editor to detect `[UniqueId]` field-value collisions across registries.
+- `IdStructGenerator` (Roslyn incremental source generator) emits the struct-side boilerplate (`_id`, `Id`, `__stringId`, equality, conversions) and supports generic target structs as well as generic containing types.
+- Analyzer diagnostics: `AFID001` (the target `IId` struct must be `partial`) and `AFID002` (one of the generated members is already declared by the user).
+- Editor UI driven by `RegistryEditorCore`: C#-identifier name validation, full Undo, explicit clean-up flow for invalid/duplicate entries, Sort/Group toolbar, manual next-id entry with backward-step warning, Open-Registry shortcut from the `IdStruct` property drawer.
+- `Assets → Create → Aspid/Id Registry/Id Registry` menu entry for creating registry assets.
+
+#### VisualElement fluent extensions
+- Extensive UI Toolkit fluent API on `VisualElement` and friends — layout, sizing, style, borders, colors, transitions, callbacks, USS classes/sheets, child management.
+- Per-element helper sets: `Button`, `Field`, `Focusable`, `Foldout`, `HelpBox`, `Image`, `IMGUIContainer`, `IMixedValueSupport`, `INotifyValueChanged`, `IStyle`, `List`, `Manipulators`, `ProgressBar`, `Slider`, `TextElement`, `CallbackEventHandler`, `ICustomStyle`.
+- Style preset helpers via `VisualElementExtensions.Style.Preset.cs` and reusable `ICustomStyle.TryGetByEnum` extension for USS-driven enum bindings.
+- Editor-side `VisualElement` command extensions in `Unity.Editor/Scripts/VisualElements/Extensions/`.
+
+#### Optional Mathematics integration
+- Satellite assembly `Aspid.FastTools.Unity.VisualElements.Math` adds `INotifyValueChanged` extensions (`SetValue`, `ValueChanged`) for `Unity.Mathematics` types (`float2/3/4`, `int2/3/4`, etc.).
+- Compiled only when `com.unity.mathematics` is installed (`versionDefines` gate, define symbol `ASPID_FASTTOOLS_UNITY_MATHEMATICS_INTEGRATION`).
+
+#### Internal editor components
+Shared UI Toolkit elements used across the package's editor surfaces, all built on the base palette `Aspid-FastTools-Default-Dark.uss`:
+
+- `AspidLabel`, `AspidBox`, `AspidGradientButton`, `AspidHelpBox`, `AspidInspectorHeader`, `AspidDividingLine`, `AspidAnimatedLogo`, `AspidAnimatedTitle`, `AspidAnimatedDotsBackground`, `AspidHoverGradientOverlay`.
+- USS-driven style structs (`AspidLabelSizeStyle`, `AspidLabelFontStyle`, `AspidDividingLineSizeStyle`, `AspidDividingLineDirectionStyle`, `AspidAnimatedLogoPulseSpeedStyle`, `AspidAnimatedLogoPulseHoverAmplitudeStyle`, `AspidAnimatedLogoLayerImageStyle`, `StatusStyle`, `ThemeStyle`, …).
+- Shared helpers: `AspidStyles` (single source of truth for USS class/property names), `InlineStyle` (USS-vs-code precedence helper), `DoubleClickTracker`.
+
+#### SerializedProperty extensions
+- Fluent chainable helpers in `SerializePropertyExtensions` (`SetValue`, `Apply`, `Persistent`) and a `Reflection` partial that exposes the backing field/value behind a `SerializedProperty`.
+
+#### IMGUI scopes
+- Disposable `VerticalScope`, `HorizontalScope`, `ScrollViewScope` wrappers that expose the layout `Rect` for hit-testing.
+
+#### Editor helper extensions
+- `MonoScript.GetScriptName()` and `MonoScript.GetScriptNameWithIndex()` — respect `[AddComponentMenu]` and append an index suffix when several copies of the same component live on one `GameObject`.
+
+#### Welcome window
+- `WelcomeWindow` editor window (menu `Tools/Aspid FastTools/Welcome`) listing the package's installable samples by parsing `package.json`.
+- `WelcomeWindowStartup` shows the window automatically on first import.
+
+#### Samples
+Five installable samples shipped under `Samples~/` (UPM convention, imported via Package Manager):
+
+- `Types`, `EnumValues`, `Ids`, `ProfilerMarkers`, `VisualElements`.
+
+#### Documentation
+- EN and RU READMEs at the package root and at `Documentation/EN/` and `Documentation/RU/`, mirroring the same content with language-appropriate image paths.
+- Per-feature reference documents next to each README: `SerializedPropertyExtensions.md`, `VisualElementExtensions.md`.
+
+[Unreleased]: https://github.com/VPDPersonal/Aspid.FastTools/compare/v1.0.0-rc.1...HEAD
+[1.0.0-rc.1]: https://github.com/VPDPersonal/Aspid.FastTools/releases/tag/v1.0.0-rc.1
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md.meta
new file mode 100644
index 00000000..dca866f0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/CHANGELOG.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: feb8641612d44a0498af86ef18621b44
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Documentation.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN.meta
new file mode 100644
index 00000000..cc63ccc8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 227c6c85a08c428c8299094196de787b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/README.md
new file mode 100644
index 00000000..9dd95a93
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/README.md
@@ -0,0 +1,621 @@
+
+
+**Aspid.FastTools** is a set of tools designed to minimize routine code writing in Unity. It combines Roslyn-powered source generators with a curated collection of runtime and editor utilities — including per-call-site `ProfilerMarker` registration, a serializable `System.Type`, an `EnumValues` dictionary, a stable `int ↔ string` ID registry, fluent UI Toolkit extensions and IMGUI layout scopes.
+
+## Source Code
+
+[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+
+## Table of Contents
+
+- **Getting Started**
+ - [Integration](#integration)
+ - [Claude Code Plugin](#claude-code-plugin)
+ - [Donate](#donate)
+- **Features**
+ - [ProfilerMarker](#profilermarker)
+ - [Serializable Type System](#serializable-type-system)
+ - [Enum System](#enum-system)
+ - [ID System (Beta)](#id-system-beta)
+ - [SerializedProperty Extensions](#serializedproperty-extensions)
+ - [IMGUI Layout Scopes](#imgui-layout-scopes)
+ - [VisualElement Extensions](#visualelement-extensions)
+ - [Editor Helper Extensions](#editor-helper-extensions)
+
+---
+
+## Integration
+
+Install Aspid.FastTools via UPM (Unity Package Manager) — add the package using its Git URL:
+
+```
+https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Aspid/FastTools
+```
+
+To install a specific version, append the release tag as a `#` fragment (see [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases) for the list of available tags):
+
+```
+https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Aspid/FastTools#v1.0.0-rc.1
+```
+
+---
+
+## Claude Code Plugin
+
+If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), the companion [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) marketplace ships the `aspid-fasttools` plugin — a set of skills that teach Claude Code this package's conventions and APIs.
+
+Add the marketplace and install the plugin:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Included skills:
+
+- **`aspid-id-struct`** — scaffold a new `IId` struct and `[UniqueId]` fields for the [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — insert `this.Marker()` call sites with the right `using`/scope shape.
+- **`aspid-visual-element-fluent`** — build editor or runtime UI using the fluent `VisualElement` extensions.
+
+---
+
+## Donate
+
+This project is developed on a voluntary basis. If you find it useful, you can support its development financially. This helps allocate more time to improving and maintaining **Aspid.FastTools**.
+
+You can donate via the following platforms:
+* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
+
+---
+
+## ProfilerMarker
+
+Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
+
+```csharp
+using UnityEngine;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Some code
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Some code
+ using var _ = this.Marker().WithName("Calculate");
+ // Some code
+ }
+ }
+}
+```
+
+
+Generated code
+
+
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 13) return DoSomething1_Marker_Line_13.Auto();
+ if (line is 19) return DoSomething2_Marker_Line_19.Auto();
+ if (line is 22) return DoSomething2_Marker_Line_22.Auto();
+#endif
+ return default;
+ }
+}
+```
+
+
+
+### Result
+
+
+
+---
+
+## Serializable Type System
+
+Allows serializing a `System.Type` reference in the Unity Inspector. The selected type is stored as an assembly-qualified name and resolved lazily on first access.
+
+### SerializableType
+
+Two variants are available:
+
+- **`SerializableType`** — stores any type (base type is `object`)
+- **`SerializableType`** — stores a type constrained to `T` or its subclasses
+
+Both support implicit conversion to `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class Ability : MonoBehaviour
+{
+ public abstract void Activate();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ [SerializeField] private SerializableType _abilityType;
+
+ private void Start()
+ {
+ var ability = (Ability)gameObject.AddComponent(_abilityType.Type);
+ ability.Activate();
+ }
+}
+```
+
+
+### TypeSelectorAttribute
+
+An editor-only `PropertyAttribute` that restricts the type selection popup to specific base types. Applied to `string` fields that store assembly-qualified type names.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // base type: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public TypeAllow Allow { get; set; } // default: TypeAllow.None
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Property | Description |
+|----------|-------------|
+| `Allow` | Which special type categories (abstract classes, interfaces) the picker includes in addition to plain concrete classes. Default: `TypeAllow.None` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class AbilityModifier
+{
+ public abstract void Apply();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ // Each element of the array is its own picker constrained to AbilityModifier.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+}
+```
+
+> The complete sample — `Ability` / `AbilitySelector` / `EnemyBase` and their subclasses — ships in the `Types` sample (Package Manager → Aspid.FastTools → Samples).
+
+---
+
+### Type Selector Window
+
+The Inspector shows a button that opens a searchable popup window with:
+
+- Hierarchical namespace organization
+- Text search with filtering
+- Keyboard navigation (Arrow keys, Enter, Escape)
+- Navigation history (back button)
+- Assembly disambiguation for types with identical names
+
+
+
+The same window is available as a public API — open it from any editor code (custom inspectors, `EditorWindow`, menu items) when you need a type picker outside the standard `SerializableType` / `[TypeSelector]` flow.
+
+```csharp
+namespace Aspid.FastTools.Types.Editors
+{
+ public sealed class TypeSelectorWindow : EditorWindow
+ {
+ public static void Show(
+ Rect screenRect,
+ Type[] types = null,
+ string currentAqn = "",
+ TypeAllow allow = TypeAllow.None,
+ Action onSelected = null);
+ }
+}
+```
+
+| Parameter | Description |
+|-----------|-------------|
+| `screenRect` | Screen-space rectangle the dropdown is anchored to. |
+| `types` | Base types used to filter visible items. Only types assignable to **all** entries are listed. Defaults to `typeof(object)`. |
+| `currentAqn` | Assembly-qualified name of the currently selected type, used to pre-navigate to its location. Pass `null` or empty to start at the root. |
+| `allow` | Which special type kinds (abstract classes, interfaces) are included in addition to concrete classes. Default: `TypeAllow.None`. |
+| `onSelected` | Callback invoked with the assembly-qualified name of the selected type, or `null` if the user chose ``. |
+
+### ComponentTypeSelector
+
+A serializable struct that renders a type-switching dropdown in the Inspector. Add it as a field to a base class — picking a subtype rewrites `m_Script` on the `SerializedObject`, effectively changing the component or ScriptableObject to the chosen subtype.
+
+The dropdown is automatically constrained to subtypes of the class that declares the field. No additional configuration is required.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class EnemyBase : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _enemyType;
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ public abstract void Attack();
+}
+
+public sealed class FastEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log($"Fast enemy strikes! (speed: {_speed})");
+}
+
+public sealed class TankEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _armor = 50f;
+
+ public override void Attack() =>
+ Debug.Log($"Tank attacks! (armor: {_armor})");
+}
+```
+
+
+
+---
+
+## Enum System
+
+Provides serializable enum-to-value mappings configurable from the Inspector.
+
+### EnumValues\
+
+A serializable collection of `EnumValue` entries with a configurable default value. Implements `IEnumerable>`.
+
+| Member | Description |
+|--------|-------------|
+| `TValue GetValue(Enum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
+| `bool Equals(Enum, Enum)` | Equality check with proper `[Flags]` support |
+
+Supports `[Flags]` enums: `Equals` uses `HasFlag` and treats `0`-valued members correctly.
+
+```csharp
+using System;
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+public enum DamageType { Physical, Fire, Ice, Poison }
+
+[Flags]
+public enum StatusEffect
+{
+ None = 0,
+ Burning = 1,
+ Frozen = 2,
+ Slowed = 4,
+ Stunned = 8,
+}
+
+public sealed class DamageDealer : MonoBehaviour
+{
+ [SerializeField] private EnumValues _damageMultipliers;
+ [SerializeField] private EnumValues _damageColors;
+
+ // Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
+ // so list composite entries BEFORE their constituent flags.
+ [SerializeField] private EnumValues _speedMultipliersByStatus;
+
+ [SerializeField] private DamageType _currentType;
+ [SerializeField] private StatusEffect _activeEffects;
+
+ private void DealDamage()
+ {
+ var multiplier = _damageMultipliers.GetValue(_currentType);
+ var color = _damageColors.GetValue(_currentType);
+ var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
+ // ...
+ }
+}
+```
+
+
+In the Inspector, select the enum type in the `EnumValues` header, then assign a value for each enum member. Right-click the property to open a context menu with **Populate Missing Enum Members** — it appends an entry for every enum member not yet in the list, seeded with the current Default Value.
+
+> The complete sample — `DamageDealer` / `DamageType` / `StatusEffect` — ships in the `EnumValues` sample (Package Manager → Aspid.FastTools → Samples).
+
+---
+
+## ID System (Beta)
+
+> **Beta:** the ID System is currently in beta. The public API, generated code layout and editor workflow may change in future releases.
+
+Maps an asset-assignable name to a stable integer ID. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string lookups at runtime.
+
+A single `IdRegistry` ScriptableObject maps string names to stable integer IDs and provides full `int ↔ string` lookups at runtime.
+
+### Setup
+
+**1.** Declare a `partial struct` implementing `IId`. The source generator adds the required fields and property automatically:
+
+```csharp
+using Aspid.FastTools.Ids;
+
+public partial struct EnemyId : IId { }
+```
+
+Generated code:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId; // editor-only field, stripped from player builds
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+The generator reports `AFID001` if the struct is missing `partial`, and `AFID002` if your code already declares `_id`, `Id`, or `__stringId` (the generator skips emission so you get a clear error pointing at the struct rather than a CS compile error inside generated source). Generic targets (`EnemyId`) and generic containing types are supported.
+
+**2.** Create the registry asset and bind it to the struct type in its Inspector:
+- `Assets → Create → Aspid → Id Registry`
+
+**3.** Use the struct as a serialized field. The Inspector shows a dropdown of registered names; the selector window also lets you create new entries on the fly:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+public class EnemySpawner : MonoBehaviour
+{
+ [SerializeField] private EnemyId _targetEnemy;
+
+ private void Spawn()
+ {
+ int id = _targetEnemy.Id; // stable integer, safe for switch / Dictionary
+ }
+}
+```
+
+
+
+### UniqueIdAttribute
+
+Marks a field as requiring a unique value across all assets of the declaring type. The Inspector shows a warning if two assets share the same ID.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class UniqueIdAttribute : PropertyAttribute { }
+```
+
+
+
+### IdRegistry
+
+`ScriptableObject` in `Aspid.FastTools.Ids` that stores `(int, string)` entries and keeps the lookup tables available at runtime. Each name is assigned a stable, auto-incrementing ID that never changes when other entries are added or removed.
+
+| Member | Description |
+|--------|-------------|
+| `bool TryGetId(string name, out int id)` | Returns `true` and the ID when found; otherwise `false` |
+| `bool TryGetName(int id, out string name)` | Returns `true` and the name when found; otherwise `false` and `string.Empty` |
+| `bool Contains(int id)` | Whether an ID is registered |
+| `bool Contains(string name)` | Whether a name is registered |
+| `int Count` | Number of entries |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Registered IDs / names, in registration order |
+| `IEnumerator> GetEnumerator()` | Iterate `(id, name)` pairs |
+
+The registry derives from `ScriptableObject` directly and exposes a generic counterpart `IdRegistry` (with `T : struct, IId`) that adds typed `Contains(T)` and `TryGetName(T, out string)` overloads. Edits — adding, renaming, removing entries — happen through the registry inspector and `RegistryEditorCore`, not via a public runtime API.
+
+
+
+---
+
+## SerializedProperty Extensions
+
+Chainable extensions on `SerializedProperty` for synchronizing the owning `SerializedObject`, writing typed values, and reflecting on the underlying field.
+
+```csharp
+property
+ .Update()
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+The package covers:
+
+- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
+- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (and `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). Each comes with a paired `SetXxxAndApply` variant.
+- **Enum setters** — `SetEnumFlag` and `SetEnumIndex` (each + `AndApply`).
+- **Arrays** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (each + `AndApply`).
+- **References** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, and `SetBoxed` (Unity 6+).
+- **Reflection helpers** — `GetPropertyType`, `GetMemberInfo`, `GetClassInstance` for resolving the C# member and runtime instance behind a property.
+
+> Full method-by-method reference: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
+
+---
+
+## IMGUI Layout Scopes
+
+Three `ref struct` scopes — `VerticalScope`, `HorizontalScope`, `ScrollViewScope` — wrap `EditorGUILayout.Begin*` / `End*`. Each exposes a `Rect` property and calls the matching `End*` method on `Dispose`:
+
+```csharp
+using (VerticalScope.Begin())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (HorizontalScope.Begin())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (ScrollViewScope.Begin(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+Capture the group rect with the `out`-overload when needed:
+
+```csharp
+using (VerticalScope.Begin(out var rect, GUI.skin.box))
+{
+ EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
+ EditorGUILayout.LabelField("Boxed content");
+}
+```
+
+All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (optional `GUIStyle`, `GUILayoutOption[]`, scroll view options, etc.).
+
+---
+
+## VisualElement Extensions
+
+Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
+
+> Full method-by-method reference: [VisualElementExtensions.md](VisualElementExtensions.md)
+
+### Example
+
+A reactive editor for an `AbilityConfig` `ScriptableObject` — title and status pill in the header, `PropertyField` body, and a Warning `HelpBox` that toggles based on `ManaCost`.
+
+```csharp
+[CustomEditor(typeof(AbilityConfig))]
+internal sealed class AbilityConfigEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ var config = (AbilityConfig)target;
+
+ var badge = new Label()
+ .SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
+ .SetPaddingX(10).SetPaddingY(3)
+ .SetBorderRadius(10).SetBorderWidth(1);
+
+ var helpBox = new HelpBox(
+ "This ability costs no mana — is that intentional?",
+ HelpBoxMessageType.Warning)
+ .SetMarginTop(8).SetBorderRadius(6);
+
+ var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
+ .AddValueChanged(_ => Refresh());
+
+ Refresh();
+ return new VisualElement()
+ .SetBorderRadius(10).SetBorderWidth(1)
+ .AddChild(new VisualElement()
+ .SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
+ .SetPaddingX(14).SetPaddingY(12)
+ .AddChild(new Label(target.GetScriptName())
+ .SetFlexGrow(1).SetFontSize(15)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(badge))
+ .AddChild(new VisualElement()
+ .SetPaddingX(14).SetPaddingY(12)
+ .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
+ .AddChild(manaField)
+ .AddChild(helpBox));
+
+ void Refresh()
+ {
+ var isFree = config.ManaCost is 0;
+ badge.SetText(isFree ? "FREE" : $"{config.ManaCost} MP");
+ helpBox.SetDisplay(isFree ? DisplayStyle.Flex : DisplayStyle.None);
+ }
+ }
+}
+```
+
+> The complete sample — `AbilityConfig.cs`, the polished `AbilityConfigEditor.cs` (custom colors, subtitle and divider, used in the screenshot below) and two `.asset` examples — ships in the `VisualElements` sample (Package Manager → Aspid.FastTools → Samples).
+
+### Result
+
+
+
+---
+
+## Editor Helper Extensions
+
+Utility methods for getting display names of Unity objects in custom editors.
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Returns the display name of a Unity object:
+- If the type has `[AddComponentMenu]`, returns `ObjectNames.GetInspectorTitle(obj)`
+- Otherwise returns `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Returns the display name with a count suffix when multiple components of the same type exist on the same GameObject. For example, if two `AudioSource` components are attached, the second returns `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — or "Custom Name" if [AddComponentMenu("Custom Name")] is present
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" when a second component of the same type exists
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/README.md.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/README.md.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md
new file mode 100644
index 00000000..c22b34c2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md
@@ -0,0 +1,124 @@
+# SerializedProperty Extensions — full reference
+
+Chainable extension methods on `SerializedProperty` for synchronizing the owning `SerializedObject`, setting values, and reflecting on the underlying field.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+All extensions are generic over `T : SerializedProperty` and return the same property instance, so calls can be chained freely.
+
+## Update / Apply
+
+Thin wrappers around the matching `SerializedObject` methods on `property.serializedObject`.
+
+```csharp
+property
+ .Update()
+ .SetInt(42)
+ .ApplyModifiedProperties();
+```
+
+| Method | Description |
+|--------|-------------|
+| `Update()` | Calls `serializedObject.Update()` |
+| `UpdateIfRequiredOrScript()` | Calls `serializedObject.UpdateIfRequiredOrScript()` |
+| `ApplyModifiedProperties()` | Calls `serializedObject.ApplyModifiedProperties()` |
+
+## SetValue / SetXxx — typed setters
+
+For each supported type four variants exist:
+
+| Variant | Behavior |
+|---------|----------|
+| `SetValue(value)` | Generic dispatch — picks the right typed setter based on the value's runtime type, returns `property` |
+| `SetValueAndApply(value)` | `SetValue(value)` followed by `ApplyModifiedProperties()` |
+| `SetXxx(value)` | Typed setter (e.g. `SetInt`) that writes to the matching `SerializedProperty.xxxValue` field |
+| `SetXxxAndApply(value)` | `SetXxx(value)` followed by `ApplyModifiedProperties()` |
+
+### Supported types
+
+| Method family | Unity type | Notes |
+|---------------|------------|-------|
+| `SetInt` | `int` | |
+| `SetUint` | `uint` | |
+| `SetLong` | `long` | |
+| `SetUlong` | `ulong` | |
+| `SetFloat` | `float` | |
+| `SetDouble` | `double` | |
+| `SetBool` | `bool` | |
+| `SetString` | `string` | |
+| `SetColor` | `Color` | |
+| `SetGradient` | `Gradient` | |
+| `SetHash128` | `Hash128` | |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` | |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` | |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` | |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` | |
+| `SetVector4` | `Vector4` | |
+| `SetQuaternion` | `Quaternion` | |
+| `SetAnimationCurve` | `AnimationCurve` | |
+| `SetEntityId` | `Unity.Entities.EntityId` | Unity 6.2+. The apply-variant is named `SetEntityIdApply` *(method name preserves the source typo: missing `And`)* |
+
+### Enum setters
+
+Enum values do not flow through `SetValue` — use the explicit pair below depending on whether the field is a `[Flags]` enum:
+
+| Method | Description |
+|--------|-------------|
+| `SetEnumFlag(int)` / `SetEnumFlagAndApply(int)` | Writes to `enumValueFlag` |
+| `SetEnumIndex(int)` / `SetEnumIndexAndApply(int)` | Writes to `enumValueIndex` |
+
+### Example
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// Equivalent forms
+property.SetValue(10).ApplyModifiedProperties();
+property.SetValueAndApply(10);
+property.SetInt(10).ApplyModifiedProperties();
+property.SetIntAndApply(10);
+
+// Chain multiple setters
+property
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+## Array operations
+
+| Method | Description |
+|--------|-------------|
+| `SetArraySize(int)` / `SetArraySizeAndApply(int)` | Sets `property.arraySize` |
+| `AddArraySize(int = 1)` / `AddArraySizeAndApply(int = 1)` | Increases `arraySize` by the given amount (default `1`) |
+| `RemoveArraySize(int = 1)` / `RemoveArraySizeAndApply(int = 1)` | Decreases `arraySize` by the given amount (default `1`) |
+
+## Reference setters
+
+| Method | Description | Notes |
+|--------|-------------|-------|
+| `SetManagedReference(object)` / `SetManagedReferenceAndApply(object)` | Writes to `managedReferenceValue` (target must be a `[SerializeReference]` field) | |
+| `SetObjectReference(Object)` / `SetObjectReferenceAndApply(Object)` | Writes to `objectReferenceValue` | |
+| `SetExposedReference(Object)` / `SetExposedReferenceAndApply(Object)` | Writes to `exposedReferenceValue` | |
+| `SetBoxed(object)` / `SetBoxedAndApply(object)` | Writes to `boxedValue` | Unity 6+ |
+
+## Reflection helpers
+
+For drawer / inspector code that needs to inspect the runtime type or instance behind a property:
+
+| Method | Returns | Description |
+|--------|---------|-------------|
+| `GetPropertyType()` | `Type` or `null` | Returns the `FieldType` / `PropertyType` of the C# member that backs the property. `null` if the member can't be resolved. |
+| `GetMemberInfo()` | `MemberInfo` or `null` | Locates the field/property on the owning class whose name matches `SerializedProperty.name`. Walks base classes via `TypeExtensions.GetMembersInfosIncludingBaseClasses`. |
+| `GetClassInstance()` | `object` | Walks `propertyPath` from the root `targetObject` and returns the runtime instance that directly contains this property. Supports nested objects, arrays, and `List` fields. |
+
+```csharp
+public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
+{
+ var declaringType = property.GetPropertyType();
+ var owner = property.GetClassInstance();
+ // …
+}
+```
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md.meta
new file mode 100644
index 00000000..78b1558d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/SerializedPropertyExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d89b4f8675a09411280afbba6c1aaf33
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md
new file mode 100644
index 00000000..c6a44b57
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md
@@ -0,0 +1,600 @@
+# VisualElement Extensions — full reference
+
+Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
+
+```csharp
+using Aspid.FastTools.UIElements; // runtime extensions
+using Aspid.FastTools.UIElements.Editors; // editor-only extensions (e.g. AddOpenScriptCommand)
+```
+
+## Core element operations
+
+```csharp
+element
+ .SetName("MyElement")
+ .SetVisible(true)
+ .SetTooltip("Tooltip text")
+ .AddChild(new Label("Hello"))
+ .AddChildren(child1, child2, child3);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetName(string)` | Sets `element.name` |
+| `SetVisible(bool)` | Sets `element.visible` |
+| `SetTooltip(string)` | Sets `element.tooltip` |
+| `SetUserData(object)` | Sets `element.userData` |
+| `SetEnabledSelf(bool)` | Sets `element.enabledSelf` |
+| `SetPickingMode(PickingMode)` | Sets `element.pickingMode` |
+| `SetUsageHints(UsageHints)` | Sets `element.usageHints` |
+| `SetViewDataKey(string)` | Sets `element.viewDataKey` |
+| `SetLanguageDirection(LanguageDirection)` | Sets `element.languageDirection` |
+| `SetDisablePlayModeTint(bool)` | Sets `element.disablePlayModeTint` |
+| `SetDataSource(object)` | Sets `element.dataSource` |
+| `SetDataSourceType(Type)` | Sets `element.dataSourceType` |
+| `SetDataSourcePath(PropertyPath)` | Sets `element.dataSourcePath` |
+| `AddChild(VisualElement)` | Appends a child, returns the parent |
+| `AddChildren(params VisualElement[])` | Appends multiple children |
+| `AddChildren(IEnumerable)` | Appends from a sequence |
+| `AddChildren(List)` | Appends from a list |
+| `AddChildren(Span)` | Appends from a span |
+| `AddChildren(ReadOnlySpan)` | Appends from a read-only span |
+| `InsertChild(int, VisualElement)` | Inserts a child at the specified index |
+| `InsertChildren(int, params VisualElement[])` | Inserts multiple children starting at an index |
+| `InsertChildren(int, IEnumerable)` | Inserts from a sequence |
+| `InsertChildren(int, List)` | Inserts from a list |
+| `InsertChildren(int, Span)` | Inserts from a span |
+| `InsertChildren(int, ReadOnlySpan)` | Inserts from a read-only span |
+
+> `RegisterCallbackOnce` and `RegisterCallbackOnce` are available on all Unity versions (polyfill included for versions prior to 2023.1).
+
+## Focusable
+
+| Method | Description |
+|--------|-------------|
+| `SetFocus()` | Attempts to give focus to the element |
+| `SetBlur()` | Tells the element to release focus |
+| `IsFocus()` | Returns whether the element currently has keyboard focus |
+| `SetTabIndex(int)` | Sets `element.tabIndex` |
+| `SetFocusable(bool)` | Sets `element.focusable` |
+| `SetDelegatesFocus(bool)` | Sets `element.delegatesFocus` |
+
+## USS & class operations
+
+| Method | Description |
+|--------|-------------|
+| `AddClass(string)` | Adds a USS class |
+| `RemoveClass(string)` | Removes a USS class |
+| `ClearClasses()` | Removes all USS classes |
+| `ToggleInClass(string)` | Toggles a USS class on/off |
+| `EnableInClass(string, bool)` | Adds or removes a USS class based on a condition |
+| `AddStyleSheets(StyleSheet)` | Adds a `StyleSheet` |
+| `RemoveStyleSheets(StyleSheet)` | Removes a `StyleSheet` |
+| `AddStyleSheetsFromResource(string)` | Adds a stylesheet loaded via `Resources.Load` |
+| `RemoveStyleSheetsFromResource(string)` | Removes a stylesheet loaded via `Resources.Load` |
+
+## Style extensions — by category
+
+All style methods are also available on `IStyle` directly (same method names, operate on the style object).
+
+### Layout
+
+| Method | Style property |
+|--------|---------------|
+| `SetFlexBasis(StyleLength)` | `flexBasis` |
+| `SetFlexGrow(StyleFloat)` | `flexGrow` |
+| `SetFlexShrink(StyleFloat)` | `flexShrink` |
+| `SetFlexWrap(StyleEnum)` | `flexWrap` |
+| `SetFlexDirection(FlexDirection)` | `flexDirection` |
+| `SetAlignSelf(StyleEnum)` | `alignSelf` |
+| `SetAlignItems(StyleEnum)` | `alignItems` |
+| `SetAlignContent(StyleEnum)` | `alignContent` |
+| `SetJustifyContent(StyleEnum)` | `justifyContent` |
+| `SetPosition(StyleEnum)` | `position` |
+
+### Size
+
+| Method | Description |
+|--------|-------------|
+| `SetSize(StyleLength)` | Sets both width and height |
+| `SetSize(width?, height?)` | Sets width and/or height independently |
+| `SetMinSize(StyleLength)` | Sets both minWidth and minHeight |
+| `SetMinSize(width?, height?)` | |
+| `SetMaxSize(StyleLength)` | Sets both maxWidth and maxHeight |
+| `SetMaxSize(width?, height?)` | |
+| `SetWidth(StyleLength)` | `width` |
+| `SetMinWidth(StyleLength)` | `minWidth` |
+| `SetMaxWidth(StyleLength)` | `maxWidth` |
+| `SetHeight(StyleLength)` | `height` |
+| `SetMinHeight(StyleLength)` | `minHeight` |
+| `SetMaxHeight(StyleLength)` | `maxHeight` |
+
+### Spacing
+
+All spacing methods have a uniform-value overload, a per-side overload (`top`, `right`, `bottom`, `left`), single-side setters, and X/Y-axis pair setters.
+
+| Method | Style properties |
+|--------|------------------|
+| `SetMargin(…)` / `SetPadding(…)` / `SetDistance(…)` | `Top/Right/Bottom/Left` (uniform or per-side) |
+| `SetMarginX/Y` · `SetPaddingX/Y` · `SetDistanceX/Y` | Sets the horizontal (X = `Left`+`Right`) or vertical (Y = `Top`+`Bottom`) pair |
+| `SetMarginTop/Right/Bottom/Left` | Single-side margin |
+| `SetPaddingTop/Right/Bottom/Left` | Single-side padding |
+| `SetDistanceTop/Right/Bottom/Left` *(via `SetTop` / `SetRight` / `SetBottom` / `SetLeft`)* | Single-side absolute offset (`top` / `right` / `bottom` / `left` style properties) |
+
+> `SetDistance` is the wrapper for the four `top`/`right`/`bottom`/`left` style properties used by absolute positioning. `SetTop`, `SetRight`, `SetBottom`, `SetLeft` are direct single-property aliases.
+
+### Font
+
+| Method | Style property |
+|--------|---------------|
+| `SetUnityFont(StyleFont)` | `unityFont` |
+| `SetFontSize(StyleLength)` | `fontSize` |
+| `SetUnityFontDefinition(StyleFontDefinition)` | `unityFontDefinition` |
+| `SetUnityFontStyleAndWeight(StyleEnum)` | `unityFontStyleAndWeight` |
+
+### Font style presets
+
+Convenience methods for toggling bold / italic without overwriting the other flag:
+
+| Method | Description |
+|--------|-------------|
+| `SetNormalUnityFontStyleAndWeight()` | Resets to `FontStyle.Normal` |
+| `AddBoldUnityFontStyleAndWeight()` | Adds bold, preserving italic |
+| `RemoveBoldUnityFontStyleAndWeight()` | Removes bold, preserving italic |
+| `AddItalicUnityFontStyleAndWeight()` | Adds italic, preserving bold |
+| `RemoveItalicUnityFontStyleAndWeight()` | Removes italic, preserving bold |
+
+### Text
+
+| Method | Style property | Notes |
+|--------|---------------|-------|
+| `SetWorldSpacing(StyleLength)` | `wordSpacing` | |
+| `SetLetterSpacing(StyleLength)` | `letterSpacing` | |
+| `SetUnityTextAlign(TextAnchor)` | `unityTextAlign` | |
+| `SetTextShadow(StyleTextShadow)` | `textShadow` | |
+| `SetUnityTextOutlineColor(StyleColor)` | `unityTextOutlineColor` | |
+| `SetUnityTextOutlineWidth(StyleFloat)` | `unityTextOutlineWidth` | |
+| `SetUnityParagraphSpacing(StyleLength)` | `unityParagraphSpacing` | |
+| `SetTextOverflow(StyleEnum)` | `textOverflow` | |
+| `SetUnityTextOverflowPosition(TextOverflowPosition)` | `unityTextOverflowPosition` | |
+| `SetUnityTextGenerator(TextGeneratorType)` | `unityTextGenerator` | Unity 6+ |
+| `SetUnityEditorTextRenderingMode(EditorTextRenderingMode)` | `unityEditorTextRenderingMode` | Unity 6+ |
+| `SetUnityTextAutoSize(StyleTextAutoSize)` | `unityTextAutoSize` | Unity 6.2+ |
+| `SetWhiteSpace(StyleEnum)` | `whiteSpace` | |
+
+### Color & Opacity
+
+| Method | Style property |
+|--------|---------------|
+| `SetColor(StyleColor)` | `color` |
+| `SetColor(string)` | `color` parsed from an HTML string (`"#RRGGBB"` or a named color) |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+### Border
+
+| Method | Description |
+|--------|-------------|
+| `SetBorderColor(StyleColor)` | All sides |
+| `SetBorderColor(top?, right?, bottom?, left?)` | Per side |
+| `SetBorderColorX(StyleColor)` · `SetBorderColorY(StyleColor)` | Horizontal (left + right) or vertical (top + bottom) pair |
+| `SetBorderColorTop/Right/Bottom/Left(StyleColor)` | Single side |
+| `SetBorderRadius(StyleLength)` | All corners |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | Per corner |
+| `SetBorderRadiusTop(StyleLength)` · `SetBorderRadiusBottom(StyleLength)` | Top or bottom corner pair |
+| `SetBorderRadiusTopLeft/TopRight/BottomLeft/BottomRight(StyleLength)` | Single corner |
+| `SetBorderWidth(StyleFloat)` | All sides |
+| `SetBorderWidth(top?, right?, bottom?, left?)` | Per side |
+| `SetBorderWidthX(StyleFloat)` · `SetBorderWidthY(StyleFloat)` | Horizontal or vertical pair |
+| `SetBorderWidthTop/Right/Bottom/Left(StyleFloat)` | Single side |
+
+### Background
+
+| Method | Style property |
+|--------|---------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundColor(string)` | `backgroundColor` parsed from an HTML string (`"#RRGGBB"` or a named color) |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundImageFromResource(string)` | Loads a `Texture2D` via `Resources.Load` and assigns it to `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | Both X and Y |
+| `SetBackgroundPosition(x?, y?)` | Independently |
+| `SetBackgroundPositionX(StyleBackgroundPosition)` | `backgroundPositionX` |
+| `SetBackgroundPositionY(StyleBackgroundPosition)` | `backgroundPositionY` |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+### Transform
+
+| Method | Style property |
+|--------|---------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+### Aspect, Filter & Material
+
+Available on Unity 6000.3+.
+
+| Method | Style property |
+|--------|---------------|
+| `SetAspectRation(StyleRatio)` | `aspectRatio` *(method name preserves the source typo)* |
+| `SetFilter(StyleList)` | `filter` |
+| `SetUnityMaterial(StyleMaterialDefinition)` | `unityMaterial` |
+
+### Transition
+
+| Method | Style property |
+|--------|---------------|
+| `SetTransitionDelay(StyleList)` | `transitionDelay` |
+| `SetTransitionDuration(StyleList)` | `transitionDuration` |
+| `SetTransitionProperty(StyleList)` | `transitionProperty` |
+| `SetTransitionTimingFunction(StyleList)` | `transitionTimingFunction` |
+
+### Overflow & Visibility
+
+| Method | Style property |
+|--------|---------------|
+| `SetOverflow(StyleEnum)` | `overflow` |
+| `SetUnityOverflowClipBox(StyleEnum)` | `unityOverflowClipBox` |
+| `SetVisibility(StyleEnum)` | `visibility` |
+| `SetDisplay(DisplayStyle)` | `display` |
+
+### Unity Slice
+
+| Method | Description |
+|--------|-------------|
+| `SetUnitySlice(StyleInt)` | All sides |
+| `SetUnitySlice(top?, right?, bottom?, left?)` | Per side |
+| `SetUnitySliceX(StyleInt)` · `SetUnitySliceY(StyleInt)` | Horizontal (left + right) or vertical (top + bottom) pair |
+| `SetUnitySliceTop/Right/Bottom/Left(StyleInt)` | Single side |
+| `SetUnitySliceScale(StyleFloat)` | `unitySliceScale` |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+### Cursor
+
+| Method | Style property |
+|--------|---------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+## Specialized element extensions
+
+### TextElement
+
+```csharp
+label
+ .SetText("Hello World")
+ .SetEnableRichText(true)
+ .SetParseEscapeSequences(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the displayed text |
+| `SetEnableRichText(bool)` | Enables rich-text tag parsing |
+| `SetEmojiFallbackSupport(bool)` | Enables emoji fallback rendering |
+| `SetParseEscapeSequences(bool)` | Whether escape sequences (e.g. `\n`) are parsed |
+| `SetDisplayTooltipWhenElided(bool)` | Shows the elided text in a tooltip on hover |
+
+### ITextEdition (TextField, IntegerField, …)
+
+```csharp
+textField
+ .SetPlaceholder("Search…")
+ .SetMaxLength(64)
+ .SetIsDelayed(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetMaxLength(int)` | Maximum number of characters |
+| `SetMaskChar(char)` | Character used to mask password input |
+| `SetIsDelayed(bool)` | Defers value change until focus loss / Enter |
+| `SetIsReadOnly(bool)` | Disables editing |
+| `SetIsPassword(bool)` | Toggles password mode (uses mask char) |
+| `SetPlaceholder(string)` | Placeholder text shown when empty |
+| `SetAutoCorrection(bool)` | Enables auto-correction (mobile) |
+| `SetHideMobileInput(bool)` | Hides the mobile soft input |
+| `SetHideSoftKeyboard(bool)` | Hides the on-screen soft keyboard |
+| `SetHidePlaceholderOnFocus(bool)` | Removes the placeholder on focus |
+| `SetKeyboardType(TouchScreenKeyboardType)` | Sets the touch-screen keyboard type |
+
+### ITextSelection
+
+```csharp
+textField
+ .SetIsSelectable(true)
+ .SetSelectAllOnFocus(true)
+ .AddOnCursorIndexChange(() => Debug.Log(textField.cursorIndex));
+```
+
+| Method | Description |
+|--------|-------------|
+| `AddOnCursorIndexChange(Action)` / `RemoveOnCursorIndexChange(Action)` | Cursor-index change subscription |
+| `AddOnSelectIndexChange(Action)` / `RemoveOnSelectIndexChange(Action)` | Selection-index change subscription |
+| `SetCursorIndex(int)` | Sets the current cursor index |
+| `SetSelectIndex(int)` | Sets the current selection anchor |
+| `SetIsSelectable(bool)` | Whether text can be selected |
+| `SetSelectAllOnFocus(bool)` | Selects all text on focus |
+| `SetSelectAllOnMouseUp(bool)` | Selects all text on mouse release |
+| `SetDoubleClickSelectsWord(bool)` | Double-click selects the word under cursor |
+| `SetTripleClickSelectsLine(bool)` | Triple-click selects the line under cursor |
+
+### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+### BaseBoolField (Toggle)
+
+```csharp
+toggle
+ .SetLabel("Enabled")
+ .SetText("Show advanced settings")
+ .SetToggleOnLabelClick(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the label next to the toggle box |
+| `SetLabel(string)` | Sets the field-level label |
+| `SetToggleOnLabelClick(bool)` | Whether clicking the label toggles the value |
+
+### INotifyValueChanged\
+
+```csharp
+field.SetValue(42, notify: false); // sets value without raising ChangeEvent
+field.AddValueChanged(evt => Debug.Log(evt.newValue));
+field.RemoveValueChanged(myCallback);
+```
+
+Typed overloads are provided for `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `decimal`, `char`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `GUID`, `Quaternion`, `Matrix4x4`, `Gradient`, `AnimationCurve`, `Delegate`, `Enum`, `Object`, `object`, plus a generic `SetValue` fallback.
+
+> When the `com.unity.mathematics` package is installed, the `ASPID_FASTTOOLS_UNITY_MATHEMATICS_INTEGRATION` define is set automatically and adds `SetValue` / `AddValueChanged` / `RemoveValueChanged` overloads for `int2/3/4` (and `intMxN`), `float2/3/4` (and `floatMxN`), `bool2/3/4` (and `boolMxN`), and `quaternion`.
+
+### IMixedValueSupport
+
+```csharp
+field.SetShowMixedValue(true); // shows the mixed-value indicator
+```
+
+### Button
+
+```csharp
+button
+ .AddClicked(() => Debug.Log("Clicked"))
+ .SetClickable(new Clickable(() => { }))
+ .SetIconImage(myBackground);
+```
+
+| Method | Description |
+|--------|-------------|
+| `AddClicked(Action)` | Subscribes to `Button.clicked` |
+| `RemoveClicked(Action)` | Unsubscribes from `Button.clicked` |
+| `SetClickable(Clickable)` | Sets `Button.clickable` |
+| `SetIconImage(Background)` | Sets `Button.iconImage` |
+
+### Slider / BaseSlider\
+
+```csharp
+slider
+ .SetLowValue(0f)
+ .SetHighValue(100f)
+ .SetShowInputField(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetLowValue(TValue)` | Sets the minimum slider value |
+| `SetHighValue(TValue)` | Sets the maximum slider value |
+| `SetFill(bool)` | Whether the track is filled up to the current value |
+| `SetInverted(bool)` | Reverses the slider direction |
+| `SetPageSize(float)` | Controls how much the value changes per page step |
+| `SetShowInputField(bool)` | Shows a numeric input field alongside the slider |
+| `SetDirection(SliderDirection)` | Sets the slider orientation |
+
+### ProgressBar
+
+```csharp
+progressBar.SetTitle("Loading...").SetLowValue(0f).SetHighValue(100f);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetTitle(string)` | Sets the title displayed in the center |
+| `SetLowValue(float)` | Sets the minimum value |
+| `SetHighValue(float)` | Sets the maximum value |
+
+### HelpBox
+
+```csharp
+helpBox
+ .SetText("Something went wrong")
+ .SetMessageType(HelpBoxMessageType.Warning);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the help-box message text |
+| `SetMessageType(HelpBoxMessageType)` | Sets the icon / severity (`None` / `Info` / `Warning` / `Error`) |
+
+### Foldout
+
+```csharp
+foldout
+ .SetText("Section Title")
+ .SetToggleOnLabelClick(true)
+ .SetValue(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the foldout title |
+| `SetToggleOnLabelClick(bool)` | Whether clicking the title toggles expansion |
+
+### Image
+
+```csharp
+image
+ .SetImage(myTexture)
+ .SetTintColor(Color.white)
+ .SetScaleMode(ScaleMode.ScaleToFit);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetImage(Texture)` | Sets `Image.image` |
+| `SetImageFromResource(string)` | Loads a texture via `Resources.Load` |
+| `SetSprite(Sprite)` | Sets `Image.sprite` |
+| `SetSpriteFromResource(string)` | Loads a sprite via `Resources.Load` |
+| `SetVectorImage(VectorImage)` | Sets `Image.vectorImage` |
+| `SetVectorImageFromResource(string)` | Loads a vector image via `Resources.Load` |
+| `SetUv(Rect)` | Sets the UV rect |
+| `SetSourceRect(Rect)` | Sets the source rect |
+| `SetTintColor(Color)` | Sets the image tint |
+| `SetScaleMode(ScaleMode)` | Sets the scale mode |
+
+### IMGUIContainer
+
+```csharp
+container
+ .SetOnGUIHandler(() => GUILayout.Label("IMGUI"))
+ .SetCullingEnabled(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetOnGUIHandler(Action)` | Replaces the `onGUIHandler` callback |
+| `AddOnGUIHandler(Action)` | Subscribes to `onGUIHandler` |
+| `RemoveOnGUIHandler(Action)` | Unsubscribes from `onGUIHandler` |
+| `SetCullingEnabled(bool)` | Skips `onGUIHandler` when the element is offscreen |
+| `SetContextType(ContextType)` | Sets the IMGUI context type |
+
+### Collection views (ListView, TreeView, MultiColumn variants)
+
+Common methods are spread across multiple targeted extensions:
+
+- `BaseVerticalCollectionViewExtensions` — applies to **all** collection views (ListView, TreeView, MultiColumn variants).
+- `BaseListViewExtensions` — applies to ListView and MultiColumnListView.
+- `BaseTreeViewExtensions` — applies to TreeView and MultiColumnTreeView.
+- `ListViewExtensions` / `TreeViewExtensions` — `MakeItem`/`BindItem`/`UnbindItem`/`DestroyItem` factories per view.
+- `MultiColumnListViewExtensions` / `MultiColumnTreeViewExtensions` — multi-column-specific helpers.
+
+```csharp
+listView
+ .SetItemsSource(items)
+ .SetMakeItem(() => new Label())
+ .SetBindItem((el, i) => ((Label)el).SetText(items[i]))
+ .SetSelectionType(SelectionType.Single)
+ .AddSelectionChanged(selected => Debug.Log(selected));
+```
+
+#### Source, layout and behavior — `BaseVerticalCollectionView`
+
+| Method | Description | Notes |
+|--------|-------------|-------|
+| `SetItemsSource(IList)` | Underlying data source | |
+| `SetReorderable(bool)` | Enables drag-to-reorder | |
+| `SetSelectedIndex(int)` | Selects a specific index | |
+| `SetSelectionType(SelectionType)` | None / Single / Multiple | |
+| `SetFixedItemHeight(float)` | Fixed item height (for `FixedHeight` virtualization) | |
+| `SetVirtualizationMethod(CollectionVirtualizationMethod)` | `FixedHeight` or `DynamicHeight` | |
+| `SetHorizontalScrollingEnabled(bool)` | Enables horizontal scrolling | |
+| `SetShowAlternatingRowBackgrounds(AlternatingRowBackground)` | Zebra striping mode | |
+| `SetMakeFooter(Func)` · `AddMakeFooter` · `RemoveMakeFooter` | Footer factory | Unity 6+ |
+| `SetMakeHeader(Func)` · `AddMakeHeader` · `RemoveMakeHeader` | Header factory | Unity 6+ |
+| `SetMakeNoneElement(Func)` · `AddMakeNoneElement` · `RemoveMakeNoneElement` | Empty-state factory | Unity 6+ |
+
+#### Events — `BaseVerticalCollectionView`
+
+| Method | Description |
+|--------|-------------|
+| `AddItemsChosen(Action>)` / `RemoveItemsChosen` | Items confirmed (e.g. double-click / Enter) |
+| `AddSelectionChanged(Action>)` / `RemoveSelectionChanged` | Selection changed (objects) |
+| `AddSelectedIndicesChanged(Action>)` / `RemoveSelectedIndicesChanged` | Selection changed (indices) |
+| `AddItemIndexChanged(Action)` / `RemoveItemIndexChanged` | Item moved (drag-reorder) |
+| `AddItemsSourceChanged(Action)` / `RemoveItemsSourceChanged` | `itemsSource` reference changed |
+| `AddCanStartDrag(Func)` / `RemoveCanStartDrag` | Custom drag-start gating |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Drag-and-drop preparation |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Drag-and-drop visual mode |
+| `AddHandleDrop(Func)` / `RemoveHandleDrop` | Drop handling |
+
+#### `BaseListView`-specific
+
+| Method | Description |
+|--------|-------------|
+| `SetAllowAdd(bool)` · `SetAllowRemove(bool)` | Toggles built-in add/remove buttons |
+| `SetHeaderTitle(string)` | Title shown when foldout header is on |
+| `SetShowFoldoutHeader(bool)` | Wraps the list in a `Foldout` |
+| `SetShowAddRemoveFooter(bool)` | Toggles the add/remove footer |
+| `SetShowBoundCollectionSize(bool)` | Shows the collection-size field |
+| `SetReorderMode(ListViewReorderMode)` | `Simple` or `Animated` |
+| `SetBindingSourceSelectionMode(BindingSourceSelectionMode)` | Auto-assign / manual |
+| `SetOnAdd(Action)` · `AddOnAdd` · `RemoveOnAdd` | Custom add-button callback |
+| `SetOnRemove(Action)` · `AddOnRemove` · `RemoveOnRemove` | Custom remove-button callback |
+| `SetOverridingAddButtonBehavior(Action)` · `AddOverridingAddButtonBehavior` · `RemoveOverridingAddButtonBehavior` | Replace default add-button click |
+| `AddItemsAdded(Action>)` / `RemoveItemsAdded` | Items added by index |
+| `AddItemsRemoved(Action>)` / `RemoveItemsRemoved` | Items removed by index |
+
+#### `BaseTreeView`-specific
+
+| Method | Description |
+|--------|-------------|
+| `SetAutoExpand(bool)` | Auto-expand new nodes |
+| `AddItemExpandedChanged(Action)` / `RemoveItemExpandedChanged` | Subscription to expansion changes |
+
+#### `ListView` / `TreeView` item factories
+
+These methods are duplicated across `ListViewExtensions` and `TreeViewExtensions` (each operating on its own view type).
+
+| Method | Description |
+|--------|-------------|
+| `SetMakeItem(Func)` · `AddMakeItem` · `RemoveMakeItem` | Item factory |
+| `SetBindItem(Action)` · `AddBindItem` · `RemoveBindItem` | Item binding |
+| `SetUnbindItem(Action)` · `AddUnbindItem` · `RemoveUnbindItem` | Item unbinding |
+| `SetDestroyItem(Action)` · `AddDestroyItem` · `RemoveDestroyItem` | Item teardown |
+| `SetItemTemplate(VisualTreeAsset)` | UXML template used to build items |
+
+#### `MultiColumnListView` / `MultiColumnTreeView`
+
+| Method | Description |
+|--------|-------------|
+| `SetSortingMode(ColumnSortingMode)` | Built-in sorting mode for the column header |
+
+## Editor commands (editor-only)
+
+```csharp
+using Aspid.FastTools.UIElements.Editors;
+
+image.AddOpenScriptCommand(target);
+// Double-clicking the element opens the script for 'target' in the IDE
+```
+
+| Method | Target | Description |
+|--------|--------|-------------|
+| `AddOpenScriptCommand(Object)` | `VisualElement` | Registers a double-click handler that opens the source script for the given `MonoBehaviour` / `ScriptableObject` in the IDE. |
+| `BindTo(SerializedObject)` | `VisualElement` | Calls `BindingExtensions.Bind` on the element. |
+| `BindTo(SerializedObject, string propertyPath)` | `IBindable` | Sets `bindingPath` and binds to the given `SerializedObject`. |
+| `BindPropertyTo(SerializedProperty)` | `IBindable` | Calls `BindingExtensions.BindProperty` with the supplied property. |
+| `Initialize(Enum defaultValue, bool includeObsoleteValues = false)` | `EnumField` / `EnumFlagsField` | Initializes the field to the supplied default enum value. |
+| `AddValueChanged(EventCallback)` / `RemoveValueChanged(...)` | `PropertyField` | Subscribes / unsubscribes to property change notifications. |
+
+## USS custom-style helpers (`ICustomStyle`)
+
+```csharp
+using Aspid.FastTools.UIElements;
+
+private static readonly CustomStyleProperty ThemeProperty = new("--aspid-fasttools-prop-theme");
+
+void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
+{
+ if (evt.customStyle.TryGetByEnum(ThemeProperty, out ThemeStyle.Type theme))
+ ApplyTheme(theme);
+}
+```
+
+| Method | Description |
+|--------|-------------|
+| `ICustomStyle.TryGetByEnum(CustomStyleProperty, out T)` | Resolves a string-typed USS custom property and parses it case-insensitively as the enum `T`. Used by every `*Style` struct that exposes a USS-driven enum (`ThemeStyle`, `StatusStyle`, `AspidLabelSizeStyle`, etc.). |
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md.meta
new file mode 100644
index 00000000..9a0b54f9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/EN/VisualElementExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a732922edf5904ae49ce09e54bf91bf6
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif
new file mode 100644
index 00000000..cbac450f
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif.meta
similarity index 98%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif.meta
index 062121ee..417ec98a 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_component_type_selector.gif.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: da2d1f94eff104523b2953ef0fef0b7d
+guid: d72ef52ee4cd4a51ac48ba2d7577b3d2
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png
new file mode 100644
index 00000000..670bdd33
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png.meta
new file mode 100644
index 00000000..9d361238
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_enum_values.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: eaf246028a8504c458b4fc9a614151f3
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif
new file mode 100644
index 00000000..c429e26c
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif.meta
new file mode 100644
index 00000000..2847fbb6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_collision.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: b1bb43ffc072458497e0dc566a6fdd90
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png
new file mode 100644
index 00000000..2622e875
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png.meta
new file mode 100644
index 00000000..d4ee8e97
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_registry.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: deff05bdcee7740e98c2fd06c500e51a
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif
new file mode 100644
index 00000000..84b40f2a
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif.meta
new file mode 100644
index 00000000..77466a67
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_id_selector.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: dfaaf12db0e543e987c9ef464f693381
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_profiler_markers.png b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_profiler_markers.png
new file mode 100644
index 00000000..3547c17c
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_profiler_markers.png differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_profiler_markers.png.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_profiler_markers.png.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif
new file mode 100644
index 00000000..f0cb32e8
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif.meta
new file mode 100644
index 00000000..d86a88f6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_readme_banner.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 3db613f6c024540ca80097f3d2f9e736
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif
new file mode 100644
index 00000000..30de9b89
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif.meta
new file mode 100644
index 00000000..1acefc21
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_serializable_type.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: b304c7446f8b445299406325a9608849
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png
new file mode 100644
index 00000000..afb53020
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png.meta
new file mode 100644
index 00000000..7553ff5d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_type_selector_window.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 648f40dc17f7a46a99e35e8dcf5318e7
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif
new file mode 100644
index 00000000..e0f00447
Binary files /dev/null and b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif differ
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif.meta
new file mode 100644
index 00000000..3b65374a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/Images/aspid_fasttools_visual_element.gif.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 64b746b29ae0246c5abd41cbad8100d9
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU.meta
new file mode 100644
index 00000000..52eac5fd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a64f88afda4440d4a0e4c182bcf7ac1e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md
new file mode 100644
index 00000000..2eb1a5c7
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md
@@ -0,0 +1,621 @@
+
+
+**Aspid.FastTools** — набор инструментов, предназначенных для минимизации рутинного написания кода в Unity. Пакет объединяет генераторы кода на базе Roslyn и подборку runtime- и editor-утилит: регистрация `ProfilerMarker` для каждого места вызова, сериализуемый `System.Type`, словарь `EnumValues`, стабильный реестр `int ↔ string` ID, fluent-расширения UI Toolkit и IMGUI-скоупы для разметки.
+
+## Source Code
+
+[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+
+## Содержание
+
+- **Getting Started**
+ - [Integration](#integration)
+ - [Claude Code Plugin](#claude-code-plugin)
+ - [Donate](#donate)
+- **Features**
+ - [ProfilerMarker](#profilermarker)
+ - [Serializable Type System](#serializable-type-system)
+ - [Enum System](#enum-system)
+ - [ID System (Beta)](#id-system-beta)
+ - [SerializedProperty Extensions](#serializedproperty-extensions)
+ - [IMGUI Layout Scopes](#imgui-layout-scopes)
+ - [VisualElement Extensions](#visualelement-extensions)
+ - [Editor Helper Extensions](#editor-helper-extensions)
+
+---
+
+## Integration
+
+Установите Aspid.FastTools через UPM (Unity Package Manager) — добавьте пакет по его Git URL:
+
+```
+https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Aspid/FastTools
+```
+
+Чтобы установить конкретную версию, добавьте тег релиза в виде `#`-фрагмента (список доступных тегов — на странице [Releases](https://github.com/VPDPersonal/Aspid.FastTools/releases)):
+
+```
+https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Aspid/FastTools#v1.0.0-rc.1
+```
+
+---
+
+## Claude Code Plugin
+
+Если вы используете [Claude Code](https://docs.claude.com/en/docs/claude-code), сопутствующий маркетплейс [Aspid.Claude.Plugins](https://github.com/VPDPersonal/Aspid.Claude.Plugins) поставляет плагин `aspid-fasttools` — набор скиллов, которые обучают Claude Code конвенциям и API этого пакета.
+
+Добавьте маркетплейс и установите плагин:
+
+```sh
+/plugin marketplace add VPDPersonal/Aspid.Claude.Plugins
+/plugin install aspid-fasttools@aspid-claude-plugins
+```
+
+Включённые скиллы:
+
+- **`aspid-id-struct`** — создаёт новую `IId`-структуру и `[UniqueId]`-поля для [ID System](#id-system-beta).
+- **`aspid-profiler-marker`** — вставляет вызовы `this.Marker()` с правильной формой `using`/scope.
+- **`aspid-visual-element-fluent`** — собирает editor- или runtime-UI через fluent-расширения `VisualElement`.
+
+---
+
+## Donate
+
+Этот проект разрабатывается на добровольной основе. Если он оказался для вас полезным, вы можете поддержать его развитие финансово. Это поможет уделять больше времени улучшению и сопровождению **Aspid.FastTools**.
+
+Поддержать проект можно через следующие платформы:
+* \[[Unity Asset Store](https://assetstore.unity.com/packages/slug/365584)\]
+
+---
+
+## ProfilerMarker
+
+Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
+
+```csharp
+using UnityEngine;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Некоторый код
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Некоторый код
+ using var _ = this.Marker().WithName("Calculate");
+ // Некоторый код
+ }
+ }
+}
+```
+
+
+Сгенерированный код
+
+
+```csharp
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_Marker_Line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_Marker_Line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+#if ENABLE_PROFILER
+ if (line is 13) return DoSomething1_Marker_Line_13.Auto();
+ if (line is 19) return DoSomething2_Marker_Line_19.Auto();
+ if (line is 22) return DoSomething2_Marker_Line_22.Auto();
+#endif
+ return default;
+ }
+}
+```
+
+
+
+### Result
+
+
+
+---
+
+## Serializable Type System
+
+Позволяет сериализовать ссылку на `System.Type` в Unity Inspector. Выбранный тип хранится как assembly-qualified name и разрешается лениво при первом обращении.
+
+### SerializableType
+
+Доступны два варианта:
+
+- **`SerializableType`** — хранит любой тип (базовый тип — `object`)
+- **`SerializableType`** — хранит тип, ограниченный `T` или его подклассами
+
+Оба поддерживают неявное преобразование в `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class Ability : MonoBehaviour
+{
+ public abstract void Activate();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ [SerializeField] private SerializableType _abilityType;
+
+ private void Start()
+ {
+ var ability = (Ability)gameObject.AddComponent(_abilityType.Type);
+ ability.Activate();
+ }
+}
+```
+
+
+### TypeSelectorAttribute
+
+Атрибут `PropertyAttribute`, доступный только в редакторе, ограничивающий всплывающее окно выбора типа конкретными базовыми типами. Применяется к полям `string`, хранящим assembly-qualified имена типов.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // базовый тип: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public TypeAllow Allow { get; set; } // по умолчанию: TypeAllow.None
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Свойство | Описание |
+|----------|----------|
+| `Allow` | Какие специальные категории типов (абстрактные классы, интерфейсы) включаются в список выбора в дополнение к обычным конкретным классам. По умолчанию: `TypeAllow.None` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class AbilityModifier
+{
+ public abstract void Apply();
+}
+
+public sealed class AbilitySelector : MonoBehaviour
+{
+ // Каждый элемент массива — отдельный picker, ограниченный AbilityModifier.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+}
+```
+
+> Полный сэмпл — `Ability` / `AbilitySelector` / `EnemyBase` и их наследники — поставляется в сэмпле `Types` (Package Manager → Aspid.FastTools → Samples).
+
+---
+
+### Type Selector Window
+
+В Inspector отображается кнопка, открывающая всплывающее окно с поиском, которое включает:
+
+- Иерархическую организацию по пространствам имён
+- Текстовый поиск с фильтрацией
+- Навигацию с клавиатуры (стрелки, Enter, Escape)
+- Историю навигации (кнопка «назад»)
+- Разрешение неоднозначности для типов с одинаковыми именами из разных сборок
+
+
+
+Это же окно доступно как публичный API — открывайте его из любого editor-кода (кастомных инспекторов, `EditorWindow`, пунктов меню), когда нужно вывести выбор типа за пределами стандартного потока `SerializableType` / `[TypeSelector]`.
+
+```csharp
+namespace Aspid.FastTools.Types.Editors
+{
+ public sealed class TypeSelectorWindow : EditorWindow
+ {
+ public static void Show(
+ Rect screenRect,
+ Type[] types = null,
+ string currentAqn = "",
+ TypeAllow allow = TypeAllow.None,
+ Action onSelected = null);
+ }
+}
+```
+
+| Параметр | Описание |
+|----------|----------|
+| `screenRect` | Прямоугольник в экранных координатах, к которому привязывается dropdown. |
+| `types` | Базовые типы, по которым фильтруются видимые элементы. В списке остаются только типы, совместимые со **всеми** записями. По умолчанию — `typeof(object)`. |
+| `currentAqn` | Assembly-qualified имя текущего выбранного типа: окно сразу откроется на его уровне иерархии. Передайте `null` или пустую строку, чтобы стартовать с корня. |
+| `allow` | Какие специальные категории (абстрактные классы, интерфейсы) включаются в список в дополнение к конкретным классам. По умолчанию: `TypeAllow.None`. |
+| `onSelected` | Callback с assembly-qualified именем выбранного типа или `null`, если пользователь выбрал ``. |
+
+### ComponentTypeSelector
+
+Сериализуемая структура, добавляющая в Inspector выпадающий список для смены типа объекта. Добавьте её как поле в базовый класс — при выборе подтипа редактор перезаписывает `m_Script` на `SerializedObject`, фактически превращая компонент или ScriptableObject в выбранный подтип.
+
+Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public abstract class EnemyBase : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _enemyType;
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ public abstract void Attack();
+}
+
+public sealed class FastEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log($"Fast enemy strikes! (speed: {_speed})");
+}
+
+public sealed class TankEnemy : EnemyBase
+{
+ [SerializeField] [Min(0)] private float _armor = 50f;
+
+ public override void Attack() =>
+ Debug.Log($"Tank attacks! (armor: {_armor})");
+}
+```
+
+
+
+---
+
+## Enum System
+
+Предоставляет сериализуемые отображения enum → значение, настраиваемые через Inspector.
+
+### EnumValues\
+
+Сериализуемая коллекция записей `EnumValue` с настраиваемым значением по умолчанию. Реализует `IEnumerable>`.
+
+| Член | Описание |
+|------|----------|
+| `TValue GetValue(Enum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
+| `bool Equals(Enum, Enum)` | Проверка равенства с поддержкой `[Flags]` |
+
+Поддерживает `[Flags]`-перечисления: `Equals` использует `HasFlag` и корректно обрабатывает члены со значением `0`.
+
+```csharp
+using System;
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+public enum DamageType { Physical, Fire, Ice, Poison }
+
+[Flags]
+public enum StatusEffect
+{
+ None = 0,
+ Burning = 1,
+ Frozen = 2,
+ Slowed = 4,
+ Stunned = 8,
+}
+
+public sealed class DamageDealer : MonoBehaviour
+{
+ [SerializeField] private EnumValues _damageMultipliers;
+ [SerializeField] private EnumValues _damageColors;
+
+ // Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins,
+ // so list composite entries BEFORE their constituent flags.
+ [SerializeField] private EnumValues _speedMultipliersByStatus;
+
+ [SerializeField] private DamageType _currentType;
+ [SerializeField] private StatusEffect _activeEffects;
+
+ private void DealDamage()
+ {
+ var multiplier = _damageMultipliers.GetValue(_currentType);
+ var color = _damageColors.GetValue(_currentType);
+ var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
+ // ...
+ }
+}
+```
+
+
+В Inspector выберите тип перечисления в заголовке `EnumValues`, затем назначьте значение для каждого члена перечисления. Нажмите правой кнопкой мыши по свойству, чтобы открыть контекстное меню с пунктом **Populate Missing Enum Members** — он добавит записи для всех отсутствующих членов перечисления, используя текущее Default Value как начальное значение.
+
+> Полный сэмпл — `DamageDealer` / `DamageType` / `StatusEffect` — поставляется в сэмпле `EnumValues` (Package Manager → Aspid.FastTools → Samples).
+
+---
+
+## ID System (Beta)
+
+> **Бета:** Система ID находится в бета-версии. Публичный API, структура генерируемого кода и редакторский UX могут измениться в будущих релизах.
+
+Сопоставляет имя, назначаемое в активе, со стабильным целочисленным ID. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на строковые поиски в рантайме.
+
+Единственный ScriptableObject `IdRegistry` сопоставляет строковые имена стабильным целочисленным ID и предоставляет полные `int ↔ string` поиски в рантайме.
+
+### Setup
+
+**1.** Объявите `partial struct`, реализующий `IId`. Генератор исходников автоматически добавит необходимые поля и свойство:
+
+```csharp
+using Aspid.FastTools.Ids;
+
+public partial struct EnemyId : IId { }
+```
+
+Сгенерированный код:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId; // editor-only поле, вырезается из player-сборок
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+Генератор сообщает `AFID001`, если у структуры отсутствует `partial`, и `AFID002`, если вы сами объявили `_id`, `Id` или `__stringId` (генерация пропускается — вы получаете явную ошибку с указанием на структуру вместо CS-ошибки внутри сгенерированного кода). Поддерживаются generic-структуры (`EnemyId`) и generic-контейнеры.
+
+**2.** Создайте ассет реестра и привяжите его к вашему типу структуры в Inspector:
+- `Assets → Create → Aspid → Id Registry`
+
+**3.** Используйте структуру как сериализуемое поле. В Inspector отображается выпадающий список зарегистрированных имён; окно селектора также позволяет создавать новые записи на лету:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+public class EnemySpawner : MonoBehaviour
+{
+ [SerializeField] private EnemyId _targetEnemy;
+
+ private void Spawn()
+ {
+ int id = _targetEnemy.Id; // стабильный integer, безопасен для switch / Dictionary
+ }
+}
+```
+
+
+
+### UniqueIdAttribute
+
+Помечает поле как требующее уникального значения среди всех ассетов объявляющего типа. Inspector показывает предупреждение, если два ассета используют одинаковый ID.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class UniqueIdAttribute : PropertyAttribute { }
+```
+
+
+
+### IdRegistry
+
+`ScriptableObject` из `Aspid.FastTools.Ids`, хранящий записи `(int, string)` и поддерживающий таблицы поиска доступными во рантайме. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
+
+| Член | Описание |
+|------|----------|
+| `bool TryGetId(string name, out int id)` | Возвращает `true` и найденный ID; иначе `false` |
+| `bool TryGetName(int id, out string name)` | Возвращает `true` и найденное имя; иначе `false` и `string.Empty` |
+| `bool Contains(int id)` | Зарегистрирован ли ID |
+| `bool Contains(string name)` | Зарегистрировано ли имя |
+| `int Count` | Количество записей |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Зарегистрированные ID / имена в порядке регистрации |
+| `IEnumerator> GetEnumerator()` | Итерация по парам `(id, name)` |
+
+Реестр наследуется напрямую от `ScriptableObject` и предоставляет генерик-аналог `IdRegistry` (с `T : struct, IId`), добавляющий типизированные перегрузки `Contains(T)` и `TryGetName(T, out string)`. Редактирование — добавление, переименование, удаление записей — выполняется через инспектор реестра и `RegistryEditorCore`, а не через публичный runtime API.
+
+
+
+---
+
+## SerializedProperty Extensions
+
+Цепочные расширения над `SerializedProperty` для синхронизации владеющего `SerializedObject`, записи типизированных значений и рефлексии над полем-источником.
+
+```csharp
+property
+ .Update()
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+Пакет покрывает:
+
+- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
+- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (и `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). К каждому идёт парный вариант `SetXxxAndApply`.
+- **Enum-сеттеры** — `SetEnumFlag` и `SetEnumIndex` (каждый + `AndApply`).
+- **Массивы** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (каждый + `AndApply`).
+- **Ссылки** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, а также `SetBoxed` (Unity 6+).
+- **Рефлексионные хелперы** — `GetPropertyType`, `GetMemberInfo`, `GetClassInstance` для разрешения C#-члена и runtime-экземпляра, стоящих за property.
+
+> Полный справочник по методам: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
+
+---
+
+## IMGUI Layout Scopes
+
+Три `ref struct`-области — `VerticalScope`, `HorizontalScope`, `ScrollViewScope` — оборачивают `EditorGUILayout.Begin*` / `End*`. Каждая предоставляет свойство `Rect` и вызывает соответствующий метод `End*` в `Dispose`:
+
+```csharp
+using (VerticalScope.Begin())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (HorizontalScope.Begin())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (ScrollViewScope.Begin(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+Получить rect области через перегрузку с `out`-параметром:
+
+```csharp
+using (VerticalScope.Begin(out var rect, GUI.skin.box))
+{
+ EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
+ EditorGUILayout.LabelField("Boxed content");
+}
+```
+
+Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view и т.д.).
+
+---
+
+## VisualElement Extensions
+
+Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
+
+> Полный справочник по методам: [VisualElementExtensions.md](VisualElementExtensions.md)
+
+### Example
+
+Реактивный редактор для `ScriptableObject` `AbilityConfig` — заголовок и статус-пилла в шапке, тело из `PropertyField`, и Warning `HelpBox`, который переключается в зависимости от `ManaCost`.
+
+```csharp
+[CustomEditor(typeof(AbilityConfig))]
+internal sealed class AbilityConfigEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ var config = (AbilityConfig)target;
+
+ var badge = new Label()
+ .SetFontSize(10).SetUnityFontStyleAndWeight(FontStyle.Bold)
+ .SetPaddingX(10).SetPaddingY(3)
+ .SetBorderRadius(10).SetBorderWidth(1);
+
+ var helpBox = new HelpBox(
+ "This ability costs no mana — is that intentional?",
+ HelpBoxMessageType.Warning)
+ .SetMarginTop(8).SetBorderRadius(6);
+
+ var manaField = new PropertyField(serializedObject.FindProperty("_manaCost"))
+ .AddValueChanged(_ => Refresh());
+
+ Refresh();
+ return new VisualElement()
+ .SetBorderRadius(10).SetBorderWidth(1)
+ .AddChild(new VisualElement()
+ .SetFlexDirection(FlexDirection.Row).SetAlignItems(Align.Center)
+ .SetPaddingX(14).SetPaddingY(12)
+ .AddChild(new Label(target.GetScriptName())
+ .SetFlexGrow(1).SetFontSize(15)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(badge))
+ .AddChild(new VisualElement()
+ .SetPaddingX(14).SetPaddingY(12)
+ .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
+ .AddChild(manaField)
+ .AddChild(helpBox));
+
+ void Refresh()
+ {
+ var isFree = config.ManaCost is 0;
+ badge.SetText(isFree ? "FREE" : $"{config.ManaCost} MP");
+ helpBox.SetDisplay(isFree ? DisplayStyle.Flex : DisplayStyle.None);
+ }
+ }
+}
+```
+
+> Полный сэмпл — `AbilityConfig.cs`, полированный `AbilityConfigEditor.cs` (свои цвета, подзаголовок и divider — то, что на скриншоте ниже) и два `.asset`-примера — поставляется в сэмпле `VisualElements` (Package Manager → Aspid.FastTools → Samples).
+
+### Result
+
+
+
+---
+
+## Editor Helper Extensions
+
+Утилитарные методы для получения отображаемых имён объектов Unity в пользовательских редакторах.
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Возвращает отображаемое имя объекта Unity:
+- Если тип имеет `[AddComponentMenu]`, возвращает `ObjectNames.GetInspectorTitle(obj)`
+- В противном случае возвращает `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Возвращает отображаемое имя с числовым суффиксом, если на одном GameObject присутствует несколько компонентов одного типа. Например, если прикреплены два компонента `AudioSource`, второй вернёт `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — или "Custom Name", если присутствует [AddComponentMenu("Custom Name")]
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" при наличии второго компонента того же типа
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md.meta
new file mode 100644
index 00000000..a1ba8c40
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9b6c3feaf2078463b98fab80105c4ed8
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md
new file mode 100644
index 00000000..ba79455b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md
@@ -0,0 +1,124 @@
+# SerializedProperty Extensions — full reference
+
+Цепочные методы расширения над `SerializedProperty` для синхронизации владеющего `SerializedObject`, установки значений и рефлексии над полем-источником.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Все расширения обобщены по `T : SerializedProperty` и возвращают тот же экземпляр, поэтому вызовы можно свободно объединять в цепочки.
+
+## Update / Apply
+
+Тонкие обёртки над одноимёнными методами `SerializedObject` у `property.serializedObject`.
+
+```csharp
+property
+ .Update()
+ .SetInt(42)
+ .ApplyModifiedProperties();
+```
+
+| Метод | Описание |
+|-------|----------|
+| `Update()` | Вызывает `serializedObject.Update()` |
+| `UpdateIfRequiredOrScript()` | Вызывает `serializedObject.UpdateIfRequiredOrScript()` |
+| `ApplyModifiedProperties()` | Вызывает `serializedObject.ApplyModifiedProperties()` |
+
+## SetValue / SetXxx — typed setters
+
+Для каждого поддерживаемого типа существуют четыре варианта:
+
+| Вариант | Поведение |
+|---------|-----------|
+| `SetValue(value)` | Обобщённый диспетчер — выбирает нужный типизированный сеттер по runtime-типу значения, возвращает `property` |
+| `SetValueAndApply(value)` | `SetValue(value)` плюс `ApplyModifiedProperties()` |
+| `SetXxx(value)` | Типизированный сеттер (например, `SetInt`), пишущий в соответствующее поле `SerializedProperty.xxxValue` |
+| `SetXxxAndApply(value)` | `SetXxx(value)` плюс `ApplyModifiedProperties()` |
+
+### Supported types
+
+| Семейство методов | Unity-тип | Примечания |
+|-------------------|-----------|------------|
+| `SetInt` | `int` | |
+| `SetUint` | `uint` | |
+| `SetLong` | `long` | |
+| `SetUlong` | `ulong` | |
+| `SetFloat` | `float` | |
+| `SetDouble` | `double` | |
+| `SetBool` | `bool` | |
+| `SetString` | `string` | |
+| `SetColor` | `Color` | |
+| `SetGradient` | `Gradient` | |
+| `SetHash128` | `Hash128` | |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` | |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` | |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` | |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` | |
+| `SetVector4` | `Vector4` | |
+| `SetQuaternion` | `Quaternion` | |
+| `SetAnimationCurve` | `AnimationCurve` | |
+| `SetEntityId` | `Unity.Entities.EntityId` | Unity 6.2+. Apply-вариант называется `SetEntityIdApply` *(имя метода сохраняет опечатку из исходника: пропущено `And`)* |
+
+### Enum setters
+
+Значения enum не идут через `SetValue` — используйте явную пару ниже в зависимости от того, является ли поле `[Flags]`-перечислением:
+
+| Метод | Описание |
+|-------|----------|
+| `SetEnumFlag(int)` / `SetEnumFlagAndApply(int)` | Пишет в `enumValueFlag` |
+| `SetEnumIndex(int)` / `SetEnumIndexAndApply(int)` | Пишет в `enumValueIndex` |
+
+### Example
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// Эквивалентные формы
+property.SetValue(10).ApplyModifiedProperties();
+property.SetValueAndApply(10);
+property.SetInt(10).ApplyModifiedProperties();
+property.SetIntAndApply(10);
+
+// Цепочка из нескольких сеттеров
+property
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+## Array operations
+
+| Метод | Описание |
+|-------|----------|
+| `SetArraySize(int)` / `SetArraySizeAndApply(int)` | Устанавливает `property.arraySize` |
+| `AddArraySize(int = 1)` / `AddArraySizeAndApply(int = 1)` | Увеличивает `arraySize` на указанное количество (по умолчанию `1`) |
+| `RemoveArraySize(int = 1)` / `RemoveArraySizeAndApply(int = 1)` | Уменьшает `arraySize` на указанное количество (по умолчанию `1`) |
+
+## Reference setters
+
+| Метод | Описание | Примечания |
+|-------|----------|------------|
+| `SetManagedReference(object)` / `SetManagedReferenceAndApply(object)` | Пишет в `managedReferenceValue` (поле должно быть помечено `[SerializeReference]`) | |
+| `SetObjectReference(Object)` / `SetObjectReferenceAndApply(Object)` | Пишет в `objectReferenceValue` | |
+| `SetExposedReference(Object)` / `SetExposedReferenceAndApply(Object)` | Пишет в `exposedReferenceValue` | |
+| `SetBoxed(object)` / `SetBoxedAndApply(object)` | Пишет в `boxedValue` | Unity 6+ |
+
+## Reflection helpers
+
+Для drawer-/inspector-кода, которому нужно получить runtime-тип или экземпляр, стоящий за property:
+
+| Метод | Возвращает | Описание |
+|-------|------------|----------|
+| `GetPropertyType()` | `Type` или `null` | Возвращает `FieldType` / `PropertyType` C#-члена, стоящего за property. `null`, если член не удаётся разрешить. |
+| `GetMemberInfo()` | `MemberInfo` или `null` | Находит field/property на классе-владельце, имя которого совпадает с `SerializedProperty.name`. Обходит базовые классы через `TypeExtensions.GetMembersInfosIncludingBaseClasses`. |
+| `GetClassInstance()` | `object` | Идёт по `propertyPath` от корневого `targetObject` и возвращает runtime-экземпляр, который непосредственно содержит это property. Поддерживает вложенные объекты, массивы и `List`-поля. |
+
+```csharp
+public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
+{
+ var declaringType = property.GetPropertyType();
+ var owner = property.GetClassInstance();
+ // …
+}
+```
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md.meta
new file mode 100644
index 00000000..76e5ed29
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/SerializedPropertyExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bf2b6a386bf354961a0a7bb99ff06b10
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md
new file mode 100644
index 00000000..6e83902c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md
@@ -0,0 +1,600 @@
+# VisualElement Extensions — full reference
+
+Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
+
+```csharp
+using Aspid.FastTools.UIElements; // runtime-расширения
+using Aspid.FastTools.UIElements.Editors; // editor-only расширения (например, AddOpenScriptCommand)
+```
+
+## Core element operations
+
+```csharp
+element
+ .SetName("MyElement")
+ .SetVisible(true)
+ .SetTooltip("Текст подсказки")
+ .AddChild(new Label("Hello"))
+ .AddChildren(child1, child2, child3);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetName(string)` | Устанавливает `element.name` |
+| `SetVisible(bool)` | Устанавливает `element.visible` |
+| `SetTooltip(string)` | Устанавливает `element.tooltip` |
+| `SetUserData(object)` | Устанавливает `element.userData` |
+| `SetEnabledSelf(bool)` | Устанавливает `element.enabledSelf` |
+| `SetPickingMode(PickingMode)` | Устанавливает `element.pickingMode` |
+| `SetUsageHints(UsageHints)` | Устанавливает `element.usageHints` |
+| `SetViewDataKey(string)` | Устанавливает `element.viewDataKey` |
+| `SetLanguageDirection(LanguageDirection)` | Устанавливает `element.languageDirection` |
+| `SetDisablePlayModeTint(bool)` | Устанавливает `element.disablePlayModeTint` |
+| `SetDataSource(object)` | Устанавливает `element.dataSource` |
+| `SetDataSourceType(Type)` | Устанавливает `element.dataSourceType` |
+| `SetDataSourcePath(PropertyPath)` | Устанавливает `element.dataSourcePath` |
+| `AddChild(VisualElement)` | Добавляет дочерний элемент, возвращает родителя |
+| `AddChildren(params VisualElement[])` | Добавляет несколько дочерних элементов |
+| `AddChildren(IEnumerable)` | Добавляет из последовательности |
+| `AddChildren(List)` | Добавляет из списка |
+| `AddChildren(Span)` | Добавляет из span |
+| `AddChildren(ReadOnlySpan)` | Добавляет из read-only span |
+| `InsertChild(int, VisualElement)` | Вставляет дочерний элемент по указанному индексу |
+| `InsertChildren(int, params VisualElement[])` | Вставляет несколько дочерних элементов начиная с индекса |
+| `InsertChildren(int, IEnumerable)` | Вставляет из последовательности |
+| `InsertChildren(int, List)` | Вставляет из списка |
+| `InsertChildren(int, Span)` | Вставляет из span |
+| `InsertChildren(int, ReadOnlySpan)` | Вставляет из read-only span |
+
+> `RegisterCallbackOnce` и `RegisterCallbackOnce` доступны на всех версиях Unity (пакет содержит polyfill для версий до 2023.1).
+
+## Focusable
+
+| Метод | Описание |
+|-------|----------|
+| `SetFocus()` | Устанавливает фокус на элемент |
+| `SetBlur()` | Снимает фокус с элемента |
+| `IsFocus()` | Возвращает, находится ли элемент в фокусе |
+| `SetTabIndex(int)` | Устанавливает `element.tabIndex` |
+| `SetFocusable(bool)` | Устанавливает `element.focusable` |
+| `SetDelegatesFocus(bool)` | Устанавливает `element.delegatesFocus` |
+
+## USS & class operations
+
+| Метод | Описание |
+|-------|----------|
+| `AddClass(string)` | Добавляет USS-класс |
+| `RemoveClass(string)` | Удаляет USS-класс |
+| `ClearClasses()` | Удаляет все USS-классы |
+| `ToggleInClass(string)` | Переключает USS-класс вкл/выкл |
+| `EnableInClass(string, bool)` | Добавляет или удаляет USS-класс по условию |
+| `AddStyleSheets(StyleSheet)` | Добавляет `StyleSheet` |
+| `RemoveStyleSheets(StyleSheet)` | Удаляет `StyleSheet` |
+| `AddStyleSheetsFromResource(string)` | Добавляет таблицу стилей через `Resources.Load` |
+| `RemoveStyleSheetsFromResource(string)` | Удаляет таблицу стилей, загруженную через `Resources.Load` |
+
+## Style extensions — by category
+
+Все методы стилей также доступны напрямую на `IStyle` (те же имена методов, работают с объектом стиля).
+
+### Layout
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetFlexBasis(StyleLength)` | `flexBasis` |
+| `SetFlexGrow(StyleFloat)` | `flexGrow` |
+| `SetFlexShrink(StyleFloat)` | `flexShrink` |
+| `SetFlexWrap(StyleEnum)` | `flexWrap` |
+| `SetFlexDirection(FlexDirection)` | `flexDirection` |
+| `SetAlignSelf(StyleEnum)` | `alignSelf` |
+| `SetAlignItems(StyleEnum)` | `alignItems` |
+| `SetAlignContent(StyleEnum)` | `alignContent` |
+| `SetJustifyContent(StyleEnum)` | `justifyContent` |
+| `SetPosition(StyleEnum)` | `position` |
+
+### Size
+
+| Метод | Описание |
+|-------|----------|
+| `SetSize(StyleLength)` | Устанавливает ширину и высоту одновременно |
+| `SetSize(width?, height?)` | Устанавливает ширину и/или высоту независимо |
+| `SetMinSize(StyleLength)` | Устанавливает minWidth и minHeight одновременно |
+| `SetMinSize(width?, height?)` | |
+| `SetMaxSize(StyleLength)` | Устанавливает maxWidth и maxHeight одновременно |
+| `SetMaxSize(width?, height?)` | |
+| `SetWidth(StyleLength)` | `width` |
+| `SetMinWidth(StyleLength)` | `minWidth` |
+| `SetMaxWidth(StyleLength)` | `maxWidth` |
+| `SetHeight(StyleLength)` | `height` |
+| `SetMinHeight(StyleLength)` | `minHeight` |
+| `SetMaxHeight(StyleLength)` | `maxHeight` |
+
+### Spacing
+
+Все методы отступов имеют перегрузку с единым значением, перегрузку по сторонам (`top`, `right`, `bottom`, `left`), сеттеры по одной стороне и сеттеры по парам осей X/Y.
+
+| Метод | Свойства стиля |
+|-------|----------------|
+| `SetMargin(…)` / `SetPadding(…)` / `SetDistance(…)` | `Top/Right/Bottom/Left` (общее значение или per-side) |
+| `SetMarginX/Y` · `SetPaddingX/Y` · `SetDistanceX/Y` | Устанавливает горизонтальную (X = `Left`+`Right`) или вертикальную (Y = `Top`+`Bottom`) пару |
+| `SetMarginTop/Right/Bottom/Left` | Margin одной стороны |
+| `SetPaddingTop/Right/Bottom/Left` | Padding одной стороны |
+| `SetDistanceTop/Right/Bottom/Left` *(через `SetTop` / `SetRight` / `SetBottom` / `SetLeft`)* | Смещение одной стороны для абсолютного позиционирования (свойства `top` / `right` / `bottom` / `left`) |
+
+> `SetDistance` — обёртка для четырёх свойств `top`/`right`/`bottom`/`left`, используемых при абсолютном позиционировании. `SetTop`, `SetRight`, `SetBottom`, `SetLeft` — это прямые алиасы для одного свойства.
+
+### Font
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetUnityFont(StyleFont)` | `unityFont` |
+| `SetFontSize(StyleLength)` | `fontSize` |
+| `SetUnityFontDefinition(StyleFontDefinition)` | `unityFontDefinition` |
+| `SetUnityFontStyleAndWeight(StyleEnum)` | `unityFontStyleAndWeight` |
+
+### Font style presets
+
+Удобные методы для переключения bold / italic без перезаписи другого флага:
+
+| Метод | Описание |
+|-------|----------|
+| `SetNormalUnityFontStyleAndWeight()` | Сбрасывает в `FontStyle.Normal` |
+| `AddBoldUnityFontStyleAndWeight()` | Добавляет bold, сохраняя italic |
+| `RemoveBoldUnityFontStyleAndWeight()` | Убирает bold, сохраняя italic |
+| `AddItalicUnityFontStyleAndWeight()` | Добавляет italic, сохраняя bold |
+| `RemoveItalicUnityFontStyleAndWeight()` | Убирает italic, сохраняя bold |
+
+### Text
+
+| Метод | Свойство стиля | Примечания |
+|-------|---------------|------------|
+| `SetWorldSpacing(StyleLength)` | `wordSpacing` | |
+| `SetLetterSpacing(StyleLength)` | `letterSpacing` | |
+| `SetUnityTextAlign(TextAnchor)` | `unityTextAlign` | |
+| `SetTextShadow(StyleTextShadow)` | `textShadow` | |
+| `SetUnityTextOutlineColor(StyleColor)` | `unityTextOutlineColor` | |
+| `SetUnityTextOutlineWidth(StyleFloat)` | `unityTextOutlineWidth` | |
+| `SetUnityParagraphSpacing(StyleLength)` | `unityParagraphSpacing` | |
+| `SetTextOverflow(StyleEnum)` | `textOverflow` | |
+| `SetUnityTextOverflowPosition(TextOverflowPosition)` | `unityTextOverflowPosition` | |
+| `SetUnityTextGenerator(TextGeneratorType)` | `unityTextGenerator` | Unity 6+ |
+| `SetUnityEditorTextRenderingMode(EditorTextRenderingMode)` | `unityEditorTextRenderingMode` | Unity 6+ |
+| `SetUnityTextAutoSize(StyleTextAutoSize)` | `unityTextAutoSize` | Unity 6.2+ |
+| `SetWhiteSpace(StyleEnum)` | `whiteSpace` | |
+
+### Color & Opacity
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetColor(StyleColor)` | `color` |
+| `SetColor(string)` | `color`, разобранный из HTML-строки (`"#RRGGBB"` или именованный цвет) |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+### Border
+
+| Метод | Описание |
+|-------|----------|
+| `SetBorderColor(StyleColor)` | Все стороны |
+| `SetBorderColor(top?, right?, bottom?, left?)` | По стороне |
+| `SetBorderColorX(StyleColor)` · `SetBorderColorY(StyleColor)` | Горизонтальная (left + right) или вертикальная (top + bottom) пара |
+| `SetBorderColorTop/Right/Bottom/Left(StyleColor)` | Одна сторона |
+| `SetBorderRadius(StyleLength)` | Все углы |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | По углу |
+| `SetBorderRadiusTop(StyleLength)` · `SetBorderRadiusBottom(StyleLength)` | Пара верхних или нижних углов |
+| `SetBorderRadiusTopLeft/TopRight/BottomLeft/BottomRight(StyleLength)` | Один угол |
+| `SetBorderWidth(StyleFloat)` | Все стороны |
+| `SetBorderWidth(top?, right?, bottom?, left?)` | По стороне |
+| `SetBorderWidthX(StyleFloat)` · `SetBorderWidthY(StyleFloat)` | Горизонтальная или вертикальная пара |
+| `SetBorderWidthTop/Right/Bottom/Left(StyleFloat)` | Одна сторона |
+
+### Background
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundColor(string)` | `backgroundColor`, разобранный из HTML-строки (`"#RRGGBB"` или именованный цвет) |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundImageFromResource(string)` | Загружает `Texture2D` через `Resources.Load` и присваивает его в `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | X и Y одновременно |
+| `SetBackgroundPosition(x?, y?)` | Независимо |
+| `SetBackgroundPositionX(StyleBackgroundPosition)` | `backgroundPositionX` |
+| `SetBackgroundPositionY(StyleBackgroundPosition)` | `backgroundPositionY` |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+### Transform
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+### Aspect, Filter & Material
+
+Доступно начиная с Unity 6000.3+.
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetAspectRation(StyleRatio)` | `aspectRatio` *(имя метода сохраняет опечатку из исходника)* |
+| `SetFilter(StyleList)` | `filter` |
+| `SetUnityMaterial(StyleMaterialDefinition)` | `unityMaterial` |
+
+### Transition
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetTransitionDelay(StyleList)` | `transitionDelay` |
+| `SetTransitionDuration(StyleList)` | `transitionDuration` |
+| `SetTransitionProperty(StyleList)` | `transitionProperty` |
+| `SetTransitionTimingFunction(StyleList)` | `transitionTimingFunction` |
+
+### Overflow & Visibility
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetOverflow(StyleEnum)` | `overflow` |
+| `SetUnityOverflowClipBox(StyleEnum)` | `unityOverflowClipBox` |
+| `SetVisibility(StyleEnum)` | `visibility` |
+| `SetDisplay(DisplayStyle)` | `display` |
+
+### Unity Slice
+
+| Метод | Описание |
+|-------|----------|
+| `SetUnitySlice(StyleInt)` | Все стороны |
+| `SetUnitySlice(top?, right?, bottom?, left?)` | По стороне |
+| `SetUnitySliceX(StyleInt)` · `SetUnitySliceY(StyleInt)` | Горизонтальная (left + right) или вертикальная (top + bottom) пара |
+| `SetUnitySliceTop/Right/Bottom/Left(StyleInt)` | Одна сторона |
+| `SetUnitySliceScale(StyleFloat)` | `unitySliceScale` |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+### Cursor
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+## Specialized element extensions
+
+### TextElement
+
+```csharp
+label
+ .SetText("Hello World")
+ .SetEnableRichText(true)
+ .SetParseEscapeSequences(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Устанавливает отображаемый текст |
+| `SetEnableRichText(bool)` | Включает разбор тегов rich-text |
+| `SetEmojiFallbackSupport(bool)` | Включает emoji-fallback при рендеринге |
+| `SetParseEscapeSequences(bool)` | Обрабатывать ли escape-последовательности (например, `\n`) |
+| `SetDisplayTooltipWhenElided(bool)` | Показывать обрезанный текст в подсказке при наведении |
+
+### ITextEdition (TextField, IntegerField, …)
+
+```csharp
+textField
+ .SetPlaceholder("Поиск…")
+ .SetMaxLength(64)
+ .SetIsDelayed(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetMaxLength(int)` | Максимальное число символов |
+| `SetMaskChar(char)` | Символ для маскировки пароля |
+| `SetIsDelayed(bool)` | Откладывает изменение значения до потери фокуса / Enter |
+| `SetIsReadOnly(bool)` | Запрещает редактирование |
+| `SetIsPassword(bool)` | Включает password-режим (использует mask char) |
+| `SetPlaceholder(string)` | Текст-плейсхолдер для пустого поля |
+| `SetAutoCorrection(bool)` | Включает автокоррекцию (mobile) |
+| `SetHideMobileInput(bool)` | Скрывает мобильный soft input |
+| `SetHideSoftKeyboard(bool)` | Скрывает экранную клавиатуру |
+| `SetHidePlaceholderOnFocus(bool)` | Убирает плейсхолдер при фокусе |
+| `SetKeyboardType(TouchScreenKeyboardType)` | Тип touch-screen клавиатуры |
+
+### ITextSelection
+
+```csharp
+textField
+ .SetIsSelectable(true)
+ .SetSelectAllOnFocus(true)
+ .AddOnCursorIndexChange(() => Debug.Log(textField.cursorIndex));
+```
+
+| Метод | Описание |
+|-------|----------|
+| `AddOnCursorIndexChange(Action)` / `RemoveOnCursorIndexChange(Action)` | Подписка на изменение позиции курсора |
+| `AddOnSelectIndexChange(Action)` / `RemoveOnSelectIndexChange(Action)` | Подписка на изменение якоря выделения |
+| `SetCursorIndex(int)` | Текущая позиция курсора |
+| `SetSelectIndex(int)` | Текущий якорь выделения |
+| `SetIsSelectable(bool)` | Можно ли выделять текст |
+| `SetSelectAllOnFocus(bool)` | Выделять весь текст при фокусе |
+| `SetSelectAllOnMouseUp(bool)` | Выделять весь текст при отпускании мыши |
+| `SetDoubleClickSelectsWord(bool)` | Двойной клик выделяет слово |
+| `SetTripleClickSelectsLine(bool)` | Тройной клик выделяет строку |
+
+### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+### BaseBoolField (Toggle)
+
+```csharp
+toggle
+ .SetLabel("Включено")
+ .SetText("Показать расширенные настройки")
+ .SetToggleOnLabelClick(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Устанавливает текст рядом с чекбоксом |
+| `SetLabel(string)` | Устанавливает label поля |
+| `SetToggleOnLabelClick(bool)` | Переключать ли значение по клику на label |
+
+### INotifyValueChanged\
+
+```csharp
+field.SetValue(42, notify: false); // устанавливает значение без генерации ChangeEvent
+field.AddValueChanged(evt => Debug.Log(evt.newValue));
+field.RemoveValueChanged(myCallback);
+```
+
+Типизированные перегрузки доступны для `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `decimal`, `char`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `GUID`, `Quaternion`, `Matrix4x4`, `Gradient`, `AnimationCurve`, `Delegate`, `Enum`, `Object`, `object`, плюс обобщённый fallback `SetValue`.
+
+> При установленном пакете `com.unity.mathematics` автоматически выставляется define `ASPID_FASTTOOLS_UNITY_MATHEMATICS_INTEGRATION` и добавляются перегрузки `SetValue` / `AddValueChanged` / `RemoveValueChanged` для `int2/3/4` (и `intMxN`), `float2/3/4` (и `floatMxN`), `bool2/3/4` (и `boolMxN`), а также `quaternion`.
+
+### IMixedValueSupport
+
+```csharp
+field.SetShowMixedValue(true); // показывает индикатор смешанного значения
+```
+
+### Button
+
+```csharp
+button
+ .AddClicked(() => Debug.Log("Clicked"))
+ .SetClickable(new Clickable(() => { }))
+ .SetIconImage(myBackground);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `AddClicked(Action)` | Подписка на `Button.clicked` |
+| `RemoveClicked(Action)` | Отписка от `Button.clicked` |
+| `SetClickable(Clickable)` | Устанавливает `Button.clickable` |
+| `SetIconImage(Background)` | Устанавливает `Button.iconImage` |
+
+### Slider / BaseSlider\
+
+```csharp
+slider
+ .SetLowValue(0f)
+ .SetHighValue(100f)
+ .SetShowInputField(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetLowValue(TValue)` | Устанавливает минимальное значение слайдера |
+| `SetHighValue(TValue)` | Устанавливает максимальное значение слайдера |
+| `SetFill(bool)` | Заполнение трека до текущего значения |
+| `SetInverted(bool)` | Инвертирует направление слайдера |
+| `SetPageSize(float)` | Шаг изменения значения при постраничной навигации |
+| `SetShowInputField(bool)` | Показывает числовое поле ввода рядом со слайдером |
+| `SetDirection(SliderDirection)` | Устанавливает ориентацию слайдера |
+
+### ProgressBar
+
+```csharp
+progressBar.SetTitle("Загрузка...").SetLowValue(0f).SetHighValue(100f);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetTitle(string)` | Устанавливает заголовок, отображаемый в центре |
+| `SetLowValue(float)` | Устанавливает минимальное значение |
+| `SetHighValue(float)` | Устанавливает максимальное значение |
+
+### HelpBox
+
+```csharp
+helpBox
+ .SetText("Что-то пошло не так")
+ .SetMessageType(HelpBoxMessageType.Warning);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Текст сообщения help-box |
+| `SetMessageType(HelpBoxMessageType)` | Иконка / уровень (`None` / `Info` / `Warning` / `Error`) |
+
+### Foldout
+
+```csharp
+foldout
+ .SetText("Section Title")
+ .SetToggleOnLabelClick(true)
+ .SetValue(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Заголовок foldout |
+| `SetToggleOnLabelClick(bool)` | Переключать ли раскрытие по клику на заголовок |
+
+### Image
+
+```csharp
+image
+ .SetImage(myTexture)
+ .SetTintColor(Color.white)
+ .SetScaleMode(ScaleMode.ScaleToFit);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetImage(Texture)` | Устанавливает `Image.image` |
+| `SetImageFromResource(string)` | Загружает текстуру через `Resources.Load` |
+| `SetSprite(Sprite)` | Устанавливает `Image.sprite` |
+| `SetSpriteFromResource(string)` | Загружает sprite через `Resources.Load` |
+| `SetVectorImage(VectorImage)` | Устанавливает `Image.vectorImage` |
+| `SetVectorImageFromResource(string)` | Загружает vector image через `Resources.Load` |
+| `SetUv(Rect)` | Устанавливает UV-rect |
+| `SetSourceRect(Rect)` | Устанавливает source rect |
+| `SetTintColor(Color)` | Цветовой tint изображения |
+| `SetScaleMode(ScaleMode)` | Режим масштабирования |
+
+### IMGUIContainer
+
+```csharp
+container
+ .SetOnGUIHandler(() => GUILayout.Label("IMGUI"))
+ .SetCullingEnabled(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetOnGUIHandler(Action)` | Заменяет коллбэк `onGUIHandler` |
+| `AddOnGUIHandler(Action)` | Подписка на `onGUIHandler` |
+| `RemoveOnGUIHandler(Action)` | Отписка от `onGUIHandler` |
+| `SetCullingEnabled(bool)` | Пропускает `onGUIHandler`, когда элемент за пределами экрана |
+| `SetContextType(ContextType)` | Устанавливает тип контекста IMGUI |
+
+### Collection views (ListView, TreeView, MultiColumn variants)
+
+Общие методы распределены по нескольким специализированным расширениям:
+
+- `BaseVerticalCollectionViewExtensions` — применяется ко **всем** collection-views (ListView, TreeView, MultiColumn-варианты).
+- `BaseListViewExtensions` — применяется к ListView и MultiColumnListView.
+- `BaseTreeViewExtensions` — применяется к TreeView и MultiColumnTreeView.
+- `ListViewExtensions` / `TreeViewExtensions` — фабрики `MakeItem`/`BindItem`/`UnbindItem`/`DestroyItem` для своего вью.
+- `MultiColumnListViewExtensions` / `MultiColumnTreeViewExtensions` — хелперы для multi-column-вариантов.
+
+```csharp
+listView
+ .SetItemsSource(items)
+ .SetMakeItem(() => new Label())
+ .SetBindItem((el, i) => ((Label)el).SetText(items[i]))
+ .SetSelectionType(SelectionType.Single)
+ .AddSelectionChanged(selected => Debug.Log(selected));
+```
+
+#### Source, layout and behavior — `BaseVerticalCollectionView`
+
+| Метод | Описание | Примечания |
+|-------|----------|------------|
+| `SetItemsSource(IList)` | Источник данных | |
+| `SetReorderable(bool)` | Включает drag-reorder | |
+| `SetSelectedIndex(int)` | Выбирает элемент по индексу | |
+| `SetSelectionType(SelectionType)` | None / Single / Multiple | |
+| `SetFixedItemHeight(float)` | Фиксированная высота элемента (для виртуализации `FixedHeight`) | |
+| `SetVirtualizationMethod(CollectionVirtualizationMethod)` | `FixedHeight` или `DynamicHeight` | |
+| `SetHorizontalScrollingEnabled(bool)` | Включает горизонтальную прокрутку | |
+| `SetShowAlternatingRowBackgrounds(AlternatingRowBackground)` | Режим зебра-полос | |
+| `SetMakeFooter(Func)` · `AddMakeFooter` · `RemoveMakeFooter` | Фабрика подвала | Unity 6+ |
+| `SetMakeHeader(Func)` · `AddMakeHeader` · `RemoveMakeHeader` | Фабрика заголовка | Unity 6+ |
+| `SetMakeNoneElement(Func)` · `AddMakeNoneElement` · `RemoveMakeNoneElement` | Фабрика empty-state | Unity 6+ |
+
+#### Events — `BaseVerticalCollectionView`
+
+| Метод | Описание |
+|-------|----------|
+| `AddItemsChosen(Action>)` / `RemoveItemsChosen` | Подтверждение элементов (двойной клик / Enter) |
+| `AddSelectionChanged(Action>)` / `RemoveSelectionChanged` | Изменение выделения (объекты) |
+| `AddSelectedIndicesChanged(Action>)` / `RemoveSelectedIndicesChanged` | Изменение выделения (индексы) |
+| `AddItemIndexChanged(Action)` / `RemoveItemIndexChanged` | Перемещение элемента (drag-reorder) |
+| `AddItemsSourceChanged(Action)` / `RemoveItemsSourceChanged` | Смена ссылки `itemsSource` |
+| `AddCanStartDrag(Func)` / `RemoveCanStartDrag` | Кастомный gating старта drag |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Подготовка drag-and-drop |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Визуальный режим drag-and-drop |
+| `AddHandleDrop(Func)` / `RemoveHandleDrop` | Обработка drop |
+
+#### `BaseListView`-specific
+
+| Метод | Описание |
+|-------|----------|
+| `SetAllowAdd(bool)` · `SetAllowRemove(bool)` | Включают встроенные кнопки add/remove |
+| `SetHeaderTitle(string)` | Заголовок при включённом foldout-header |
+| `SetShowFoldoutHeader(bool)` | Оборачивает список в `Foldout` |
+| `SetShowAddRemoveFooter(bool)` | Показывает footer с add/remove |
+| `SetShowBoundCollectionSize(bool)` | Поле размера коллекции |
+| `SetReorderMode(ListViewReorderMode)` | `Simple` или `Animated` |
+| `SetBindingSourceSelectionMode(BindingSourceSelectionMode)` | Auto-assign / manual |
+| `SetOnAdd(Action)` · `AddOnAdd` · `RemoveOnAdd` | Кастомный коллбэк add-кнопки |
+| `SetOnRemove(Action)` · `AddOnRemove` · `RemoveOnRemove` | Кастомный коллбэк remove-кнопки |
+| `SetOverridingAddButtonBehavior(Action)` · `AddOverridingAddButtonBehavior` · `RemoveOverridingAddButtonBehavior` | Подменяет дефолтное поведение add |
+| `AddItemsAdded(Action>)` / `RemoveItemsAdded` | Добавление элементов по индексам |
+| `AddItemsRemoved(Action>)` / `RemoveItemsRemoved` | Удаление элементов по индексам |
+
+#### `BaseTreeView`-specific
+
+| Метод | Описание |
+|-------|----------|
+| `SetAutoExpand(bool)` | Авто-разворачивание новых узлов |
+| `AddItemExpandedChanged(Action)` / `RemoveItemExpandedChanged` | Подписка на изменение раскрытия |
+
+#### `ListView` / `TreeView` item factories
+
+Эти методы дублируются в `ListViewExtensions` и `TreeViewExtensions` (каждое работает со своим типом view).
+
+| Метод | Описание |
+|-------|----------|
+| `SetMakeItem(Func)` · `AddMakeItem` · `RemoveMakeItem` | Фабрика элементов |
+| `SetBindItem(Action)` · `AddBindItem` · `RemoveBindItem` | Привязка элемента |
+| `SetUnbindItem(Action)` · `AddUnbindItem` · `RemoveUnbindItem` | Отвязка элемента |
+| `SetDestroyItem(Action)` · `AddDestroyItem` · `RemoveDestroyItem` | Уничтожение элемента |
+| `SetItemTemplate(VisualTreeAsset)` | UXML-шаблон, по которому строятся элементы |
+
+#### `MultiColumnListView` / `MultiColumnTreeView`
+
+| Метод | Описание |
+|-------|----------|
+| `SetSortingMode(ColumnSortingMode)` | Встроенный режим сортировки заголовка колонки |
+
+## Editor commands (editor-only)
+
+```csharp
+using Aspid.FastTools.UIElements.Editors;
+
+image.AddOpenScriptCommand(target);
+// Двойной клик на элемент открывает скрипт 'target' в IDE
+```
+
+| Метод | Цель | Описание |
+|-------|------|----------|
+| `AddOpenScriptCommand(Object)` | `VisualElement` | Регистрирует обработчик двойного клика, открывающий исходный скрипт `MonoBehaviour` / `ScriptableObject` в IDE. |
+| `BindTo(SerializedObject)` | `VisualElement` | Вызывает `BindingExtensions.Bind` на элементе. |
+| `BindTo(SerializedObject, string propertyPath)` | `IBindable` | Устанавливает `bindingPath` и привязывается к указанному `SerializedObject`. |
+| `BindPropertyTo(SerializedProperty)` | `IBindable` | Вызывает `BindingExtensions.BindProperty` для переданного property. |
+| `Initialize(Enum defaultValue, bool includeObsoleteValues = false)` | `EnumField` / `EnumFlagsField` | Инициализирует поле указанным значением enum по умолчанию. |
+| `AddValueChanged(EventCallback)` / `RemoveValueChanged(...)` | `PropertyField` | Подписка / отписка от уведомлений об изменении свойства. |
+
+## USS custom-style helpers (`ICustomStyle`)
+
+```csharp
+using Aspid.FastTools.UIElements;
+
+private static readonly CustomStyleProperty ThemeProperty = new("--aspid-fasttools-prop-theme");
+
+void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
+{
+ if (evt.customStyle.TryGetByEnum(ThemeProperty, out ThemeStyle.Type theme))
+ ApplyTheme(theme);
+}
+```
+
+| Метод | Описание |
+|-------|----------|
+| `ICustomStyle.TryGetByEnum(CustomStyleProperty, out T)` | Резолвит USS custom-property со строковым значением и парсит её регистронезависимо как enum `T`. Используется во всех `*Style`-структурах с USS-driven enum (`ThemeStyle`, `StatusStyle`, `AspidLabelSizeStyle` и т. д.). |
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md.meta
new file mode 100644
index 00000000..33dbcc05
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Documentation/RU/VisualElementExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a181f7da19b70437eb557c0cda2dad2a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues.meta
similarity index 67%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues.meta
index f833786b..2580f461 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues.meta
@@ -1,5 +1,6 @@
fileFormatVersion: 2
-guid: 55b0a716a25414d609e45c84c5e34851
+guid: 7c561c66bd9a74b5b910a51cbf1bcca3
+folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
similarity index 67%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
index c1b162f2..d27664cf 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
@@ -1,5 +1,6 @@
fileFormatVersion: 2
-guid: 7f88981b89c7a48f196d36c976c31f20
+guid: e9d7c8b6a5f4e3d2c1b0a9f8e7d6c5b4
+folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab
new file mode 100644
index 00000000..3b8c25cf
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab
@@ -0,0 +1,112 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: DamageDealer
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 3bd9644f273549748da7b083e4de1b37, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.EnumValues::Aspid.FastTools.Samples.EnumValues.DamageDealer
+ _damageMultipliers:
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: 1
+ _values:
+ - _key: Physical
+ _value: 1
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Fire
+ _value: 1.5
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Ice
+ _value: 0.8
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Poison
+ _value: 0.6
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _damageColors:
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: {r: 1, g: 1, b: 1, a: 1}
+ _values:
+ - _key: Physical
+ _value: {r: 0.85, g: 0.85, b: 0.85, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Fire
+ _value: {r: 1, g: 0.5, b: 0.1, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Ice
+ _value: {r: 0.4, g: 0.8, b: 1, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Poison
+ _value: {r: 0.5, g: 0.9, b: 0.3, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _speedMultipliersByStatus:
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: 1
+ _values:
+ - _key: Burning, Slowed
+ _value: 0.4
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Burning
+ _value: 1
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Frozen
+ _value: 0.2
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Slowed
+ _value: 0.5
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _currentDamageType: 1
+ _activeEffects: 5
+ _baseDamage: 10
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
similarity index 74%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
index cfdabfd5..78d6e5a7 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: d6fe21a6dc34048eea8586d6afdbd9f2
+guid: b6af73ff1ec54f22b2b637c93cf4287b
PrefabImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md
new file mode 100644
index 00000000..f080c2ee
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md
@@ -0,0 +1,21 @@
+# EnumValues Sample
+
+A tiny combat damage system that maps enum members to typed values through `EnumValues`. `DamageDealer` picks a `DamageType` and `StatusEffect` in the Inspector, then on `Space` applies damage — pulling the damage multiplier, log color, and speed modifier from three `EnumValues` fields.
+
+Look at:
+- `Scripts/DamageDealer.cs:9` — `EnumValues` mapping `DamageType` to damage multiplier.
+- `Scripts/DamageDealer.cs:10` — `EnumValues` mapping `DamageType` to debug log color.
+- `Scripts/DamageDealer.cs:14` — `EnumValues` keyed on `[Flags]` enum `StatusEffect`; composite entries like `Burning | Slowed` must come before single-flag entries (see the inline comment).
+- `Scripts/DamageDealer.cs:30` — `GetValue` call on the Flags-keyed field.
+- `Scripts/StatusEffect.cs` — `[Flags]` enum used by the third mapping.
+
+## How to run
+
+Open `Scenes/EnumValues.unity` and enter Play Mode. The scene hosts a `DamageDealer` wired up from `Prefabs/EnumValues.prefab`, which is pre-seeded with:
+
+- `_damageMultipliers`: `Physical = 1.0`, `Fire = 1.5`, `Ice = 0.8`, `Poison = 0.6`.
+- `_damageColors`: grey / orange / cyan / acid-green per `DamageType`.
+- `_speedMultipliersByStatus`: `Burning | Slowed = 0.4` **first**, then `Burning = 1.0`, `Frozen = 0.2`, `Slowed = 0.5` — composite-first ordering is what makes the combined flag resolve to `0.4` instead of falling through to the first single-flag match.
+- `_currentDamageType = Fire`, `_activeEffects = Burning | Slowed`, `_baseDamage = 10`.
+
+Press `Space` and the Console prints `Fire hit: 15 dmg (speed mod: 0.40)` in orange. Change the enums in the Inspector (or toggle flags in `_activeEffects`) to see different lookups.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md.meta
new file mode 100644
index 00000000..7aa3b861
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5fb2e3aa6c2a846b4b53ffdd69fccf9b
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md
new file mode 100644
index 00000000..3d47f2d7
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md
@@ -0,0 +1,21 @@
+# Пример EnumValues
+
+Маленькая система боевого урона, которая сопоставляет члены enum типизированным значениям через `EnumValues`. `DamageDealer` выбирает `DamageType` и `StatusEffect` в Inspector, а по нажатию `Space` наносит урон — извлекая множитель урона, цвет лога и модификатор скорости из трёх полей `EnumValues`.
+
+Смотрите:
+- `Scripts/DamageDealer.cs:9` — `EnumValues`, сопоставляющий `DamageType` множителю урона.
+- `Scripts/DamageDealer.cs:10` — `EnumValues`, сопоставляющий `DamageType` цвету отладочного лога.
+- `Scripts/DamageDealer.cs:14` — `EnumValues` по `[Flags]` enum `StatusEffect`; композитные записи вроде `Burning | Slowed` должны идти до одиночных флагов (см. комментарий рядом).
+- `Scripts/DamageDealer.cs:30` — вызов `GetValue` на Flags-поле.
+- `Scripts/StatusEffect.cs` — `[Flags]` enum, используемый в третьем сопоставлении.
+
+## Как запустить
+
+Откройте `Scenes/EnumValues.unity` и войдите в Play Mode. В сцене есть `DamageDealer`, подключённый из `Prefabs/EnumValues.prefab`, который предзаполнен:
+
+- `_damageMultipliers`: `Physical = 1.0`, `Fire = 1.5`, `Ice = 0.8`, `Poison = 0.6`.
+- `_damageColors`: серый / оранжевый / голубой / ядовито-зелёный по `DamageType`.
+- `_speedMultipliersByStatus`: `Burning | Slowed = 0.4` **первой**, затем `Burning = 1.0`, `Frozen = 0.2`, `Slowed = 0.5` — порядок с композитом впереди именно и гарантирует, что комбинация флагов разрешается в `0.4`, а не проваливается на первое совпадение одиночного флага.
+- `_currentDamageType = Fire`, `_activeEffects = Burning | Slowed`, `_baseDamage = 10`.
+
+Нажмите `Space` — в Console появится оранжевое `Fire hit: 15 dmg (speed mod: 0.40)`. Меняйте значения enum в Inspector (или переключайте флаги в `_activeEffects`), чтобы увидеть другие варианты поиска.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta
new file mode 100644
index 00000000..cb485b44
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7a3b1c4d5e6f7081929304a5b6c7d8e9
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
index 78aeed3f..c445a598 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c5527f6f042e7408ebabfc82d150433b
+guid: d68d6fc528e76449db0a639caa762203
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity
new file mode 100644
index 00000000..8eeccc3e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity
@@ -0,0 +1,449 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 10
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 13
+ m_BakeOnSceneLoad: 0
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 512
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 256
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 1
+ m_PVRDenoiserTypeDirect: 1
+ m_PVRDenoiserTypeIndirect: 1
+ m_PVRDenoiserTypeAO: 1
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 1
+ m_PVRFilteringGaussRadiusAO: 1
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 3
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ buildHeightMesh: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &100001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &100002
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ serializedVersion: 13
+ m_Type: 1
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.80208
+ m_CookieSize2D: {x: 10, y: 10}
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 4
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ForceVisible: 0
+ m_ShapeRadius: 0
+ m_ShadowAngle: 0
+ m_LightUnit: 1
+ m_LuxAtDistance: 1
+ m_EnableSpotReflector: 1
+--- !u!4 &100003
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!114 &100004
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
+ m_UsePipelineSettings: 1
+ m_AdditionalLightsShadowResolutionTier: 2
+ m_CustomShadowLayers: 0
+ m_LightCookieSize: {x: 1, y: 1}
+ m_LightCookieOffset: {x: 0, y: 0}
+ m_SoftShadowQuality: 0
+ m_RenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_ShadowRenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_Version: 4
+ m_LightLayerMask: 1
+ m_ShadowLayerMask: 1
+ m_RenderingLayers: 1
+ m_ShadowRenderingLayers: 1
+--- !u!1 &200001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &200002
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+--- !u!20 &200003
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_Iso: 200
+ m_ShutterSpeed: 0.005
+ m_Aperture: 16
+ m_FocusDistance: 10
+ m_FocalLength: 50
+ m_BladeCount: 5
+ m_Curvature: {x: 2, y: 11}
+ m_BarrelClipping: 0.25
+ m_Anamorphism: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &200004
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &200005
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
+ m_RenderShadows: 1
+ m_RequiresDepthTextureOption: 2
+ m_RequiresOpaqueTextureOption: 2
+ m_CameraType: 0
+ m_Cameras: []
+ m_RendererIndex: -1
+ m_VolumeLayerMask:
+ serializedVersion: 2
+ m_Bits: 1
+ m_VolumeTrigger: {fileID: 0}
+ m_VolumeFrameworkUpdateModeOption: 2
+ m_RenderPostProcessing: 0
+ m_Antialiasing: 0
+ m_AntialiasingQuality: 2
+ m_StopNaN: 0
+ m_Dithering: 0
+ m_ClearDepth: 1
+ m_AllowXRRendering: 1
+ m_AllowHDROutput: 1
+ m_UseScreenCoordOverride: 0
+ m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
+ m_RequiresDepthTexture: 0
+ m_RequiresColorTexture: 0
+ m_TaaSettings:
+ m_Quality: 3
+ m_FrameInfluence: 0.1
+ m_JitterScale: 1
+ m_MipBias: 0
+ m_VarianceClampScale: 0.9
+ m_ContrastAdaptiveSharpening: 0
+ m_Version: 2
+--- !u!1001 &1501333895
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 1234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_Name
+ value: EnumValues
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+--- !u!1660057539 &9223372036854775807
+SceneRoots:
+ m_ObjectHideFlags: 0
+ m_Roots:
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 1501333895}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta
new file mode 100644
index 00000000..8d581a24
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: dbdae9a1d49d4a6ca82c5a3ab5bc1d24
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts.meta
new file mode 100644
index 00000000..46f29a9c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d3bd9c0a75f8b4c77986e2aeb360dc13
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
similarity index 76%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
index 5199cfd0..9e824ff2 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
@@ -1,8 +1,8 @@
{
- "name": "Aspid.UnityFastTools.ProfilerMarkers",
+ "name": "Aspid.FastTools.Samples.EnumValues",
"rootNamespace": "",
"references": [
- "GUID:7c010b89992542508a6b6189977e64d4"
+ "Aspid.FastTools.Unity"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta
new file mode 100644
index 00000000..46d73b13
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: fe2fa73e3f3d499f8e95516349d3768a
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs
new file mode 100644
index 00000000..957d9800
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ public sealed class DamageDealer : MonoBehaviour
+ {
+ [SerializeField] private EnumValues _damageMultipliers;
+ [SerializeField] private EnumValues _damageColors;
+
+ // Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins, so list
+ // composite entries BEFORE their constituent flags — otherwise the single-flag entry matches first.
+ [SerializeField] private EnumValues _speedMultipliersByStatus;
+
+ [SerializeField] private DamageType _currentDamageType = DamageType.Physical;
+ [SerializeField] private StatusEffect _activeEffects = StatusEffect.None;
+ [SerializeField] private float _baseDamage = 10f;
+
+ private void Update()
+ {
+ if (!Input.GetKeyDown(KeyCode.Space)) return;
+ DealDamage();
+ }
+
+ private void DealDamage()
+ {
+ var multiplier = _damageMultipliers.GetValue(_currentDamageType);
+ var color = _damageColors.GetValue(_currentDamageType);
+ var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
+ var finalDamage = _baseDamage * multiplier;
+ var colorHex = ColorUtility.ToHtmlStringRGB(color);
+
+ Debug.Log($"{_currentDamageType} hit: {finalDamage} dmg (speed mod: {speedMod:F2})");
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta
new file mode 100644
index 00000000..2f16782f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3bd9644f273549748da7b083e4de1b37
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs
new file mode 100644
index 00000000..3a42c2fd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs
@@ -0,0 +1,11 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ public enum DamageType
+ {
+ Physical,
+ Fire,
+ Ice,
+ Poison,
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta
new file mode 100644
index 00000000..7b7289d5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8617fbe193994a1d9ffd71e02ac3c5b5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs
new file mode 100644
index 00000000..8922e133
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs
@@ -0,0 +1,17 @@
+using System;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ [Flags]
+ public enum StatusEffect
+ {
+ None = 0,
+ Burning = 1,
+ Frozen = 2,
+ Slowed = 4,
+ Stunned = 8,
+ // Combinations such as Burning | Slowed are matched via HasFlag semantics in EnumValues
+ // and can be registered as their own entry with a dedicated value.
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta
new file mode 100644
index 00000000..52cb56ff
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f66e403cd7044cc96c00ad003ccc68a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids.meta
new file mode 100644
index 00000000..d3271a04
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5ba755d592364471fa27430d0b3b2299
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data.meta
new file mode 100644
index 00000000..81bb9e92
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: aabb1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset
new file mode 100644
index 00000000..f117b9f8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset
@@ -0,0 +1,23 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 70e390d14588e72409a6a95aca1461a7, type: 3}
+ m_Name: IdRegistry_EnemyId
+ m_EditorClassIdentifier: Aspid.FastTools.Unity::Aspid.FastTools.Ids.IdRegistry
+ _targetStructType: Aspid.FastTools.Samples.Ids.EnemyId, Aspid.FastTools.Samples.Ids,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _nextId: 5
+ _ids: 01000000020000000300000004000000
+ _names:
+ - fly_enemy_dragon
+ - walk_enemy_goblin
+ - walk_enemy_orc
+ - walk_enemy_skeleton
diff --git a/Aspid.UnityFastTools/Assets/Settings/Renderer/URP Global Settings.asset.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta
old mode 100755
new mode 100644
similarity index 79%
rename from Aspid.UnityFastTools/Assets/Settings/Renderer/URP Global Settings.asset.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta
index 81b84f2a..ae394d4f
--- a/Aspid.UnityFastTools/Assets/Settings/Renderer/URP Global Settings.asset.meta
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 18dc0cd2c080841dea60987a38ce93fa
+guid: 3b547b6a17aa54f5f8c80dd0aa23d0e2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset
new file mode 100644
index 00000000..ba1dde9a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: fly_enemy_dragon
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: fly_enemy_dragon
+ _id: 1
+ _displayName: Dragon
+ _maxHealth: 500
+ _moveSpeed: 4
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta
new file mode 100644
index 00000000..5fa2fe1e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: edca3936962f4004af0272178ad75bfd
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset
new file mode 100644
index 00000000..c258690b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_goblin
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_goblin
+ _id: 2
+ _displayName: Goblin
+ _maxHealth: 80
+ _moveSpeed: 3.5
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta
new file mode 100644
index 00000000..1a107503
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d92adbaedb324bff9fcbe616fbf03416
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset
new file mode 100644
index 00000000..91665de9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_orc
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_orc
+ _id: 3
+ _displayName: Orc
+ _maxHealth: 150
+ _moveSpeed: 2
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta
new file mode 100644
index 00000000..5c039b9d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fc61e5b486184ad4b3ed8c54016f6d5f
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset
new file mode 100644
index 00000000..cbf097dc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_skeleton
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_skeleton
+ _id: 4
+ _displayName: Skeleton
+ _maxHealth: 60
+ _moveSpeed: 3
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta
new file mode 100644
index 00000000..0de4d09b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 56ba102dc1764873b64e5f7803207cc6
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs.meta
new file mode 100644
index 00000000..8b71e2ab
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bbcc1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab
new file mode 100644
index 00000000..79633111
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab
@@ -0,0 +1,54 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: Ids
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ce0efc593ee24a5d97d8511d669c9de7, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemySpawner
+ _catalog:
+ - {fileID: 11400000, guid: edca3936962f4004af0272178ad75bfd, type: 2}
+ - {fileID: 11400000, guid: d92adbaedb324bff9fcbe616fbf03416, type: 2}
+ - {fileID: 11400000, guid: fc61e5b486184ad4b3ed8c54016f6d5f, type: 2}
+ - {fileID: 11400000, guid: 56ba102dc1764873b64e5f7803207cc6, type: 2}
+ _spawnTarget:
+ __stringId: walk_enemy_orc
+ _id: 3
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta
new file mode 100644
index 00000000..059c2697
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bbe54125698a41cb8a8dab897d567ff8
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md
new file mode 100644
index 00000000..84c30649
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md
@@ -0,0 +1,30 @@
+# Ids Sample
+
+Demonstrates the `IId` / `IdRegistry` / `[UniqueId]` trio: fields show a human-readable string in the Inspector while serializing as a stable integer, and the Inspector catches collisions at edit-time.
+
+## How it works
+
+- `IId` — a marker interface declaring the `int Id { get; }` property.
+- `IdRegistry` — a `ScriptableObject` that binds a struct type to a list of `(Id, Name)` entries and keeps the name ↔ int map available at runtime. The property drawer renders a dropdown sourced from this registry.
+- `[UniqueId]` — validates at edit-time that no two `ScriptableObject` assets share the same resolved integer ID.
+
+## Scenario
+
+An enemy catalog. Each `EnemyDefinition` asset holds a unique `EnemyId` plus display data (`_displayName`, `_maxHealth`, `_moveSpeed`). An `EnemySpawner` picks a target `EnemyId` via dropdown and looks the matching asset up in its catalog on `Start()`.
+
+Look at:
+
+- `Scripts/EnemyId.cs` — `partial struct : IId`. `IdStructGenerator` emits `__stringId`, `_id`, and the `Id` property.
+- `Scripts/EnemyDefinition.cs:10` — `[UniqueId]` on a serialized `EnemyId` field prevents duplicate IDs across assets.
+- `Data/IdRegistry_EnemyId.asset` — the registry binding names (`fly_enemy_dragon`, `walk_enemy_goblin`, `walk_enemy_orc`, `walk_enemy_skeleton`) to stable ints.
+- `Scripts/EnemySpawner.cs:9` — dropdown-selected `EnemyId` resolved to `int` at runtime via `.Id`.
+
+## How to run
+
+Open `Scenes/Ids.unity` — it contains an `EnemySpawner` GameObject (also available as `Prefabs/Ids.prefab`). Wire it up once:
+
+1. Drag the four `Data/*_enemy_*.asset` files into the spawner's `Catalog` array.
+2. Pick a target enemy from the `Spawn Target` dropdown — the picker is sourced from `IdRegistry_EnemyId`.
+3. Enter Play Mode — the Console logs the resolved `EnemyDefinition` (display name, HP, move speed). Switch the dropdown to see different lookups.
+
+To create more entries, open `Data/IdRegistry_EnemyId.asset` to add registry rows, then `Assets > Create > Aspid > FastTools > Samples > Enemy Definition` for the asset side.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md.meta
new file mode 100644
index 00000000..55d313e2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 79a0f34c9d7ac4d45a00d0f1a9909de4
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md
new file mode 100644
index 00000000..4cacef15
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md
@@ -0,0 +1,30 @@
+# Пример Ids
+
+Демонстрирует связку `IId` / `IdRegistry` / `[UniqueId]`: поля показывают человекочитаемую строку в Inspector, а сериализуются как стабильное целое число, и Inspector ловит коллизии прямо при редактировании.
+
+## Как это работает
+
+- `IId` — маркерный интерфейс, объявляющий свойство `int Id { get; }`.
+- `IdRegistry` — `ScriptableObject`, связывающий тип-структуру со списком записей `(Id, Name)` и сохраняющий отображение имя ↔ int доступным во рантайме. Property drawer отрисовывает выпадающий список, источником которого является этот реестр.
+- `[UniqueId]` — валидирует во время редактирования, что ни два `ScriptableObject`-актива не имеют одинакового результирующего целочисленного ID.
+
+## Сценарий
+
+Каталог врагов. Каждый актив `EnemyDefinition` хранит уникальный `EnemyId` плюс данные для отображения (`_displayName`, `_maxHealth`, `_moveSpeed`). `EnemySpawner` выбирает целевой `EnemyId` через выпадающий список и ищет соответствующий актив в своём каталоге в `Start()`.
+
+Смотрите:
+
+- `Scripts/EnemyId.cs` — `partial struct : IId`. `IdStructGenerator` генерирует `__stringId`, `_id` и свойство `Id`.
+- `Scripts/EnemyDefinition.cs:10` — `[UniqueId]` на сериализованном поле `EnemyId` предотвращает дублирование ID между активами.
+- `Data/IdRegistry_EnemyId.asset` — реестр, связывающий имена (`fly_enemy_dragon`, `walk_enemy_goblin`, `walk_enemy_orc`, `walk_enemy_skeleton`) со стабильными целочисленными значениями.
+- `Scripts/EnemySpawner.cs:9` — выбранный из списка `EnemyId`, преобразуется в `int` во время выполнения через `.Id`.
+
+## Как запустить
+
+Откройте `Scenes/Ids.unity` — в сцене лежит GameObject `EnemySpawner` (также доступен как `Prefabs/Ids.prefab`). Подключите его один раз:
+
+1. Перетащите четыре актива `Data/*_enemy_*.asset` в массив `Catalog` спавнера.
+2. Выберите цель в выпадающем списке `Spawn Target` — список берётся из `IdRegistry_EnemyId`.
+3. Войдите в Play Mode — в Console появится лог найденного `EnemyDefinition` (display name, HP, move speed). Меняйте значение в списке, чтобы увидеть другие варианты поиска.
+
+Чтобы добавить новые записи, откройте `Data/IdRegistry_EnemyId.asset` для строк реестра и `Assets > Create > Aspid > FastTools > Samples > Enemy Definition` — для соответствующих ассетов.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md.meta
new file mode 100644
index 00000000..bdd6dddf
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ad6e4f7081924b3c5d6e7f80910a1b2c
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes.meta
new file mode 100644
index 00000000..9ba291b0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ccdd1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
similarity index 75%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
index 42337b6b..4731c200 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
@@ -119,7 +119,7 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
---- !u!1 &1956111
+--- !u!1 &100001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -127,9 +127,9 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 1956113}
- - component: {fileID: 1956112}
- - component: {fileID: 1956114}
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
@@ -137,22 +137,22 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!108 &1956112
+--- !u!108 &100002
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
- serializedVersion: 11
+ serializedVersion: 13
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
- m_CookieSize: 10
+ m_CookieSize2D: {x: 10, y: 10}
m_Shadows:
m_Type: 2
m_Resolution: -1
@@ -197,18 +197,18 @@ Light:
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
- m_ShadowRadius: 0
+ m_ShapeRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
---- !u!4 &1956113
+--- !u!4 &100003
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
@@ -217,13 +217,13 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
---- !u!114 &1956114
+--- !u!114 &100004
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
@@ -246,7 +246,7 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
---- !u!1 &628555577
+--- !u!1 &200001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -254,10 +254,10 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 628555580}
- - component: {fileID: 628555579}
- - component: {fileID: 628555578}
- - component: {fileID: 628555581}
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
@@ -265,21 +265,21 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!81 &628555578
+--- !u!81 &200002
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
---- !u!20 &628555579
+--- !u!20 &200003
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
@@ -324,13 +324,13 @@ Camera:
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
---- !u!4 &628555580
+--- !u!4 &200004
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
@@ -339,13 +339,13 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &628555581
+--- !u!114 &200005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
@@ -383,54 +383,67 @@ MonoBehaviour:
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
---- !u!1 &642957226
-GameObject:
+--- !u!1001 &1455822910
+PrefabInstance:
m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 642957227}
- - component: {fileID: 642957228}
- m_Layer: 0
- m_Name: Visual Element Inspector
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!4 &642957227
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -0, y: 0, z: -0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &642957228
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: dcfbfa43e527425e8881c33eec85bec3, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.UnityFastTools.Samples.VisualElements.VisualElementInspector
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 1234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_Name
+ value: Ids
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- - {fileID: 628555580}
- - {fileID: 1956113}
- - {fileID: 642957227}
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 1455822910}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta
new file mode 100644
index 00000000..bad758d2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a978c0327ba8450696b7a546a1c26959
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts.meta
new file mode 100644
index 00000000..3926b383
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4b48604dab5254cd296df004a49949ff
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
similarity index 74%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
index f0f15e45..113ba780 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
@@ -1,7 +1,9 @@
{
- "name": "Aspid.UnityFastTools",
+ "name": "Aspid.FastTools.Samples.Ids",
"rootNamespace": "",
- "references": [],
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta
new file mode 100644
index 00000000..a62fec60
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 34984631d7b44f53aa977da146924369
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs
new file mode 100644
index 00000000..7910e72b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs
@@ -0,0 +1,22 @@
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ [CreateAssetMenu(fileName = "New Enemy Definition", menuName = "Aspid/FastTools/Samples/Enemy Definition")]
+ public class EnemyDefinition : ScriptableObject
+ {
+ [UniqueId]
+ [SerializeField] private EnemyId _id;
+
+ [SerializeField] private string _displayName;
+ [SerializeField] private int _maxHealth;
+ [SerializeField] private float _moveSpeed;
+
+ public EnemyId Id => _id;
+ public string DisplayName => _displayName;
+ public int MaxHealth => _maxHealth;
+ public float MoveSpeed => _moveSpeed;
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta
new file mode 100644
index 00000000..43009abb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 953e77fd8a034ac6974fb9fa4c49aae4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs
new file mode 100644
index 00000000..632905fd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs
@@ -0,0 +1,11 @@
+using System;
+using Aspid.FastTools.Ids;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ // Declaring `partial struct : IId` triggers IdStructGenerator, which emits __stringId, _id,
+ // and the Id property — so consumers only ever write the one-liner below.
+ [Serializable]
+ public partial struct EnemyId : IId { }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta
new file mode 100644
index 00000000..9b790019
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1d2651027ff84fa5a6f13b1bc0ab8978
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs
new file mode 100644
index 00000000..52e3c67b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs
@@ -0,0 +1,24 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ public class EnemySpawner : MonoBehaviour
+ {
+ [SerializeField] private EnemyDefinition[] _catalog;
+ [SerializeField] private EnemyId _spawnTarget;
+
+ private void Start()
+ {
+ foreach (var enemy in _catalog)
+ {
+ if (enemy.Id.Id != _spawnTarget.Id) continue;
+
+ Debug.Log($"Spawning {enemy.DisplayName} — HP: {enemy.MaxHealth}, Speed: {enemy.MoveSpeed}");
+ return;
+ }
+
+ Debug.LogWarning($"No EnemyDefinition found for id {_spawnTarget.Id}");
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta
new file mode 100644
index 00000000..4729ced3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ce0efc593ee24a5d97d8511d669c9de7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs.meta
new file mode 100644
index 00000000..9f9d9055
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 67e55e4b150b9426293f6e52bf919fdd
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab
new file mode 100644
index 00000000..d4c819db
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab
@@ -0,0 +1,46 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: FrameProfiler
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 81773b495ca84416af8f5b4210f650f6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.ProfilerMarkers::Aspid.FastTools.Samples.ProfilerMarkers.FrameProfiler
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta
new file mode 100644
index 00000000..3370b575
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4d438180f96141d9b383946bbf647c1c
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md
new file mode 100644
index 00000000..098fb274
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md
@@ -0,0 +1,61 @@
+# ProfilerMarkers Sample
+
+Demonstrates the three supported usage forms of `this.Marker()`. In every
+form the Aspid.FastTools source generator replaces each call site with a
+unique `ProfilerMarker` keyed by `(type, method, line)`. `.WithName(...)`
+is optional — when omitted, the generator auto-names the marker after the
+enclosing type and method.
+
+## Supported forms
+
+1. **Block `using`-statement with an explicit name.** Marker scope is the
+ block; the full display name is `"{TypeName}.{WithName} ({line})"`, so
+ pass the short suffix only.
+ ```csharp
+ using (this.Marker().WithName("Physics")) // Profiler: "FrameProfiler.Physics ()"
+ SimulatePhysics();
+ ```
+2. **`using`-declaration without `WithName`.** Marker scope is the rest of
+ the enclosing block; the generator auto-names the marker after the
+ enclosing method.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateInput ()"
+ ```
+3. **Combined form.** A method-wide `using`-declaration paired with a
+ nested `using`-statement — useful when you want one outer marker for the
+ whole method and a narrower marker around a hot sub-step. Both get
+ auto-named after the method; their different line numbers produce
+ distinct Profiler entries.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateAudio ()"
+ using (this.Marker()) // Profiler: "FrameProfiler.SimulateAudio ()"
+ {
+ // Some code.
+ }
+ ```
+
+## How to run
+
+1. Open `Scenes/ProfilerMarkers.unity` — it already contains a `FrameProfiler`
+ GameObject. The `Prefabs/ProfilerMarkers.prefab` variant can be dropped
+ into your own scenes.
+2. Open `Window → Analysis → Profiler`.
+3. Enter Play Mode and inspect the CPU track for the named markers.
+
+## Where to look
+
+- `Scripts/FrameProfiler.cs:19,22,25` — three top-level markers with explicit
+ names in `Update` (`FrameProfiler.Physics`, `FrameProfiler.AI`,
+ `FrameProfiler.Render`).
+- `Scripts/FrameProfiler.cs:44` — nested `FrameProfiler.AI.Agent` marker
+ emitted once per loop iteration in `SimulateAI`, appearing under the `AI`
+ scope.
+- `Scripts/FrameProfiler.cs:68` — `using`-declaration form without `WithName`
+ in `SimulateInput`; the generator names the marker after the method.
+- `Scripts/FrameProfiler.cs:84,87` — combined form in `SimulateAudio`: an
+ outer method-wide `using`-declaration plus a nested `using`-statement
+ around `MixAudio()`.
+
+Every `using` scope (statement or declaration) starts and ends the
+generated marker automatically, so the Profiler shows precise self-time
+for every phase.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta
new file mode 100644
index 00000000..95a29bc6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b168656911e1546e5b8b5036803240be
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md
new file mode 100644
index 00000000..554a9549
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md
@@ -0,0 +1,61 @@
+# Пример ProfilerMarkers
+
+Демонстрирует три поддерживаемые формы вызова `this.Marker()`. В каждой
+форме source-генератор Aspid.FastTools заменяет место вызова уникальным
+`ProfilerMarker`, привязанным к `(type, method, line)`. `.WithName(...)`
+не обязателен — если его не указать, генератор автоматически назовёт
+маркер по имени содержащего типа и метода.
+
+## Поддерживаемые формы
+
+1. **Блок `using`-statement с явным именем.** Область маркера — блок;
+ полное отображаемое имя имеет вид `"{TypeName}.{WithName} ({line})"`,
+ поэтому в `WithName` передавайте только короткий суффикс.
+ ```csharp
+ using (this.Marker().WithName("Physics")) // Profiler: "FrameProfiler.Physics ()"
+ SimulatePhysics();
+ ```
+2. **`using`-declaration без `WithName`.** Область маркера — остаток
+ содержащего блока; генератор именует маркер по имени содержащего
+ метода.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateInput ()"
+ ```
+3. **Комбинированная форма.** Метод-широкий `using`-declaration в паре с
+ вложенным `using`-statement — удобно, когда нужен один внешний маркер
+ на весь метод и более узкий — на «горячий» подэтап. Оба маркера
+ получают автоимя по методу; разные номера строк дают разные записи в
+ Profiler.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateAudio ()"
+ using (this.Marker()) // Profiler: "FrameProfiler.SimulateAudio ()"
+ {
+ // Некоторый код.
+ }
+ ```
+
+## Как запустить
+
+1. Откройте `Scenes/ProfilerMarkers.unity` — в сцене уже есть GameObject с
+ `FrameProfiler`. Вариант `Prefabs/ProfilerMarkers.prefab` можно добавить
+ в собственные сцены.
+2. Откройте `Window → Analysis → Profiler`.
+3. Войдите в Play Mode и осмотрите CPU-трек на наличие именованных маркеров.
+
+## Где смотреть
+
+- `Scripts/FrameProfiler.cs:19,22,25` — три маркера верхнего уровня с явными
+ именами в `Update` (`FrameProfiler.Physics`, `FrameProfiler.AI`,
+ `FrameProfiler.Render`).
+- `Scripts/FrameProfiler.cs:44` — вложенный маркер `FrameProfiler.AI.Agent`
+ в `SimulateAI`, генерируемый раз за итерацию цикла, отображается под
+ областью `AI`.
+- `Scripts/FrameProfiler.cs:68` — форма `using`-declaration без `WithName`
+ в `SimulateInput`; генератор именует маркер по имени метода.
+- `Scripts/FrameProfiler.cs:84,87` — комбинированная форма в
+ `SimulateAudio`: внешний метод-широкий `using`-declaration плюс
+ вложенный `using`-statement вокруг `MixAudio()`.
+
+Каждый блок `using` (statement или declaration) автоматически запускает и
+завершает сгенерированный маркер, поэтому Profiler показывает точное
+self-time для каждой фазы.
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta
new file mode 100644
index 00000000..91185974
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9c5d3e6f70819203b4c5d6e7f8091a2b
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity
similarity index 89%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity
index 404b9673..750900c1 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity
@@ -119,7 +119,7 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
---- !u!1 &1956111
+--- !u!1 &100001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -127,9 +127,9 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 1956113}
- - component: {fileID: 1956112}
- - component: {fileID: 1956114}
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
@@ -137,22 +137,22 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!108 &1956112
+--- !u!108 &100002
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
- serializedVersion: 11
+ serializedVersion: 13
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
- m_CookieSize: 10
+ m_CookieSize2D: {x: 10, y: 10}
m_Shadows:
m_Type: 2
m_Resolution: -1
@@ -197,18 +197,18 @@ Light:
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
- m_ShadowRadius: 0
+ m_ShapeRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
---- !u!4 &1956113
+--- !u!4 &100003
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
@@ -217,17 +217,17 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
---- !u!114 &1956114
+--- !u!114 &100004
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
- m_Name:
+ m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
m_UsePipelineSettings: 1
m_AdditionalLightsShadowResolutionTier: 2
@@ -246,7 +246,7 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
---- !u!1 &628555577
+--- !u!1 &200001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -254,10 +254,10 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 628555580}
- - component: {fileID: 628555579}
- - component: {fileID: 628555578}
- - component: {fileID: 628555581}
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
@@ -265,21 +265,21 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!81 &628555578
+--- !u!81 &200002
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
---- !u!20 &628555579
+--- !u!20 &200003
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
@@ -324,13 +324,13 @@ Camera:
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
---- !u!4 &628555580
+--- !u!4 &200004
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
@@ -339,17 +339,17 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &628555581
+--- !u!114 &200005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
- m_Name:
+ m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
@@ -383,7 +383,7 @@ MonoBehaviour:
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
---- !u!1 &642957226
+--- !u!1 &300001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -391,46 +391,46 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 642957227}
- - component: {fileID: 642957228}
+ - component: {fileID: 300002}
+ - component: {fileID: 300003}
m_Layer: 0
- m_Name: Marker Test
+ m_Name: FrameProfiler
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!4 &642957227
+--- !u!4 &300002
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
+ m_GameObject: {fileID: 300001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -0, y: 0, z: -0}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &642957228
+--- !u!114 &300003
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
+ m_GameObject: {fileID: 300001}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 199696d16b85e4112a63975aa9dcfd55, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.UnityFastTools.Samples.ProfilerMarkers.MarkerTest
+ m_Script: {fileID: 11500000, guid: 81773b495ca84416af8f5b4210f650f6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.ProfilerMarkers::Aspid.FastTools.Samples.ProfilerMarkers.FrameProfiler
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- - {fileID: 628555580}
- - {fileID: 1956113}
- - {fileID: 642957227}
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 300002}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta
new file mode 100644
index 00000000..7adfa9b3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 345b39adb7834b58b853bfeee78228bb
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts.meta
rename to Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts.meta
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef
new file mode 100644
index 00000000..092138d3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Aspid.FastTools.Samples.ProfilerMarkers",
+ "rootNamespace": "",
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta
new file mode 100644
index 00000000..99365ca0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 240a32c5c53f4b2dafe608de70ef1eb2
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs
new file mode 100644
index 00000000..02b328be
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs
@@ -0,0 +1,107 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.ProfilerMarkers
+{
+ public sealed class FrameProfiler : MonoBehaviour
+ {
+ [SerializeField] [Min(1)] private int _physicsLoad = 5000;
+ [SerializeField] [Min(1)] private int _aiAgents = 20;
+ [SerializeField] [Min(1)] private int _aiStepsPerAgent = 500;
+ [SerializeField] [Min(1)] private int _renderLoad = 3000;
+ [SerializeField] [Min(1)] private int _inputLoad = 1500;
+ [SerializeField] [Min(1)] private int _audioLoad = 2000;
+
+ private void Update()
+ {
+ // Every call site of this.Marker() becomes a unique ProfilerMarker.
+ // Display name: "{TypeName}.{WithName-or-method} ({line})".
+ using (this.Marker().WithName("Physics")) // Profiler: FrameProfiler.Physics (19)
+ SimulatePhysics();
+
+ using (this.Marker().WithName("AI")) // Profiler: FrameProfiler.AI (22)
+ SimulateAI();
+
+ using (this.Marker().WithName("Render")) // Profiler: FrameProfiler.Render (25)
+ SimulateRender();
+
+ SimulateInput();
+ SimulateAudio();
+ }
+
+ private void SimulatePhysics()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _physicsLoad; i++)
+ sum += Mathf.Sqrt(i);
+ _ = sum;
+ }
+
+ private void SimulateAI()
+ {
+ for (var agent = 0; agent < _aiAgents; agent++)
+ {
+ using (this.Marker().WithName("AI.Agent")) // Profiler: FrameProfiler.AI.Agent (44)
+ StepAgent();
+ }
+ }
+
+ private void StepAgent()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _aiStepsPerAgent; i++)
+ sum += Mathf.Sin(i);
+ _ = sum;
+ }
+
+ private void SimulateRender()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _renderLoad; i++)
+ sum += Mathf.Cos(i);
+ _ = sum;
+ }
+
+ // using-declaration without .WithName(): auto-named after the method.
+ private void SimulateInput()
+ {
+ using var _ = this.Marker(); // Profiler: FrameProfiler.SimulateInput (68)
+ DoInputWork();
+ }
+
+ private void DoInputWork()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _inputLoad; i++)
+ sum += Mathf.Tan(i);
+ _ = sum;
+ }
+
+ // Combined form: outer method-wide using-declaration + nested using-statement.
+ // Both auto-named after the method; distinct because their line numbers differ.
+ private void SimulateAudio()
+ {
+ using var _ = this.Marker(); // Profiler: FrameProfiler.SimulateAudio (84)
+ PrepareAudio();
+
+ using (this.Marker()) // Profiler: FrameProfiler.SimulateAudio (87)
+ MixAudio();
+ }
+
+ private void PrepareAudio()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _audioLoad; i++)
+ sum += Mathf.Sqrt(i);
+ _ = sum;
+ }
+
+ private void MixAudio()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _audioLoad; i++)
+ sum += Mathf.Cos(i);
+ _ = sum;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta
new file mode 100644
index 00000000..209bacc8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 81773b495ca84416af8f5b4210f650f6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types.meta
new file mode 100644
index 00000000..ccc47fc4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5549194b371a41d08e429360a65e08ff
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs.meta
new file mode 100644
index 00000000..84e9ac9a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d7c22acfa3b44fdeba05b6ac683bbc1b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab
new file mode 100644
index 00000000..fc0897f2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab
@@ -0,0 +1,56 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: AbilitySelector
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5c99d520a5f74297b09811a16d0e65fd, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Types::Aspid.FastTools.Samples.Types.AbilitySelector
+ _abilityType:
+ _assemblyQualifiedName: Aspid.FastTools.Samples.Types.Dash, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _modifierTypes:
+ - Aspid.FastTools.Samples.Types.CooldownReductionModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - Aspid.FastTools.Samples.Types.DoubleDamageModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - Aspid.FastTools.Samples.Types.RangeBoostModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta
new file mode 100644
index 00000000..7a58b3f2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c922b4c1592c4065b541f02f0acc425b
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab
new file mode 100644
index 00000000..c5d01843
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab
@@ -0,0 +1,47 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &9120107065992179554
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 3305627272239008027}
+ - component: {fileID: 9066011880317238500}
+ m_Layer: 0
+ m_Name: Enemy
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &3305627272239008027
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 9120107065992179554}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &9066011880317238500
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 9120107065992179554}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0667ffc8ad8b4dbba538545a568350f3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Types::Aspid.FastTools.Samples.Types.FastEnemy
+ _health: 100
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta
new file mode 100644
index 00000000..c782e275
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f2ebce908ab18403bbddb4e5101b460a
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/README.md b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/README.md
new file mode 100644
index 00000000..038dca69
--- /dev/null
+++ b/Aspid.FastTools/Assets/Aspid/FastTools/Samples~/Types/README.md
@@ -0,0 +1,21 @@
+# Types Sample
+
+A tiny ability system that demonstrates polymorphic type selection in the Unity Inspector using `SerializableType`, `TypeSelectorAttribute`, and `ComponentTypeSelector`. The player picks an `Ability` subclass and a list of `AbilityModifier` subclasses; enemies use `ComponentTypeSelector` so the concrete enemy script can be hot-swapped from the Inspector.
+
+Look at:
+
+- `Scripts/Abilities/AbilitySelector.cs:20` — `SerializableType` field, constrained picker for a single subtype.
+- `Scripts/Abilities/AbilitySelector.cs:25` — `[TypeSelector(typeof(AbilityModifier))]` on a `string[]` field.
+- `Scripts/Enemies/EnemyBase.cs:18` — `ComponentTypeSelector` declaration that swaps the attached script in place.
+
+Both Type drawers ship a UIToolkit and an IMGUI rendering path. Parallel `IMGUI*` variants force the IMGUI path so you can compare them side by side or migrate IMGUI-only projects:
+
+- `Scripts/Abilities/IMGUIAbilitySelector.cs` + `Scripts/Editor/IMGUIAbilitySelectorEditor.cs` — same `SerializableType